mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-04-17 14:46:37 +08:00
Compare commits
163 Commits
pure-dev
...
multipleDb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e960db0d3e | ||
|
|
eb2c05e9df | ||
|
|
353a6b9d0c | ||
|
|
5d2d269f11 | ||
|
|
9acb157fae | ||
|
|
4198b53996 | ||
|
|
fdec9ed6b8 | ||
|
|
84cd83894b | ||
|
|
18dd177961 | ||
|
|
41f91ea12d | ||
|
|
91bf5f93cd | ||
|
|
9445fa8005 | ||
|
|
6b491d1246 | ||
|
|
6b47ae232d | ||
|
|
536c3cc56b | ||
|
|
b75a8cb60d | ||
|
|
17bc4ade84 | ||
|
|
aea0896356 | ||
|
|
ae82a2d1cf | ||
|
|
2412bc1da4 | ||
|
|
42b00515eb | ||
|
|
f3c5d0862b | ||
|
|
e832921edf | ||
|
|
0c0ead26c0 | ||
|
|
f9a018638b | ||
|
|
d5ca8ddf1e | ||
|
|
650c29e75a | ||
|
|
ed5c20c612 | ||
|
|
49f1d1a8fa | ||
|
|
a87d6345c2 | ||
|
|
d83db53acb | ||
|
|
c944bd3b0e | ||
|
|
751cc3cadb | ||
|
|
80fe1116a8 | ||
|
|
9aaa88ef51 | ||
|
|
ef2d00a254 | ||
|
|
d38159f68b | ||
|
|
dd29c9a2fa | ||
|
|
ca1b8a728d | ||
|
|
6b647cf4ea | ||
|
|
0e6f79c28e | ||
|
|
894d4eb051 | ||
|
|
8d9c5bb762 | ||
|
|
7a916fc78e | ||
|
|
73db2a202a | ||
|
|
f9890bdc7f | ||
|
|
8a0c0de8a1 | ||
|
|
8c940126b5 | ||
|
|
71b7b7cc79 | ||
|
|
f6be4ad7ac | ||
|
|
f6cbe899c6 | ||
|
|
7598c8319f | ||
|
|
a798f36529 | ||
|
|
59d9674aeb | ||
|
|
36ed5400be | ||
|
|
6378c69764 | ||
|
|
b44db20938 | ||
|
|
61cd7b42d4 | ||
|
|
77d64796e0 | ||
|
|
a4001c21b1 | ||
|
|
9e1d01774f | ||
|
|
be4f0a2a90 | ||
|
|
c45c17748e | ||
|
|
57ad7ae1a3 | ||
|
|
998d97b669 | ||
|
|
453d95a460 | ||
|
|
d55545849a | ||
|
|
22ba44c271 | ||
|
|
ae2cc7ad9b | ||
|
|
c880f32d33 | ||
|
|
7b20b68b6a | ||
|
|
974f264272 | ||
|
|
bcbb2b5139 | ||
|
|
e09aaa2dc7 | ||
|
|
e3178d7579 | ||
|
|
b59dfbc3fd | ||
|
|
0f21688b3c | ||
|
|
801e30c1dc | ||
|
|
7049175827 | ||
|
|
f27a5a135b | ||
|
|
82ad9e249a | ||
|
|
fcff711a04 | ||
|
|
0c78b8d868 | ||
|
|
b6b54164a8 | ||
|
|
983daddebc | ||
|
|
605db9340c | ||
|
|
36ea04bd70 | ||
|
|
6f691e45d8 | ||
|
|
2c6558874d | ||
|
|
d60b432f0c | ||
|
|
adb9849650 | ||
|
|
48abbbf83e | ||
|
|
9e5361338c | ||
|
|
c5ecd71c6e | ||
|
|
b09bbba21b | ||
|
|
2db543573c | ||
|
|
cadd4df5d0 | ||
|
|
427de4b42f | ||
|
|
79cb82ea24 | ||
|
|
1b472c4ad7 | ||
|
|
21ab4950c8 | ||
|
|
1b2977d591 | ||
|
|
c54cf0bca2 | ||
|
|
935c990fe4 | ||
|
|
44f94f8398 | ||
|
|
8380cb1084 | ||
|
|
83fc4f46b2 | ||
|
|
9a97134a37 | ||
|
|
912010ed70 | ||
|
|
09b0bd8b09 | ||
|
|
c0dece8936 | ||
|
|
571b610417 | ||
|
|
658339047c | ||
|
|
13120712b1 | ||
|
|
10e1fad7f3 | ||
|
|
d7629763ef | ||
|
|
94ee0fb058 | ||
|
|
d4e8ce9c89 | ||
|
|
707e241f25 | ||
|
|
58fa94e8b8 | ||
|
|
72d307503e | ||
|
|
d9fd9163e4 | ||
|
|
21807c3a66 | ||
|
|
f499d2d8a9 | ||
|
|
0f9958bb26 | ||
|
|
8e66a9880c | ||
|
|
38e112fb06 | ||
|
|
bb0e48cd41 | ||
|
|
fd0edd93ea | ||
|
|
6359696bde | ||
|
|
1fee392989 | ||
|
|
dfdced9644 | ||
|
|
e50c1c374a | ||
|
|
dbcd051aae | ||
|
|
96571bb999 | ||
|
|
0620f6b6e3 | ||
|
|
ae163167b6 | ||
|
|
ba0bb32b5f | ||
|
|
b15e789b0b | ||
|
|
8c7afa2e7a | ||
|
|
87e30b9edf | ||
|
|
887ebe6f2f | ||
|
|
099581dddc | ||
|
|
3e84890765 | ||
|
|
d2fb0791d9 | ||
|
|
a2f22007cf | ||
|
|
d4dd531ac4 | ||
|
|
f7790c46d2 | ||
|
|
775e31c5e9 | ||
|
|
e1ea210fe9 | ||
|
|
d9022a0383 | ||
|
|
0b0c1405ea | ||
|
|
eb8d1626ea | ||
|
|
db94cd32d5 | ||
|
|
67c7ef37e6 | ||
|
|
2e22f4ea67 | ||
|
|
b985c2c784 | ||
|
|
dc242420f8 | ||
|
|
0656e3f536 | ||
|
|
cfffcda068 | ||
|
|
187885fdb9 | ||
|
|
f67b60dd82 | ||
|
|
92a2421a9b |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -20,7 +20,7 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[L]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
@@ -269,5 +269,9 @@ dist
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
|
||||
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Development.json
|
||||
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json
|
||||
/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/appsettings.Development.json
|
||||
database_backup
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Staging.json
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
|
||||
|
||||
|
||||
213
README-en.md
Normal file
213
README-en.md
Normal file
@@ -0,0 +1,213 @@
|
||||
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi-Framework</h1>
|
||||
<h4 align="center">A .NET 8 Web open-source Asp.NetCore framework focused on user experience.</h4>
|
||||
<h5 align="center">Supports Native/Abp.vNext/Furion/Ruoyi/Pure</h5>
|
||||
<h2 align="center">A comprehensive solution that ultimately becomes a wheel</h2>
|
||||
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
|
||||
English | [简体中文](README.md)
|
||||
****
|
||||
## 🍍 Introduction:
|
||||
YiFramework is a DDD (Domain-Driven Design) backend open-source framework based on .Net8, Abp.vNext, and SqlSugar.
|
||||
|
||||
Who says ABP is complex? Who says DDD is difficult?`Breaking conventions, simplifying complexity.`,Newcomer-friendly and one of the best approaches for project extensions.
|
||||
|
||||
Modular design allows for the independent inclusion or exclusion of components based on business needs. It is an all-encompassing framework where you may gain unique insights.
|
||||
|
||||
A Comprehensive Solution, Ultimately Just Another Wheel.
|
||||
|
||||
(Frequent updates, feel free to watch for continuous updates.)
|
||||
|
||||
— This is not just a program; it is also a work of art, focused on artistic development!
|
||||
|
||||
> Core Features: Simple and easy to use, the framework is not referenced in a packaged form, but is provided directly with the project alongside the source code. It offers maximum freedom and complies with the MIT license, allowing for unrestricted modifications (please indicate the source).
|
||||
|
||||
**Branch Directory:**
|
||||
|
||||
- Branch **Abp**: Based on the Abp.vNext branch, DDD (Domain-Driven Design) simplifies the essence of development, providing support for multiple frontends from one backend.
|
||||
|
||||
- Yi.Abp.Net8:Backend
|
||||
- Yi.Bbs.Vue3:Bbs Community - Frontend
|
||||
- Yi.Doc.Md: Open Source Documentation Tutorial
|
||||
- Yi.Pure.Vue3:Pure TS Backend Frontend
|
||||
- Yi.RuoYi.Vue3:RuoYi JS Backend Frontend
|
||||
|
||||
****
|
||||
|
||||
## 🍊 Official website and demo link:
|
||||
|
||||
Let's get straight to the point and provide the link.
|
||||
|
||||
YiCommunity official website URL.(Bbs):[ccnetcore.com](https://ccnetcore.com) (Now live, welcome to join!)
|
||||
|
||||
Rbac:https://ccnetcore.com:1000 (userName cc\password 123456)
|
||||
|
||||
Pure:https://ccnetcore.com:1001 (userNamecc\password 123456)
|
||||
|
||||
## 🍏 Support:
|
||||
|
||||
- [x] Fully supports monolithic application architecture
|
||||
- [x] Fully supports distributed application architecture
|
||||
- [x] Fully supports microservices architecture
|
||||
|
||||
****
|
||||
## 🍇 Explosive Detail Yi Framework Tutorial Navigation:
|
||||
|
||||
1. [Framework Quick Start Guide](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(Completed)
|
||||
2. [Framework Functionality Module Tutorials](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(Completed)
|
||||
3. [Practical Development Exercises](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(Completed)
|
||||
4. [Chengzi Ops CI/CD Tutorial](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(Completed)
|
||||
5. [Version Update Log](https://ccnetcore.com/article/e9e69a38-ce1e-06f5-7944-3a0fdc942ef3)(Completed)
|
||||
|
||||
****
|
||||
## 🍓 Its philosophy:
|
||||
Who says ABP is complicated? Who says DDD is difficult? Break the norm, simplify complexity, and serve as one of the best ways for newcomers and project second development.
|
||||
|
||||
> For every hundred people, there are a hundred different interpretations of DDD. The YiFramework may not strict adherence to DDD principles, but it is built on the shoulders of giants, distilled from numerous projects to craft a best practice.
|
||||
|
||||
Effortlessly achieve rapid development; typically, simplicity and elegance are hard to reconcile. The YiFramework does not solely pursue extreme decoupling but considers user experience and ease of use.
|
||||
|
||||
A user-oriented rapid development backend framework.
|
||||
|
||||
> Once you truly get hands-on, you'll understand this: extreme simplicity is also a form of elegance.
|
||||
****
|
||||
|
||||
## 🍍 Features
|
||||
- A user-oriented backend framework that is easy to use, suitable for small, medium, and enterprise-level projects.
|
||||
- The project comes with the source code directly embedded, without packaging, making it ideal for secondary development and modification.
|
||||
- Includes a large number of reusable modules for common scenarios.
|
||||
- Elegantly supports distributed and microservices architectures.
|
||||
- And more…
|
||||
|
||||
## 🥭 Core Technologies
|
||||
#### Backend
|
||||
C# Asp.NetCore 8.0
|
||||
- [x] Dynamic API: Abp.vNext
|
||||
- [x] Authentication and Authorization: Jwt
|
||||
- [x] Logging: Serilog
|
||||
- [x] Modularization: Abp.vNext
|
||||
- [x] Dependency Injection: Autofac
|
||||
- [x] Object Mapping: Mapster
|
||||
- [x] ORM: SqlsugarCore
|
||||
- [x] Multi-tenancy: Abp.vNext
|
||||
- [x] Background Tasks: Quartz.Net
|
||||
- [x] Local Caching: Abp.vNext
|
||||
- [x] Distributed Caching: Abp.vNext
|
||||
- [x] Event Bus: Abp.vNext
|
||||
|
||||
#### Frontend
|
||||
js Vue3
|
||||
- [x] Asynchronous Requests: axios
|
||||
- [x] Charts: echarts
|
||||
- [x] UI: element-plus
|
||||
- [x] State Management: pinia
|
||||
- [x] Routing: vue-router
|
||||
- [x] Bundling: vite
|
||||
|
||||
#### DevOps
|
||||
- [x] Deployment: nginx
|
||||
- [x] CICD: gitlab+Jenkins
|
||||
- [x] Docker: harbor
|
||||
|
||||
#### 🍉 Demo:
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/101.png"/></td>
|
||||
<td><img src="readme/102.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/103.png"/></td>
|
||||
<td><img src="readme/104.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/201.png"/></td>
|
||||
<td><img src="readme/202.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/203.png"/></td>
|
||||
<td><img src="readme/204.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/205.png"/></td>
|
||||
<td><img src="readme/206.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/1.png"/></td>
|
||||
<td><img src="readme/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/3.png"/></td>
|
||||
<td><img src="readme/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/3.png"/></td>
|
||||
<td><img src="readme/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/5.png"/></td>
|
||||
<td><img src="readme/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/7.png"/></td>
|
||||
<td><img src="readme/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/9.png"/></td>
|
||||
<td><img src="readme/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/11.png"/></td>
|
||||
<td><img src="readme/12.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 🌶 Thank you:
|
||||
|
||||
[橙子]https://ccnetcore.com
|
||||
|
||||
[XWen]https://gitee.com/on-wensil
|
||||
|
||||
[朝夕教育]https://www.zhaoxiedu.net
|
||||
|
||||
[Sqlsugar老杰哥]https://www.donet5.com/Home/Doc
|
||||
|
||||
[车神]微信公众号搜索Dotnet技术进阶
|
||||
|
||||
[RuYiAdmin如意老兄]https://gitee.com/pang-mingjun/RuYiAdmin
|
||||
|
||||
[ZrAdminNetCore字母老哥]https://gitee.com/izory/ZrAdminNetCore
|
||||
|
||||
[Admin.NET]https://gitee.com/zuohuaijun/Admin.NET
|
||||
|
||||
[Furion百小僧]https://furion.baiqian.ltd/
|
||||
|
||||
****
|
||||
## 🌽 Contact Us:
|
||||
|
||||
Author's QQ:`454313500`
|
||||
|
||||
QQ group chat:官方一群(Full)、官方二群(Full)、官方三群:`786308927`(Full)、官方四群:`498310311`(Full)、官方五群:`981136525`(New)
|
||||
|
||||
WeChat Group Chat:官方微信一群(Full)、官方微信二群
|
||||
|
||||
WeChat Community: Add the author's WeChat chengzilaoge520 (橙子老哥520),Note: Join the group.
|
||||
|
||||
Contact the author, everyone here is a consultant.
|
||||
|
||||
Official website message area:[ccnetcore.com](https://ccnetcore.com)
|
||||
|
||||
****
|
||||
## 🍄 FQA:
|
||||
|
||||
Visit the official website to view the message board.
|
||||
|
||||
[the message board](https://ccnetcore.com/discuss/1641030787056930818)
|
||||
79
README.md
79
README.md
@@ -1,6 +1,6 @@
|
||||
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi框架</h1>
|
||||
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
|
||||
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端后台接入Ruoyi Vue3.0</h5>
|
||||
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端接入Ruoyi/Pure Vue</h5>
|
||||
<h2 align="center">集大成者,终究轮子</h2>
|
||||
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
[English](README-en.md) | 简体中文
|
||||
****
|
||||
## :tw-1f34e: 简介:
|
||||
## 🍍 简介:
|
||||
YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端开源框架
|
||||
|
||||
谁说Abp复杂?谁说DDD难?`打破常规,化繁为简`,新人入门,项目二开,最佳方式之一
|
||||
@@ -31,44 +31,45 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
|
||||
|
||||
> 核心特点:简单好用,框架不以打包形式引用,而是直接以项目附带源码给出,自由度拉满,遵循Mit协议,允许随意修改(请注明来源即可)
|
||||
|
||||
**分支:**
|
||||
**分支目录:**
|
||||
|
||||
- (推荐) **Abp**: 基于Abp.vNext分支,DDD领域驱动设计,回归开发本质,极度简单,用起来贼爽
|
||||
- 分支**Abp**: 基于Abp.vNext分支,DDD领域驱动设计,回归开发本质,极度简单,一个后台支持以下多个前端
|
||||
|
||||
- **Furion**: 基于Furion分支
|
||||
- Yi.Abp.Net8:后端
|
||||
- Yi.Bbs.Vue3:Bbs社区 前端
|
||||
- Yi.Doc.Md: 开源文档教程
|
||||
- Yi.Pure.Vue3:Pure ts后台前端
|
||||
- Yi.RuoYi.Vue3:RuoYi js后台前端
|
||||
|
||||
****
|
||||
|
||||
## :tw-1f350: 官网及演示地址:
|
||||
## 🍊 官网及演示地址:
|
||||
|
||||
废话少说直接上地址
|
||||
|
||||
Yi社区官网网址:[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
|
||||
Yi社区官网网址(Bbs社区正式):[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
|
||||
|
||||
Rbac后台管理系统:已上线,暂不提供演示地址,可本地部署访问
|
||||
Rbac后台演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
App移动端系统:已上线,暂不提供演示地址,可本地部署访问
|
||||
Pure后台演示地址:https://ccnetcore.com:1001 (用户cc、密码123456)
|
||||
|
||||
Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
|
||||
## :tw-1f351: 支持:
|
||||
## 🍏 支持:
|
||||
|
||||
- [x] 完全支持单体应用架构
|
||||
- [x] 完全支持分布式应用架构
|
||||
- [x] 完全支持微服务架构
|
||||
|
||||
****
|
||||
## :tw-1f352: 详细到爆炸的Yi框架教程导航:
|
||||
## 🍇 详细到爆炸的Yi框架教程导航:
|
||||
|
||||
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)
|
||||
2. [框架功能模块教程](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(已完成)
|
||||
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)
|
||||
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(已完成)
|
||||
4. [橙子运维CICD教程](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(已完成)
|
||||
5. [版本更新日志](https://ccnetcore.com/article/e9e69a38-ce1e-06f5-7944-3a0fdc942ef3)(已完成)
|
||||
|
||||
****
|
||||
## :tw-1f353: 它的理念:
|
||||
## 🍓 它的理念:
|
||||
谁说Abp复杂?谁说DDD难?打破常规,化繁为简,新人入门,项目二开,最佳方式之一
|
||||
|
||||
> 一百个人,就有一百种DDD,Yi框架不一定是极度严格的DDD,而是站在巨人的肩膀上,经过极多项目的提炼,摸索出一种最佳实践
|
||||
@@ -78,17 +79,17 @@ Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
> 一个面向用户的快速开发后端框架
|
||||
|
||||
在真正的使用这,你会明白这一点,极致的简单,也是优雅的一种体现。
|
||||
在真正的使用过,你会明白这一点,极致的简单,也是优雅的一种体现。
|
||||
****
|
||||
|
||||
## :tw-1f354: 特点
|
||||
## 🍍 特点
|
||||
- 面向用户的后端框架,使用简单,适合小型、中型、企业级项目
|
||||
- 项目直接内置源码,不打包,非常适合进行二开改造
|
||||
- 内置包含大量通用场景模块
|
||||
- 优雅支持分布式及微服务架构
|
||||
- 等等
|
||||
|
||||
## :tw-1f340: 基础设施简介
|
||||
## 🍍 基础设施简介
|
||||
|
||||
以下全部功能可直接使用:
|
||||
|
||||
@@ -96,14 +97,14 @@ Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
- [SqlSugar官网](https://www.donet5.com/home/doc)
|
||||
|
||||
## :tw-1f341: 内置模块简介
|
||||
- Rbac权限管理系统(已上线)
|
||||
## 🍅 内置模块简介
|
||||
- Rbac权限管理系统(已上线)(支持pure、ruoyi前端)
|
||||
- Bbs论坛社区系统(已上线)
|
||||
|
||||
> 重复的东西,无需再写一遍,这也是优雅的体现之一
|
||||
|
||||
****
|
||||
## :tw-1f31e: 核心技术
|
||||
## 🥭 核心技术
|
||||
#### 后端
|
||||
C# Asp.NetCore 8.0
|
||||
- [x] 动态Api:Abp.vNext
|
||||
@@ -120,7 +121,7 @@ C# Asp.NetCore 8.0
|
||||
- [x] 事件总线:Abp.vNext
|
||||
|
||||
#### 前端
|
||||
js Vue3.2
|
||||
js Vue3
|
||||
- [x] 异步请求:axios
|
||||
- [x] 图表:echarts
|
||||
- [x] ui:element-plus
|
||||
@@ -135,9 +136,9 @@ js Vue3.2
|
||||
|
||||
|
||||
****
|
||||
## :tw-1f366: 业务支持模块:
|
||||
## 🍌 业务支持模块:
|
||||
|
||||
#### :tw-1f42f: RABC权限管理系统(持续更新)
|
||||
#### 🍒 RABC权限管理系统(持续更新)
|
||||
(采用ruoyi前端)
|
||||
- 用户管理
|
||||
- 角色管理
|
||||
@@ -152,9 +153,8 @@ js Vue3.2
|
||||
- 定时任务
|
||||
- 缓存列表
|
||||
- 服务监控
|
||||
- WebFirst代码生成工具
|
||||
|
||||
#### :tw-1f431: BBS社区论坛系统(持续更新)
|
||||
#### 🍐 BBS社区论坛系统(持续更新)
|
||||
(采用vue3前端)
|
||||
- 文章功能
|
||||
- 板块功能
|
||||
@@ -163,7 +163,7 @@ js Vue3.2
|
||||
- 授权中心
|
||||
- 权限管理
|
||||
|
||||
#### :star: 演示截图:
|
||||
#### 🍉 演示截图:
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/101.png"/></td>
|
||||
@@ -175,6 +175,21 @@ js Vue3.2
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/201.png"/></td>
|
||||
<td><img src="readme/202.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/203.png"/></td>
|
||||
<td><img src="readme/204.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/205.png"/></td>
|
||||
<td><img src="readme/206.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -207,7 +222,7 @@ js Vue3.2
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## :tw-1f44f: 感谢:
|
||||
## 🌶 感谢:
|
||||
|
||||
[橙子]https://ccnetcore.com
|
||||
|
||||
@@ -228,12 +243,14 @@ js Vue3.2
|
||||
[Furion百小僧]https://furion.baiqian.ltd/
|
||||
|
||||
****
|
||||
## :tw-1f438: 联系我们:
|
||||
## 🌽 联系我们:
|
||||
|
||||
作者QQ:`454313500`,2029年之前作者24小时在线,时刻保持活跃更新。
|
||||
|
||||
QQ交流群:官方一群(已满)、官方二群(已满)、官方三群:`786308927`(已满)、官方四群:`498310311`(基本已满)、官方五群:`981136525`(新群)
|
||||
|
||||
微信交流群:官方微信一群(已满)、官方微信二群
|
||||
|
||||
微信交流群:加作者微信 chengzilaoge520 (橙子老哥520),备注拉群
|
||||
|
||||
联系作者,这里人人都是顾问
|
||||
@@ -241,7 +258,7 @@ QQ交流群:官方一群(已满)、官方二群(已满)、官方三群
|
||||
官方网址留言区:[ccnetcore.com](https://ccnetcore.com)
|
||||
|
||||
****
|
||||
## :tw-1f41e: FQA:
|
||||
## 🍄 FQA:
|
||||
|
||||
前往官网查看留言区
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
usings.props = usings.props
|
||||
version.props = version.props
|
||||
publish.bat = publish.bat
|
||||
publish_Demo.bat = publish_Demo.bat
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SqlSugarCore.Abstractions", "framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj", "{FD6D6860-3753-4747-8A26-977E4A3001F9}"
|
||||
@@ -79,20 +80,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.S
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AspNetCore.Authentication.OAuth", "framework\Yi.Framework.AspNetCore.Authentication.OAuth\Yi.Framework.AspNetCore.Authentication.OAuth.csproj", "{791AC2FA-50D3-4408-8D68-31DA72F608BE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{01300F0F-686E-47B3-821D-12424177867B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Web", "sample\Acme.BookStore.Web\Acme.BookStore.Web.csproj", "{576DBC97-4E5D-4444-B65C-F41649A5F8E0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain.Shared", "sample\Acme.BookStore.Domain.Shared\Acme.BookStore.Domain.Shared.csproj", "{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain", "sample\Acme.BookStore.Domain\Acme.BookStore.Domain.csproj", "{B615847F-8568-41D1-8B7E-63D61AE69F3D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application.Contracts", "sample\Acme.BookStore.Application.Contracts\Acme.BookStore.Application.Contracts.csproj", "{20827DB5-5CDE-491A-82E8-3CAB82618C1E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application", "sample\Acme.BookStore.Application\Acme.BookStore.Application.csproj", "{320273B6-7AE3-42DA-9675-D9AD4928A289}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}"
|
||||
@@ -169,6 +156,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Tool.HttpApi.Client"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.SettingManagement.Application", "module\setting-management\Yi.Framework.SettingManagement.Application\Yi.Framework.SettingManagement.Application.csproj", "{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.BackgroundWorkers.Hangfire", "framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj", "{862CA181-BEE6-4870-82D2-B662E527ED8C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -275,30 +264,6 @@ Global
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -427,6 +392,10 @@ Global
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -460,12 +429,6 @@ Global
|
||||
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
|
||||
{499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
||||
@@ -502,6 +465,7 @@ Global
|
||||
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||
{C8F97775-D903-4365-A4FF-3DA97E318CD2} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.AspNetCore.WebClientInfo;
|
||||
|
||||
namespace Yi.Framework.AspNetCore;
|
||||
|
||||
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
|
||||
{
|
||||
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
|
||||
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string? GetClientIpAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpContext = HttpContextAccessor.HttpContext;
|
||||
|
||||
var headers = httpContext?.Request?.Headers;
|
||||
|
||||
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
|
||||
{
|
||||
httpContext.Connection.RemoteIpAddress =
|
||||
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
|
||||
}
|
||||
|
||||
return httpContext?.Connection?.RemoteIpAddress?.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogException(ex, LogLevel.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 异常元数据
|
||||
/// </summary>
|
||||
public sealed class ExceptionMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int StatusCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public object ErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(没被复写过的 ErrorCode )
|
||||
/// </summary>
|
||||
public object OriginErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误对象(信息)
|
||||
/// </summary>
|
||||
public object Errors { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外数据
|
||||
/// </summary>
|
||||
public object Data { get; internal set; }
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
/// <summary>
|
||||
/// 友好异常拦截器
|
||||
/// </summary>
|
||||
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常拦截
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnExceptionAsync(ExceptionContext context)
|
||||
{
|
||||
|
||||
// 排除 WebSocket 请求处理
|
||||
if (context.HttpContext.IsWebSocketRequest()) return;
|
||||
|
||||
// 如果异常在其他地方被标记了处理,那么这里不再处理
|
||||
if (context.ExceptionHandled) return;
|
||||
|
||||
// 解析异常信息
|
||||
var exceptionMetadata = GetExceptionMetadata(context);
|
||||
|
||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||
// 执行规范化异常处理
|
||||
context.Result = unifyResult.OnException(context, exceptionMetadata);
|
||||
|
||||
// 创建日志记录器
|
||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
|
||||
|
||||
// 记录拦截日常
|
||||
logger.LogError(context.Exception, context.Exception.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取异常元数据
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static ExceptionMetadata GetExceptionMetadata(ActionContext context)
|
||||
{
|
||||
object errorCode = default;
|
||||
object originErrorCode = default;
|
||||
object errors = default;
|
||||
object data = default;
|
||||
var statusCode = StatusCodes.Status500InternalServerError;
|
||||
var isValidationException = false; // 判断是否是验证异常
|
||||
var isFriendlyException = false;
|
||||
|
||||
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
|
||||
var exception = context is ExceptionContext exContext
|
||||
? exContext.Exception
|
||||
: (
|
||||
context is ActionExecutedContext edContext
|
||||
? edContext.Exception
|
||||
: default
|
||||
);
|
||||
|
||||
// 判断是否是友好异常
|
||||
if (exception is UserFriendlyException friendlyException)
|
||||
{
|
||||
int statusCode2 = 500;
|
||||
int.TryParse(friendlyException.Code, out statusCode2);
|
||||
isFriendlyException = true;
|
||||
errorCode = friendlyException.Code;
|
||||
originErrorCode = friendlyException.Code;
|
||||
statusCode = statusCode2==0?403:statusCode2;
|
||||
isValidationException = false;
|
||||
errors = friendlyException.Message;
|
||||
data = friendlyException.Data;
|
||||
}
|
||||
|
||||
return new ExceptionMetadata
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
ErrorCode = errorCode,
|
||||
OriginErrorCode = originErrorCode,
|
||||
Errors = errors,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化结构(请求成功)过滤器
|
||||
/// </summary>
|
||||
public class SucceededUnifyResultFilter : IAsyncActionFilter, IOrderedFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 过滤器排序
|
||||
/// </summary>
|
||||
private const int FilterOrder = 8888;
|
||||
|
||||
/// <summary>
|
||||
/// 排序属性
|
||||
/// </summary>
|
||||
public int Order => FilterOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 处理规范化结果
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
// 执行 Action 并获取结果
|
||||
var actionExecutedContext = await next();
|
||||
|
||||
// 排除 WebSocket 请求处理
|
||||
if (actionExecutedContext.HttpContext.IsWebSocketRequest()) return;
|
||||
|
||||
// 处理已经含有状态码结果的 Result
|
||||
if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult &&
|
||||
statusCodeResult.StatusCode != null)
|
||||
{
|
||||
// 小于 200 或者 大于 299 都不是成功值,直接跳过
|
||||
if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299)
|
||||
{
|
||||
// 处理规范化结果
|
||||
if (!CheckStatusCodeNonUnify(context.HttpContext, out var unifyRes))
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
var statusCode = statusCodeResult.StatusCode.Value;
|
||||
|
||||
// 解决刷新 Token 时间和 Token 时间相近问题
|
||||
if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized
|
||||
&& httpContext.Response.Headers.ContainsKey("access-token")
|
||||
&& httpContext.Response.Headers.ContainsKey("x-access-token"))
|
||||
{
|
||||
httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden;
|
||||
}
|
||||
|
||||
// 如果 Response 已经完成输出,则禁止写入
|
||||
if (httpContext.Response.HasStarted) return;
|
||||
await unifyRes.OnResponseStatusCodes(httpContext, statusCode,
|
||||
httpContext.RequestServices.GetService<IOptions<UnifyResultSettingsOptions>>()?.Value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果出现异常,则不会进入该过滤器
|
||||
if (actionExecutedContext.Exception != null) return;
|
||||
|
||||
// 获取控制器信息
|
||||
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
||||
|
||||
// 判断是否支持 MVC 规范化处理,检测配置而已
|
||||
// if (!UnifyContext.CheckSupportMvcController(context.HttpContext, actionDescriptor, out _)) return;
|
||||
|
||||
// 判断是否跳过规范化处理,检测NonUnifyAttribute而已
|
||||
if (CheckSucceededNonUnify(actionDescriptor.MethodInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||
|
||||
// 处理 BadRequestObjectResult 类型规范化处理
|
||||
if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult)
|
||||
{
|
||||
// 解析验证消息
|
||||
var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value);
|
||||
|
||||
var result = unifyResult.OnValidateFailed(context, validationMetadata);
|
||||
if (result != null) actionExecutedContext.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
IActionResult result = default;
|
||||
|
||||
// 检查是否是有效的结果(可进行规范化的结果)
|
||||
if (CheckVaildResult(actionExecutedContext.Result, out var data))
|
||||
{
|
||||
result = unifyResult.OnSucceeded(actionExecutedContext, data);
|
||||
}
|
||||
|
||||
// 如果是不能规范化的结果类型,则跳过
|
||||
if (result == null) return;
|
||||
|
||||
actionExecutedContext.Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取验证错误信息
|
||||
/// </summary>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
private static ValidationMetadata GetValidationMetadata(object errors)
|
||||
{
|
||||
ModelStateDictionary _modelState = null;
|
||||
object validationResults = null;
|
||||
(string message, string firstErrorMessage, string firstErrorProperty) = (default, default, default);
|
||||
|
||||
// 判断是否是集合类型
|
||||
if (errors is IEnumerable && errors is not string)
|
||||
{
|
||||
// 如果是模型验证字典类型
|
||||
if (errors is ModelStateDictionary modelState)
|
||||
{
|
||||
_modelState = modelState;
|
||||
// 将验证错误信息转换成字典并序列化成 Json
|
||||
validationResults = modelState.Where(u => modelState[u.Key].ValidationState == ModelValidationState.Invalid)
|
||||
.ToDictionary(u => u.Key, u => modelState[u.Key].Errors.Select(c => c.ErrorMessage).ToArray());
|
||||
}
|
||||
// 如果是 ValidationProblemDetails 特殊类型
|
||||
else if (errors is ValidationProblemDetails validation)
|
||||
{
|
||||
validationResults = validation.Errors
|
||||
.ToDictionary(u => u.Key, u => u.Value.ToArray());
|
||||
}
|
||||
// 如果是字典类型
|
||||
else if (errors is Dictionary<string, string[]> dicResults)
|
||||
{
|
||||
validationResults = dicResults;
|
||||
}
|
||||
|
||||
message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true
|
||||
});
|
||||
firstErrorMessage = (validationResults as Dictionary<string, string[]>).First().Value[0];
|
||||
firstErrorProperty = (validationResults as Dictionary<string, string[]>).First().Key;
|
||||
}
|
||||
// 其他类型
|
||||
else
|
||||
{
|
||||
validationResults = firstErrorMessage = message = errors?.ToString();
|
||||
}
|
||||
|
||||
return new ValidationMetadata
|
||||
{
|
||||
ValidationResult = validationResults,
|
||||
Message = message,
|
||||
ModelState = _modelState,
|
||||
FirstErrorProperty = firstErrorProperty,
|
||||
FirstErrorMessage = firstErrorMessage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否是有效的结果(可进行规范化的结果)
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private bool CheckVaildResult(IActionResult result, out object data)
|
||||
{
|
||||
data = default;
|
||||
|
||||
// 排除以下结果,跳过规范化处理
|
||||
var isDataResult = result switch
|
||||
{
|
||||
ViewResult => false,
|
||||
PartialViewResult => false,
|
||||
FileResult => false,
|
||||
ChallengeResult => false,
|
||||
SignInResult => false,
|
||||
SignOutResult => false,
|
||||
RedirectToPageResult => false,
|
||||
RedirectToRouteResult => false,
|
||||
RedirectResult => false,
|
||||
RedirectToActionResult => false,
|
||||
LocalRedirectResult => false,
|
||||
ForbidResult => false,
|
||||
ViewComponentResult => false,
|
||||
PageResult => false,
|
||||
NotFoundResult => false,
|
||||
NotFoundObjectResult => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// 目前支持返回值 ActionResult
|
||||
if (isDataResult) data = result switch
|
||||
{
|
||||
// 处理内容结果
|
||||
ContentResult content => content.Content,
|
||||
// 处理对象结果
|
||||
ObjectResult obj => obj.Value,
|
||||
// 处理 JSON 对象
|
||||
JsonResult json => json.Value,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return isDataResult;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 检查短路状态码(>=400)是否进行规范化处理
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="unifyResult"></param>
|
||||
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
|
||||
internal static bool CheckStatusCodeNonUnify(HttpContext context, out IUnifyResultProvider unifyResult)
|
||||
{
|
||||
// 获取终点路由特性
|
||||
var endpointFeature = context.Features.Get<IEndpointFeature>();
|
||||
if (endpointFeature == null) return (unifyResult = null) == null;
|
||||
|
||||
// 判断是否跳过规范化处理
|
||||
var isSkip = context.GetEndpoint()?.Metadata?.GetMetadata<NonUnifyAttribute>()!= null
|
||||
|| endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null
|
||||
|| context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase)
|
||||
|| context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isSkip == true) unifyResult = null;
|
||||
else
|
||||
{
|
||||
unifyResult = context.RequestServices.GetRequiredService<IUnifyResultProvider>();
|
||||
}
|
||||
|
||||
return unifyResult == null || isSkip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查请求成功是否进行规范化处理
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="isWebRequest"></param>
|
||||
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
|
||||
private bool CheckSucceededNonUnify(MethodInfo method, bool isWebRequest = true)
|
||||
{
|
||||
// 判断是否跳过规范化处理
|
||||
var isSkip = method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|
||||
|| method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData");
|
||||
|
||||
if (!isWebRequest)
|
||||
{
|
||||
return isSkip;
|
||||
}
|
||||
return isSkip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化结果提供器
|
||||
/// </summary>
|
||||
public interface IUnifyResultProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnSucceeded(ActionExecutedContext context, object data);
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 拦截返回状态码
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = default);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 禁止规范化处理
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
public sealed class NonUnifyAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// RESTful 风格返回值
|
||||
/// </summary>
|
||||
[Dependency(TryRegister = true)]
|
||||
[ExposeServices(typeof(IUnifyResultProvider))]
|
||||
public class RESTfulResultProvider : IUnifyResultProvider,ITransientDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置响应状态码
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
public static void SetResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
if (unifyResultSettings == null) return;
|
||||
|
||||
// 篡改响应状态码
|
||||
if (unifyResultSettings.AdaptStatusCodes != null && unifyResultSettings.AdaptStatusCodes.Length > 0)
|
||||
{
|
||||
var adaptStatusCode = unifyResultSettings.AdaptStatusCodes.FirstOrDefault(u => u[0] == statusCode);
|
||||
if (adaptStatusCode != null && adaptStatusCode.Length > 0 && adaptStatusCode[0] > 0)
|
||||
{
|
||||
context.Response.StatusCode = adaptStatusCode[1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果为 null,则所有请求错误的状态码设置为 200
|
||||
if (unifyResultSettings.Return200StatusCodes == null) context.Response.StatusCode = 200;
|
||||
// 否则只有里面的才设置为 200
|
||||
else if (unifyResultSettings.Return200StatusCodes.Contains(statusCode)) context.Response.StatusCode = 200;
|
||||
else { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnSucceeded(ActionExecutedContext context, object data)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败/业务异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 特定状态码返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
// 设置响应状态码
|
||||
SetResponseStatusCodes(context, statusCode, unifyResultSettings);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
// 处理 401 状态码
|
||||
case StatusCodes.Status401Unauthorized:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 Unauthorized"));
|
||||
break;
|
||||
// 处理 403 状态码
|
||||
case StatusCodes.Status403Forbidden:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 Forbidden"));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回 RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="succeeded"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
public static RESTfulResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)
|
||||
{
|
||||
return new RESTfulResult<object>
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Succeeded = succeeded,
|
||||
Data = data,
|
||||
Errors = errors,
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class RESTfulResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int? StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行成功
|
||||
/// </summary>
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public object Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public object Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳
|
||||
/// </summary>
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
|
||||
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化接口
|
||||
/// 由于太多人反应,想兼容一套类似furion的返回情况,200状态码包一层更符合国内习惯,既然如此,不如直接搬过来
|
||||
/// </summary>
|
||||
public static class UnifyResultExtensions
|
||||
{
|
||||
public static IServiceCollection AddFurionUnifyResultApi(this IServiceCollection services)
|
||||
{
|
||||
//成功规范接口
|
||||
services.AddTransient<SucceededUnifyResultFilter>();
|
||||
//异常规范接口
|
||||
services.AddTransient<FriendlyExceptionFilter>();
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Filters.AddService<SucceededUnifyResultFilter>(99);
|
||||
options.Filters.AddService<FriendlyExceptionFilter>(100);
|
||||
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化配置选项
|
||||
/// </summary>
|
||||
public sealed class UnifyResultSettingsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置返回 200 状态码列表
|
||||
/// <para>默认:401,403,如果设置为 null,则标识所有状态码都返回 200 </para>
|
||||
/// </summary>
|
||||
public int[] Return200StatusCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适配(篡改)Http 状态码(只支持短路状态码,比如 401,403,500 等)
|
||||
/// </summary>
|
||||
public int[][] AdaptStatusCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持 MVC 控制台规范化处理
|
||||
/// </summary>
|
||||
public bool? SupportMvcController { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项后期配置
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public void PostConfigure(UnifyResultSettingsOptions options, IConfiguration configuration)
|
||||
{
|
||||
options.Return200StatusCodes ??= new[] { 401, 403 };
|
||||
options.SupportMvcController ??= false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 验证信息元数据
|
||||
/// </summary>
|
||||
public sealed class ValidationMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证结果
|
||||
/// </summary>
|
||||
/// <remarks>返回字典或字符串类型</remarks>
|
||||
public object ValidationResult { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常消息
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态
|
||||
/// </summary>
|
||||
public ModelStateDictionary ModelState { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public object ErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(没被复写过的 ErrorCode )
|
||||
/// </summary>
|
||||
public object OriginErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int? StatusCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首个错误属性
|
||||
/// </summary>
|
||||
public string FirstErrorProperty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首个错误消息
|
||||
/// </summary>
|
||||
public string FirstErrorMessage { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外数据
|
||||
/// </summary>
|
||||
public object Data { get; internal set; }
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.AspNetCore.WebClientInfo;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
using Yi.Framework.AspNetCore.Mvc;
|
||||
@@ -22,6 +23,11 @@ namespace Yi.Framework.AspNetCore
|
||||
)]
|
||||
public class YiFrameworkAspNetCoreModule : AbpModule
|
||||
{
|
||||
|
||||
public override void PostConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var services = context.Services;
|
||||
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
|
||||
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Hangfire.Server;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Threading;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
|
||||
{
|
||||
private const string CurrentJobUow = "HangfireUnitOfWork";
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
public void OnPerforming(PerformingContext context)
|
||||
{
|
||||
var uow = _unitOfWorkManager.Begin();
|
||||
context.Items.Add(CurrentJobUow, uow);
|
||||
}
|
||||
|
||||
public void OnPerformed(PerformedContext context)
|
||||
{
|
||||
AsyncHelper.RunSync(()=>OnPerformedAsync(context));
|
||||
}
|
||||
|
||||
private async Task OnPerformedAsync(PerformedContext context)
|
||||
{
|
||||
if (context.Items.TryGetValue(CurrentJobUow, out var obj)
|
||||
&& obj is IUnitOfWork uow)
|
||||
{
|
||||
if (context.Exception == null && !uow.IsCompleted)
|
||||
{
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await uow.RollbackAsync();
|
||||
}
|
||||
uow.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.BackgroundWorkers;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
|
||||
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
|
||||
{
|
||||
public override void PreConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
|
||||
}
|
||||
|
||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||
{
|
||||
//定时任务自动注入,Abp默认只有在Quartz才实现
|
||||
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
|
||||
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
|
||||
|
||||
foreach (var work in works)
|
||||
{
|
||||
//如果为空,默认使用服务器本地utc时间
|
||||
work.TimeZone ??= TimeZoneInfo.Local;
|
||||
await backgroundWorkerManager.AddAsync(work);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
var services = context.ServiceProvider;
|
||||
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
|
||||
{
|
||||
protected override bool IsConventionalRegistrationDisabled(Type type)
|
||||
{
|
||||
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type);
|
||||
}
|
||||
|
||||
protected override List<Type> GetExposedServiceTypes(Type type)
|
||||
{
|
||||
return new List<Type>()
|
||||
{
|
||||
typeof(IHangfireBackgroundWorker)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using Hangfire.Dashboard;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Users;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
|
||||
{
|
||||
private const string Bearer = "Bearer: ";
|
||||
private string RequireUser { get; set; } = "cc";
|
||||
private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10);
|
||||
private IServiceProvider _serviceProvider;
|
||||
|
||||
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public YiTokenAuthorizationFilter SetRequireUser(string userName)
|
||||
{
|
||||
RequireUser = userName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime)
|
||||
{
|
||||
ExpiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool Authorize(DashboardContext context)
|
||||
{
|
||||
var httpContext = context.GetHttpContext();
|
||||
var _currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
|
||||
//如果验证通过,设置cookies
|
||||
if (_currentUser.IsAuthenticated)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟
|
||||
};
|
||||
|
||||
|
||||
var authorization = httpContext.Request.Headers["Authorization"].ToString();
|
||||
if (!string.IsNullOrWhiteSpace(authorization))
|
||||
{
|
||||
var token = httpContext.Request.Headers["Authorization"].ToString().Substring(Bearer.Length - 1);
|
||||
httpContext.Response.Cookies.Append("Token", token, cookieOptions);
|
||||
}
|
||||
|
||||
if (_currentUser.UserName == RequireUser)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
SetChallengeResponse(httpContext);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetChallengeResponse(HttpContext httpContext)
|
||||
{
|
||||
httpContext.Response.StatusCode = 401;
|
||||
httpContext.Response.ContentType = "text/html; charset=utf-8";
|
||||
string html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Token 输入</title>
|
||||
<script>
|
||||
function sendToken() {
|
||||
// 获取输入的 token
|
||||
var token = document.getElementById("tokenInput").value;
|
||||
// 构建请求 URL
|
||||
var url = "/hangfire";
|
||||
// 发送 GET 请求
|
||||
fetch(url,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // 设置内容类型为 JSON
|
||||
'Authorization': 'Bearer '+encodeURIComponent(token), // 设置授权头,例如使用 Bearer token
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.text(); // 或使用 response.json() 如果返回的是 JSON
|
||||
}
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(data => {
|
||||
// 处理成功返回的数据
|
||||
document.open();
|
||||
document.write(data);
|
||||
document.close();
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
alert("请求失败: " + error.message);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="text-align: center;">
|
||||
<h1>Yi-hangfire</h1>
|
||||
<h1>输入您的Token,我们将验证您是否为管理员</h1>
|
||||
<input type="text" id="tokenInput" placeholder="请输入 token" />
|
||||
<button onclick="sendToken()">校验</button>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
httpContext.Response.WriteAsync(html);
|
||||
}
|
||||
|
||||
public Task<bool> AuthorizeAsync(DashboardContext context)
|
||||
{
|
||||
return Task.FromResult(Authorize(context));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
@@ -74,9 +74,12 @@ namespace Yi.Framework.Core.Extensions
|
||||
result = "127.0.0.1";
|
||||
|
||||
result = result.Replace("::ffff:", "127.0.0.1");
|
||||
|
||||
//如果有端口号,删除端口号
|
||||
result = Regex.Replace(result, @":\d{1,5}$", "");
|
||||
//Ip规则校验
|
||||
var regResult = Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
|
||||
var regResult =
|
||||
Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$")
|
||||
|| Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):\d{1,5}$");
|
||||
|
||||
result = regResult ? result : "127.0.0.1";
|
||||
return result;
|
||||
@@ -96,5 +99,15 @@ namespace Yi.Framework.Core.Extensions
|
||||
{
|
||||
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是 WebSocket 请求
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsWebSocketRequest(this HttpContext context)
|
||||
{
|
||||
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +78,25 @@ namespace Yi.Framework.Core.Helper
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CPU使用情况
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static CPUMetrics GetCPUMetrics()
|
||||
{
|
||||
CPUMetrics cpuMetrics = new CPUMetrics();
|
||||
var cpudetail = GetCPUDetails();
|
||||
cpuMetrics.CoreTotal = cpudetail.Cores;
|
||||
cpuMetrics.LogicalProcessors =cpudetail.LogicalProcessors;
|
||||
cpuMetrics.CPURate = Math.Ceiling(ParseToDouble(GetCPURate()));
|
||||
cpuMetrics.FreeRate = 1 - cpuMetrics.CPURate;
|
||||
return cpuMetrics;
|
||||
}
|
||||
/// <summary>
|
||||
/// 内存使用情况
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MemoryMetrics GetComputerInfo()
|
||||
public static MemoryMetrics GetMemoryMetrics()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -94,7 +107,7 @@ namespace Yi.Framework.Core.Helper
|
||||
memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB";
|
||||
memoryMetrics.TotalRAM = Math.Round(memoryMetrics.Total / 1024, 2) + "GB";
|
||||
memoryMetrics.RAMRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total).ToString() + "%";
|
||||
memoryMetrics.CPURate = Math.Ceiling(ParseToDouble(GetCPURate()));
|
||||
|
||||
return memoryMetrics;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -105,7 +118,7 @@ namespace Yi.Framework.Core.Helper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内存大小
|
||||
/// 获取磁盘信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<DiskInfo> GetDiskInfos()
|
||||
@@ -221,6 +234,67 @@ namespace Yi.Framework.Core.Helper
|
||||
}
|
||||
return runTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static CPUInfo GetCPUDetails()
|
||||
{
|
||||
int logicalProcessors = 0;
|
||||
int cores = 0;
|
||||
|
||||
if (IsUnix())
|
||||
{
|
||||
string logicalOutput = ShellHelper.Bash("lscpu | grep '^CPU(s):' | awk '{print $2}'");
|
||||
logicalProcessors = int.Parse(logicalOutput.Trim());
|
||||
|
||||
string coresOutput = ShellHelper.Bash("lscpu | grep 'Core(s) per socket:' | awk '{print $4}'");
|
||||
string socketsOutput = ShellHelper.Bash("lscpu | grep 'Socket(s):' | awk '{print $2}'");
|
||||
cores = int.Parse(coresOutput.Trim()) * int.Parse(socketsOutput.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
string output = ShellHelper.Cmd("wmic", "cpu get NumberOfCores,NumberOfLogicalProcessors /format:csv");
|
||||
var lines = output.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (lines.Length > 1)
|
||||
{
|
||||
var values = lines[1].Split(',');
|
||||
|
||||
cores = int.Parse(values[1].Trim());
|
||||
logicalProcessors =int.Parse(values[2].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
return new CPUInfo
|
||||
{
|
||||
LogicalProcessors = logicalProcessors,
|
||||
Cores = cores
|
||||
};
|
||||
}
|
||||
}
|
||||
public class CPUInfo
|
||||
{
|
||||
public int LogicalProcessors { get; set; }
|
||||
public int Cores { get; set; }
|
||||
}
|
||||
public class CPUMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// 内核数
|
||||
/// </summary>
|
||||
public int CoreTotal { get; set; }
|
||||
/// <summary>
|
||||
/// 逻辑处理器数
|
||||
/// </summary>
|
||||
public int LogicalProcessors { get; set; }
|
||||
/// <summary>
|
||||
/// CPU使用率%
|
||||
/// </summary>
|
||||
public double CPURate { get; set; }
|
||||
/// <summary>
|
||||
/// CPU空闲率%
|
||||
/// </summary>
|
||||
public double FreeRate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,10 +310,7 @@ namespace Yi.Framework.Core.Helper
|
||||
public double Free { get; set; }
|
||||
|
||||
public string UsedRam { get; set; }
|
||||
/// <summary>
|
||||
/// CPU使用率%
|
||||
/// </summary>
|
||||
public double CPURate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总内存 GB
|
||||
/// </summary>
|
||||
@@ -306,20 +377,25 @@ namespace Yi.Framework.Core.Helper
|
||||
/// <returns></returns>
|
||||
public MemoryMetrics GetUnixMetrics()
|
||||
{
|
||||
string output = ShellHelper.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'");
|
||||
string output = ShellHelper.Bash(@"
|
||||
# 从 /proc/meminfo 文件中提取总内存
|
||||
total_mem=$(cat /proc/meminfo | grep -i ""MemTotal"" | awk '{print $2}')
|
||||
# 从 /proc/meminfo 文件中提取剩余内存
|
||||
free_mem=$(cat /proc/meminfo | grep -i ""MemFree"" | awk '{print $2}')
|
||||
# 显示提取的信息
|
||||
echo $total_mem $used_mem $free_mem
|
||||
");
|
||||
var metrics = new MemoryMetrics();
|
||||
var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (lines.Length <= 0) return metrics;
|
||||
if (!string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length >= 2)
|
||||
{
|
||||
metrics.Total = Math.Round(double.Parse(memory[0]) / 1024, 0);
|
||||
|
||||
if (lines != null && lines.Length > 0)
|
||||
{
|
||||
var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length >= 3)
|
||||
{
|
||||
metrics.Total = double.Parse(memory[0]);
|
||||
metrics.Used = double.Parse(memory[1]);
|
||||
metrics.Free = double.Parse(memory[2]);//m
|
||||
metrics.Free = Math.Round(double.Parse(memory[1])/ 1024, 0);//m
|
||||
metrics.Used = metrics.Total - metrics.Free;
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.Core.Json;
|
||||
|
||||
public class DatetimeJsonConverter : JsonConverter<DateTime>
|
||||
{
|
||||
private string _format;
|
||||
public DatetimeJsonConverter(string format="yyyy-MM-dd HH:mm:ss")
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if(reader.TokenType==JsonTokenType.String)
|
||||
{
|
||||
if (DateTime.TryParse(reader.GetString(), out DateTime dateTime)) return dateTime;
|
||||
}
|
||||
return reader.GetDateTime();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString(_format));
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,39 @@ namespace Yi.Framework.Ddd.Application.Contracts
|
||||
/// 查询结束时间条件
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序列名,字段名对应前端
|
||||
/// </summary>
|
||||
public string? OrderByColumn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否顺序,字段名对应前端
|
||||
/// </summary>
|
||||
public string? IsAsc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否顺序
|
||||
/// </summary>
|
||||
public bool CanAsc => IsAsc?.ToLower() == "ascending" ? true : false;
|
||||
|
||||
private string _sorting;
|
||||
|
||||
//排序引用
|
||||
public new string? Sorting
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!OrderByColumn.IsNullOrWhiteSpace())
|
||||
{
|
||||
return $"{OrderByColumn} {(CanAsc ? "ASC" : "DESC")}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return _sorting;
|
||||
}
|
||||
}
|
||||
set => _sorting = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@ using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Yi.Framework.Ddd.Application
|
||||
{
|
||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||
public abstract class
|
||||
YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey,
|
||||
PagedAndSortedResultRequestDto>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TEntityDto : IEntityDto<TKey>
|
||||
{
|
||||
@@ -49,7 +51,8 @@ namespace Yi.Framework.Ddd.Application
|
||||
}
|
||||
|
||||
|
||||
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput,
|
||||
TUpdateInput>
|
||||
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TGetOutputDto : IEntityDto<TKey>
|
||||
@@ -59,6 +62,42 @@ namespace Yi.Framework.Ddd.Application
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
||||
{
|
||||
await CheckUpdatePolicyAsync();
|
||||
|
||||
var entity = await GetEntityByIdAsync(id);
|
||||
await CheckUpdateInputDtoAsync(entity,input);
|
||||
|
||||
await MapToEntityAsync(input, entity);
|
||||
await Repository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
|
||||
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity,TUpdateInput input)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
|
||||
{
|
||||
await CheckCreatePolicyAsync();
|
||||
await CheckCreateInputDtoAsync(input);
|
||||
var entity = await MapToEntityAsync(input);
|
||||
|
||||
TryToSetTenantId(entity);
|
||||
|
||||
await Repository.InsertAsync(entity, autoSave: true);
|
||||
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
|
||||
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多查
|
||||
/// </summary>
|
||||
@@ -70,12 +109,14 @@ namespace Yi.Framework.Ddd.Application
|
||||
//区分多查还是批量查
|
||||
if (input is IPagedResultRequest pagedInput)
|
||||
{
|
||||
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount, string.Empty);
|
||||
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount,
|
||||
string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
entites = await Repository.GetListAsync();
|
||||
}
|
||||
|
||||
var total = await Repository.GetCountAsync();
|
||||
var output = await MapToGetListOutputDtosAsync(entites);
|
||||
return new PagedResultDto<TGetListOutputDto>(total, output);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using SqlSugar;
|
||||
using ArgumentException = System.ArgumentException;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
@@ -19,7 +20,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
/// </summary>
|
||||
public bool EnabledDbSeed { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开启驼峰转下划线
|
||||
/// </summary>
|
||||
public bool EnableUnderLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启codefirst
|
||||
@@ -50,6 +54,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
/// 开启Saas多租户
|
||||
/// </summary>
|
||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
public interface ISqlSugarDbConnectionCreator
|
||||
{
|
||||
DbConnOptions Options { get; }
|
||||
Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
|
||||
Action<object, DataAfterModel> DataExecuted { get; set; }
|
||||
Action<object, DataFilterModel> DataExecuting { get; set; }
|
||||
Action<string, SugarParameter[]> OnLogExecuting { get; set; }
|
||||
Action<string, SugarParameter[]> OnLogExecuted { get; set; }
|
||||
Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
|
||||
|
||||
ConnectionConfig Build(Action<ConnectionConfig>? action = null);
|
||||
void SetDbAop(ISqlSugarClient currentDb);
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
public interface ISqlSugarDbContext
|
||||
{
|
||||
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||
/// <summary>
|
||||
/// SqlSugarDb
|
||||
/// </summary>
|
||||
ISqlSugarClient SqlSugarClient { get; }
|
||||
DbConnOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库备份
|
||||
/// </summary>
|
||||
void BackupDataBase();
|
||||
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
public interface ISqlSugarDbContextDependencies
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行顺序
|
||||
/// </summary>
|
||||
int ExecutionOrder { get; }
|
||||
|
||||
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
|
||||
void DataExecuted(object oldValue, DataAfterModel entityInfo);
|
||||
void DataExecuting(object oldValue, DataFilterModel entityInfo);
|
||||
|
||||
void OnLogExecuting(string sql, SugarParameter[] pars);
|
||||
void OnLogExecuted(string sql, SugarParameter[] pars);
|
||||
|
||||
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Entities.Events;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
public class DefaultSqlSugarDbContext : SqlSugarDbContext
|
||||
{
|
||||
|
||||
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
|
||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||
|
||||
protected IEntityChangeEventHelper EntityChangeEventHelper =>
|
||||
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
|
||||
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
if (IsSoftDeleteFilterEnabled)
|
||||
{
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
|
||||
}
|
||||
|
||||
if (IsMultiTenantFilterEnabled)
|
||||
{
|
||||
//表达式里只能有具体值,不能运算
|
||||
var expressionCurrentTenant = CurrentTenant.Id ?? null;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
//审计日志
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.UpdateByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
|
||||
{
|
||||
if (!DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
{
|
||||
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.InsertByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
|
||||
{
|
||||
//类型为guid
|
||||
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
//主键为空或者为默认最小值
|
||||
if (Guid.Empty.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(GuidGenerator.Create());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
|
||||
{
|
||||
//为空或者为默认最小值
|
||||
if (DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
{
|
||||
//类型为guid
|
||||
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
if (CurrentUser.Id is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
{
|
||||
if (CurrentTenant.Id is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentTenant.Id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//领域事件
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.UpdateByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//软删除,发布的是删除事件
|
||||
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
||||
{
|
||||
if (softDelete.IsDeleted == true)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.DeleteByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//这里sqlsugar有个特殊,删除会返回批量的结果
|
||||
if (entityInfo.EntityValue is IEnumerable entityValues)
|
||||
{
|
||||
foreach (var entityValue in entityValues)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==========Yi-SQL执行:==========");
|
||||
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
|
||||
sb.AppendLine("===============================");
|
||||
Logger.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||
{
|
||||
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
|
||||
{
|
||||
entityColumnInfo.IsEnableUpdateVersionValidation = true;
|
||||
}
|
||||
|
||||
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
|
||||
{
|
||||
entityColumnInfo.IsIgnore = true;
|
||||
}
|
||||
|
||||
if (propertyInfo.Name == nameof(Entity<object>.Id))
|
||||
{
|
||||
entityColumnInfo.IsPrimarykey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Nito.AsyncEx;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Linq;
|
||||
@@ -13,9 +17,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
|
||||
{
|
||||
public ISqlSugarClient _Db => GetDbContextAsync().Result;
|
||||
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
|
||||
|
||||
public ISugarQueryable<TEntity> _DbQueryable => GetDbContextAsync().Result.Queryable<TEntity>();
|
||||
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
|
||||
|
||||
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
|
||||
public IAsyncQueryableExecuter AsyncExecuter { get; }
|
||||
@@ -33,9 +37,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
/// <returns></returns>
|
||||
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
|
||||
{
|
||||
|
||||
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
|
||||
//await Console.Out.WriteLineAsync("获取的id:" + db.ContextID);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -372,6 +374,20 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
|
||||
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
|
||||
{
|
||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>())//带版本号乐观锁更新
|
||||
{
|
||||
try
|
||||
{
|
||||
int num = await (await GetDbSimpleClientAsync())
|
||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||
return num>0;
|
||||
}
|
||||
catch (VersionExceptions ex)
|
||||
{
|
||||
|
||||
throw new AbpDbConcurrencyException($"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||
}
|
||||
}
|
||||
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public static class SqlSugarCoreExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增db对象,可支持多个
|
||||
/// </summary>
|
||||
/// <param name="service"></param>
|
||||
/// <param name="serviceLifetime"></param>
|
||||
/// <typeparam name="TDbContext"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies
|
||||
{
|
||||
service.AddTransient<ISqlSugarDbContextDependencies, TDbContext>();
|
||||
return service;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增db对象,可支持多个
|
||||
/// </summary>
|
||||
/// <param name="service"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <typeparam name="TDbContext"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, Action<DbConnOptions> options) where TDbContext : class, ISqlSugarDbContextDependencies
|
||||
{
|
||||
service.Configure<DbConnOptions>(options.Invoke);
|
||||
service.AddYiDbContext<TDbContext>();
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
|
||||
public class SqlSugarDbConnectionCreator: ISqlSugarDbConnectionCreator,ITransientDependency
|
||||
{
|
||||
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
|
||||
{
|
||||
Options = options.Value;
|
||||
}
|
||||
public DbConnOptions Options { get; }
|
||||
|
||||
public void SetDbAop(ISqlSugarClient currentDb)
|
||||
{
|
||||
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
|
||||
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
|
||||
currentDb.Aop.DataExecuting = this.DataExecuting;
|
||||
currentDb.Aop.DataExecuted = this.DataExecuted;
|
||||
OnSqlSugarClientConfig(currentDb);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ConnectionConfig Build(Action<ConnectionConfig>? action=null)
|
||||
{
|
||||
var dbConnOptions = Options;
|
||||
#region 组装options
|
||||
if (dbConnOptions.DbType is null)
|
||||
{
|
||||
throw new ArgumentException("DbType配置为空");
|
||||
}
|
||||
var slavaConFig = new List<SlaveConnectionConfig>();
|
||||
if (dbConnOptions.EnabledReadWrite)
|
||||
{
|
||||
if (dbConnOptions.ReadUrl is null)
|
||||
{
|
||||
throw new ArgumentException("读写分离为空");
|
||||
}
|
||||
|
||||
var readCon = dbConnOptions.ReadUrl;
|
||||
|
||||
readCon.ForEach(s =>
|
||||
{
|
||||
//如果是动态saas分库,这里的连接串都不能写死,需要动态添加,这里只配置共享库的连接
|
||||
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 组装连接config
|
||||
var connectionConfig = new ConnectionConfig()
|
||||
{
|
||||
ConfigId= ConnectionStrings.DefaultConnectionStringName,
|
||||
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
|
||||
ConnectionString = dbConnOptions.Url,
|
||||
IsAutoCloseConnection = true,
|
||||
SlaveConnectionConfigs = slavaConFig,
|
||||
//设置codefirst非空值判断
|
||||
ConfigureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityService = (c, p) =>
|
||||
{
|
||||
if (new NullabilityInfoContext()
|
||||
.Create(c).WriteState is NullabilityState.Nullable)
|
||||
{
|
||||
p.IsNullable = true;
|
||||
}
|
||||
|
||||
EntityService(c, p);
|
||||
}
|
||||
},
|
||||
//这里多租户有个坑,无效的
|
||||
AopEvents = new AopEvents
|
||||
{
|
||||
DataExecuted = DataExecuted,
|
||||
DataExecuting = DataExecuting,
|
||||
OnLogExecuted = OnLogExecuted,
|
||||
OnLogExecuting = OnLogExecuting
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (action is not null)
|
||||
{
|
||||
action.Invoke(connectionConfig);
|
||||
}
|
||||
#endregion
|
||||
return connectionConfig;
|
||||
}
|
||||
[DisablePropertyInjection]
|
||||
public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<object, DataAfterModel> DataExecuted { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<object, DataFilterModel> DataExecuting { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,376 +1,43 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Security.Policy;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Reflection;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Entities.Events;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
|
||||
{
|
||||
public class SqlSugarDbContext : ISqlSugarDbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
//属性注入
|
||||
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||
protected ISqlSugarClient SqlSugarClient { get;private set; }
|
||||
public int ExecutionOrder => 0;
|
||||
|
||||
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
|
||||
|
||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||
|
||||
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
public AbpDbConnectionOptions ConnectionOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbConnectionOptions>>().Value;
|
||||
|
||||
public ISerializeService SerializeService=> LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||
|
||||
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
||||
public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
SqlSugarClient = sqlSugarClient;
|
||||
}
|
||||
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
|
||||
_dbConnectionCreator = connectionCreator;
|
||||
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
|
||||
connectionCreator.EntityService = EntityService;
|
||||
connectionCreator.DataExecuting = DataExecuting;
|
||||
connectionCreator.DataExecuted = DataExecuted;
|
||||
connectionCreator.OnLogExecuting = OnLogExecuting;
|
||||
connectionCreator.OnLogExecuted = OnLogExecuted;
|
||||
SqlSugarClient = new SqlSugarClient(connectionCreator.Build(action: options =>
|
||||
{
|
||||
options.ConnectionString = GetCurrentConnectionString();
|
||||
options.DbType = GetCurrentDbType();
|
||||
}));
|
||||
connectionCreator.SetDbAop(SqlSugarClient);
|
||||
//替换默认序列化器
|
||||
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// db切换多库支持
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string GetCurrentConnectionString()
|
||||
{
|
||||
var defautlUrl = Options.Url ?? ConnectionOptions.GetConnectionStringOrNull(ConnectionStrings.DefaultConnectionStringName);
|
||||
//如果未开启多租户,返回db url 或者 默认连接字符串
|
||||
if (!Options.EnabledSaasMultiTenancy)
|
||||
{
|
||||
return defautlUrl;
|
||||
}
|
||||
|
||||
//开启了多租户
|
||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
||||
var connectionString = connectionStringResolver.ResolveAsync().GetAwaiter().GetResult();
|
||||
|
||||
|
||||
//没有检测到使用多租户功能,默认使用默认库即可
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
|
||||
connectionString = defautlUrl;
|
||||
}
|
||||
return connectionString!;
|
||||
}
|
||||
|
||||
protected virtual DbType GetCurrentDbType()
|
||||
{
|
||||
if (CurrentTenant.Name is not null)
|
||||
{
|
||||
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
|
||||
if (dbTypeFromTenantName is not null)
|
||||
{
|
||||
return dbTypeFromTenantName.Value;
|
||||
}
|
||||
}
|
||||
Volo.Abp.Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
return Options.DbType!.Value;
|
||||
}
|
||||
|
||||
//根据租户name进行匹配db类型: Test_Sqlite,[来自AI]
|
||||
private DbType? GetDbTypeFromTenantName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找下划线的位置
|
||||
int underscoreIndex = name.LastIndexOf('_');
|
||||
|
||||
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取 枚举 部分
|
||||
string enumString = name.Substring(underscoreIndex + 1);
|
||||
|
||||
// 尝试将 尾缀 转换为枚举
|
||||
if (Enum.TryParse<DbType>(enumString, out DbType result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 条件不满足时返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 上下文对象扩展
|
||||
/// </summary>
|
||||
/// <param name="sqlSugarClient"></param>
|
||||
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
//需自定义扩展
|
||||
if (IsSoftDeleteFilterEnabled)
|
||||
{
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
|
||||
}
|
||||
if (IsMultiTenantFilterEnabled)
|
||||
{
|
||||
//表达式不能放方法
|
||||
Guid? tenantId = CurrentTenant?.Id;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == tenantId);
|
||||
}
|
||||
CustomDataFilter(sqlSugarClient);
|
||||
}
|
||||
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
|
||||
}
|
||||
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
/// <param name="oldValue"></param>
|
||||
/// <param name="entityInfo"></param>
|
||||
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
//审计日志
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.UpdateByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
|
||||
{
|
||||
if (!DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
|
||||
{
|
||||
//主键为空或者为默认最小值
|
||||
if (Guid.Empty.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(GuidGenerator.Create());
|
||||
}
|
||||
}
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
|
||||
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
//为空或者为默认最小值
|
||||
if (oldValue is null || DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
|
||||
//插入时,需要租户id,先预留
|
||||
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (CurrentTenant is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentTenant.Id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//领域事件
|
||||
switch (entityInfo.OperationType)
|
||||
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
}
|
||||
|
||||
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
break;
|
||||
case DataFilterType.UpdateByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//软删除,发布的是删除事件
|
||||
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
||||
{
|
||||
if (softDelete.IsDeleted == true)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case DataFilterType.DeleteByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//这里sqlsugar有个特殊,删除会返回批量的结果
|
||||
if (entityInfo.EntityValue is IEnumerable entityValues)
|
||||
{
|
||||
foreach (var entityValue in entityValues)
|
||||
{
|
||||
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="pars"></param>
|
||||
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==========Yi-SQL执行:==========");
|
||||
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
|
||||
sb.AppendLine("===============================");
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="pars"></param>
|
||||
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体配置
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="column"></param>
|
||||
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
|
||||
{
|
||||
if (property.Name == "ConcurrencyStamp")
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
if (property.PropertyType == typeof(ExtraPropertyDictionary))
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
if (property.Name == nameof(Entity<object>.Id))
|
||||
{
|
||||
column.IsPrimarykey = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void BackupDataBase()
|
||||
{
|
||||
string directoryName = "database_backup";
|
||||
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
switch (Options.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
//MySql
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.sql");//mysql 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.Sqlite:
|
||||
//Sqlite
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.SqlServer:
|
||||
//SqlServer
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.bak"/*服务器路径*/);//第一个参数库名
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("其他数据库备份未实现");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Threading;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
using Check = Volo.Abp.Check;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public class SqlSugarDbContextFactory : ISqlSugarDbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
|
||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||
|
||||
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
|
||||
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
|
||||
|
||||
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
|
||||
|
||||
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
|
||||
var connectionString = GetCurrentConnectionString();
|
||||
|
||||
//获取连接配置操作,需要进行缓存
|
||||
var connectionConfig = ConnectionConfigCache.GetOrAdd(connectionString, (_) =>
|
||||
BuildConnectionConfig(action: options =>
|
||||
{
|
||||
options.ConnectionString = connectionString;
|
||||
options.DbType = GetCurrentDbType();
|
||||
}));
|
||||
SqlSugarClient = new SqlSugarClient(connectionConfig);
|
||||
//生命周期,以下都可以直接使用sqlsugardb了
|
||||
|
||||
// Aop及多租户连接字符串和类型,需要单独设置
|
||||
// Aop操作不能进行缓存
|
||||
SetDbAop(SqlSugarClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建Aop-sqlsugaraop在多租户模式中,需单独设置
|
||||
/// </summary>
|
||||
/// <param name="sqlSugarClient"></param>
|
||||
protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
//替换默认序列化器
|
||||
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||
|
||||
//将所有,ISqlSugarDbContextDependencies进行累加
|
||||
Action<string, SugarParameter[]> onLogExecuting = null;
|
||||
Action<string, SugarParameter[]> onLogExecuted = null;
|
||||
Action<object, DataFilterModel> dataExecuting = null;
|
||||
Action<object, DataAfterModel> dataExecuted = null;
|
||||
Action<ISqlSugarClient> onSqlSugarClientConfig = null;
|
||||
|
||||
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
|
||||
{
|
||||
onLogExecuting += dependency.OnLogExecuting;
|
||||
onLogExecuted += dependency.OnLogExecuted;
|
||||
dataExecuting += dependency.DataExecuting;
|
||||
dataExecuted += dependency.DataExecuted;
|
||||
|
||||
onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig;
|
||||
}
|
||||
|
||||
//最先存放db操作
|
||||
onSqlSugarClientConfig(sqlSugarClient);
|
||||
|
||||
sqlSugarClient.Aop.OnLogExecuting =onLogExecuting;
|
||||
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
|
||||
|
||||
sqlSugarClient.Aop.DataExecuting =dataExecuting;
|
||||
sqlSugarClient.Aop.DataExecuted =dataExecuted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建连接配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig>? action = null)
|
||||
{
|
||||
var dbConnOptions = Options;
|
||||
|
||||
#region 组装options
|
||||
|
||||
if (dbConnOptions.DbType is null)
|
||||
{
|
||||
throw new ArgumentException("DbType配置为空");
|
||||
}
|
||||
|
||||
var slavaConFig = new List<SlaveConnectionConfig>();
|
||||
if (dbConnOptions.EnabledReadWrite)
|
||||
{
|
||||
if (dbConnOptions.ReadUrl is null)
|
||||
{
|
||||
throw new ArgumentException("读写分离为空");
|
||||
}
|
||||
|
||||
var readCon = dbConnOptions.ReadUrl;
|
||||
|
||||
readCon.ForEach(s =>
|
||||
{
|
||||
//如果是动态saas分库,这里的连接串都不能写死,需要动态添加,这里只配置共享库的连接
|
||||
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 组装连接config
|
||||
|
||||
var connectionConfig = new ConnectionConfig()
|
||||
{
|
||||
ConfigId = ConnectionStrings.DefaultConnectionStringName,
|
||||
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
|
||||
ConnectionString = dbConnOptions.Url,
|
||||
IsAutoCloseConnection = true,
|
||||
SlaveConnectionConfigs = slavaConFig,
|
||||
//设置codefirst非空值判断
|
||||
ConfigureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
// 处理表
|
||||
EntityNameService = (type, entity) =>
|
||||
{
|
||||
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
|
||||
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
|
||||
},
|
||||
EntityService = (c, p) =>
|
||||
{
|
||||
if (new NullabilityInfoContext()
|
||||
.Create(c).WriteState is NullabilityState.Nullable)
|
||||
{
|
||||
p.IsNullable = true;
|
||||
}
|
||||
|
||||
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
|
||||
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName); // 驼峰转下划线
|
||||
|
||||
//将所有,ISqlSugarDbContextDependencies的EntityService进行累加
|
||||
//额外的实体服务需要这里配置,
|
||||
|
||||
Action<PropertyInfo, EntityColumnInfo> entityService = null;
|
||||
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
|
||||
{
|
||||
entityService += dependency.EntityService;
|
||||
}
|
||||
|
||||
entityService(c, p);
|
||||
}
|
||||
},
|
||||
//这里多租户有个坑,这里配置是无效的
|
||||
// AopEvents = new AopEvents
|
||||
// {
|
||||
// DataExecuted = DataExecuted,
|
||||
// DataExecuting = DataExecuting,
|
||||
// OnLogExecuted = OnLogExecuted,
|
||||
// OnLogExecuting = OnLogExecuting
|
||||
// }
|
||||
};
|
||||
|
||||
if (action is not null)
|
||||
{
|
||||
action.Invoke(connectionConfig);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return connectionConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// db切换多库支持
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string GetCurrentConnectionString()
|
||||
{
|
||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
||||
var connectionString =
|
||||
AsyncHelper.RunSync(() => connectionStringResolver.ResolveAsync());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
Check.NotNull(Options.Url, "dbUrl未配置");
|
||||
}
|
||||
|
||||
return connectionString!;
|
||||
}
|
||||
|
||||
protected virtual DbType GetCurrentDbType()
|
||||
{
|
||||
if (CurrentTenant.Name is not null)
|
||||
{
|
||||
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
|
||||
if (dbTypeFromTenantName is not null)
|
||||
{
|
||||
return dbTypeFromTenantName.Value;
|
||||
}
|
||||
}
|
||||
|
||||
Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
return Options.DbType!.Value;
|
||||
}
|
||||
|
||||
//根据租户name进行匹配db类型: Test_Sqlite,[来自AI]
|
||||
private DbType? GetDbTypeFromTenantName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找下划线的位置
|
||||
int underscoreIndex = name.LastIndexOf('_');
|
||||
|
||||
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取 枚举 部分
|
||||
string enumString = name.Substring(underscoreIndex + 1);
|
||||
|
||||
// 尝试将 尾缀 转换为枚举
|
||||
if (Enum.TryParse<DbType>(enumString, out DbType result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 条件不满足时返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public virtual void BackupDataBase()
|
||||
{
|
||||
string directoryName = "database_backup";
|
||||
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
|
||||
switch (Options.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
//MySql
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
|
||||
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.Sqlite:
|
||||
//Sqlite
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.SqlServer:
|
||||
//SqlServer
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
|
||||
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("其他数据库备份未实现");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public static class SqlsugarCoreExtensions
|
||||
{
|
||||
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
|
||||
{
|
||||
service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
|
||||
return service;
|
||||
}
|
||||
public static IServiceCollection TryAddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
|
||||
{
|
||||
service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
|
||||
return service;
|
||||
}
|
||||
|
||||
|
||||
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, Action<DbConnOptions> options) where DbContext : class, ISqlSugarDbContext
|
||||
{
|
||||
|
||||
service.Configure<DbConnOptions>(ops =>
|
||||
{
|
||||
options.Invoke(ops);
|
||||
});
|
||||
service.AddYiDbContext<DbContext>();
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
|
||||
public ISqlSugarDbContext GetDbContext()
|
||||
{
|
||||
|
||||
return _sqlsugarDbContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
{
|
||||
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
||||
{
|
||||
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
@@ -28,8 +26,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
IConnectionStringResolver connectionStringResolver,
|
||||
ICancellationTokenProvider cancellationTokenProvider,
|
||||
ICurrentTenant currentTenant,
|
||||
ISqlSugarDbConnectionCreator dbConnectionCreator
|
||||
ICurrentTenant currentTenant
|
||||
)
|
||||
{
|
||||
UnitOfWorkManager = unitOfWorkManager;
|
||||
@@ -37,7 +34,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
CancellationTokenProvider = cancellationTokenProvider;
|
||||
CurrentTenant = currentTenant;
|
||||
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
|
||||
_dbConnectionCreator = dbConnectionCreator;
|
||||
}
|
||||
|
||||
//private static object _databaseApiLock = new object();
|
||||
@@ -48,7 +44,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
|
||||
//获取当前连接字符串,未多租户时,默认为空
|
||||
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
|
||||
var dbContextKey = $"{this.GetType().FullName}_{connectionString}";
|
||||
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
|
||||
|
||||
|
||||
var unitOfWork = UnitOfWorkManager.Current;
|
||||
|
||||
@@ -6,12 +6,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.Domain;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Modularity;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
using Yi.Framework.SqlSugarCore.Repositories;
|
||||
using Yi.Framework.SqlSugarCore.Uow;
|
||||
@@ -25,9 +22,10 @@ namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
var service = context.Services;
|
||||
var configuration = service.GetConfiguration();
|
||||
Configure<DbConnOptions>(configuration.GetSection("DbConnOptions"));
|
||||
var section = configuration.GetSection("DbConnOptions");
|
||||
Configure<DbConnOptions>(section);
|
||||
|
||||
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
|
||||
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContextFactory>();
|
||||
|
||||
//不开放sqlsugarClient
|
||||
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
|
||||
@@ -40,7 +38,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
|
||||
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
|
||||
//替换Sqlsugar默认序列化器,用来解决.Select()不支持嵌套对象/匿名对象的非公有访问器 值无法绑定,如Id属性
|
||||
context.Services.AddSingleton<ISerializeService,SqlSugarNonPublicSerializer> ();
|
||||
context.Services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
|
||||
|
||||
var dbConfig = section.Get<DbConnOptions>();
|
||||
//将默认db传递给abp连接字符串模块
|
||||
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
|
||||
|
||||
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -51,7 +55,7 @@ namespace Yi.Framework.SqlSugarCore
|
||||
var service = context.ServiceProvider;
|
||||
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
var _logger= service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
|
||||
var logger = service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
|
||||
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -65,13 +69,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
sb.AppendLine("===============================");
|
||||
|
||||
|
||||
_logger.LogInformation(sb.ToString());
|
||||
//Todo:准备支持多租户种子数据及CodeFirst
|
||||
logger.LogInformation(sb.ToString());
|
||||
|
||||
if (options.EnabledCodeFirst)
|
||||
{
|
||||
CodeFirst(service);
|
||||
}
|
||||
|
||||
if (options.EnabledDbSeed)
|
||||
{
|
||||
await DataSeedAsync(service);
|
||||
@@ -80,7 +84,6 @@ namespace Yi.Framework.SqlSugarCore
|
||||
|
||||
private void CodeFirst(IServiceProvider service)
|
||||
{
|
||||
|
||||
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
||||
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
||||
|
||||
@@ -95,11 +98,11 @@ namespace Yi.Framework.SqlSugarCore
|
||||
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
|
||||
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
||||
}
|
||||
|
||||
if (types.Count > 0)
|
||||
{
|
||||
db.CopyNew().CodeFirst.InitTables(types.ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task DataSeedAsync(IServiceProvider service)
|
||||
|
||||
@@ -16,6 +16,7 @@ public class AuditingStore : IAuditingStore, ITransientDependency
|
||||
protected IUnitOfWorkManager UnitOfWorkManager { get; }
|
||||
protected AbpAuditingOptions Options { get; }
|
||||
protected IAuditLogInfoToAuditLogConverter Converter { get; }
|
||||
|
||||
public AuditingStore(
|
||||
IAuditLogRepository auditLogRepository,
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
@@ -52,10 +53,10 @@ public class AuditingStore : IAuditingStore, ITransientDependency
|
||||
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
|
||||
{
|
||||
Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss"));
|
||||
using (var uow = UnitOfWorkManager.Begin(true))
|
||||
{
|
||||
// using (var uow = UnitOfWorkManager.Begin(true,isTransactional:false))
|
||||
// {
|
||||
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
// await uow.CompleteAsync();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public class AuditLogActionEntity : Entity<Guid>, IMultiTenant
|
||||
|
||||
public virtual string? MethodName { get; protected set; }
|
||||
|
||||
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
|
||||
public virtual string? Parameters { get; protected set; }
|
||||
|
||||
public virtual DateTime? ExecutionTime { get; protected set; }
|
||||
|
||||
@@ -103,12 +103,14 @@ namespace Yi.Framework.AuditLogging.Domain.Entities
|
||||
|
||||
public virtual string? CorrelationId { get; set; }
|
||||
|
||||
[SugarColumn(Length = 2000)]
|
||||
public virtual string? BrowserInfo { get; protected set; }
|
||||
|
||||
public virtual string? HttpMethod { get; protected set; }
|
||||
|
||||
public virtual string? Url { get; protected set; }
|
||||
|
||||
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
|
||||
public virtual string? Exceptions { get; protected set; }
|
||||
|
||||
public virtual string? Comments { get; protected set; }
|
||||
|
||||
@@ -9,11 +9,11 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
public class DiscussGetListOutputDto : EntityDto<Guid>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否禁止评论创建功能
|
||||
/// 是否禁止评论创建功能
|
||||
/// </summary>
|
||||
public bool IsDisableCreateComment { get; set; }
|
||||
/// <summary>
|
||||
/// 是否已点赞,默认未登录不点赞
|
||||
/// 是否已点赞,默认未登录不点赞
|
||||
/// </summary>
|
||||
public bool IsAgree { get; set; } = false;
|
||||
public string Title { get; set; }
|
||||
@@ -23,26 +23,26 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
public int AgreeNum { get; set; }
|
||||
public int SeeNum { get; set; }
|
||||
|
||||
//批量查询,不给内容,性能考虑
|
||||
//批量查询,不给内容,性能考虑
|
||||
//public string Content { get; set; }
|
||||
public string? Color { get; set; }
|
||||
|
||||
public Guid PlateId { get; set; }
|
||||
|
||||
//是否置顶,默认false
|
||||
//是否置顶,默认false
|
||||
public bool IsTop { get; set; }
|
||||
|
||||
public DiscussPermissionTypeEnum PermissionType { get; set; }
|
||||
//是否禁止,默认false
|
||||
//是否禁止,默认false
|
||||
public bool IsBan { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 封面
|
||||
/// 封面
|
||||
/// </summary>
|
||||
public string? Cover { get; set; }
|
||||
|
||||
//私有需要判断code权限
|
||||
//私有需要判断code权限
|
||||
public string? PrivateCode { get; set; }
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
Title = DiscussConst.Privacy;
|
||||
Introduction = "";
|
||||
Cover = null;
|
||||
//被禁止
|
||||
//被禁止
|
||||
IsBan = true;
|
||||
}
|
||||
}
|
||||
@@ -73,14 +73,14 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
case DiscussPermissionTypeEnum.Public:
|
||||
break;
|
||||
case DiscussPermissionTypeEnum.Oneself:
|
||||
//当前主题是仅自己可见,同时不是当前登录用户
|
||||
//当前主题是仅自己可见,同时不是当前登录用户
|
||||
if (dto.User.Id != userId)
|
||||
{
|
||||
dto.SetBan();
|
||||
}
|
||||
break;
|
||||
case DiscussPermissionTypeEnum.User:
|
||||
//当前主题为部分可见,同时不是当前登录用户 也 不在可见用户列表中
|
||||
//当前主题为部分可见,同时不是当前登录用户 也 不在可见用户列表中
|
||||
if (dto.User.Id != userId && !dto.PermissionUserIds.Contains(userId))
|
||||
{
|
||||
dto.SetBan();
|
||||
|
||||
@@ -4,7 +4,9 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.EventBus;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Caches;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Extensions;
|
||||
|
||||
@@ -14,6 +16,29 @@ namespace Yi.Framework.Bbs.Application.Extensions;
|
||||
/// 需考虑一致性问题,又不能上锁影响性能
|
||||
/// </summary>
|
||||
public class AccessLogMiddleware : IMiddleware, ITransientDependency
|
||||
{
|
||||
private static int _accessLogNumber = 0;
|
||||
|
||||
internal static void ResetAccessLogNumber()
|
||||
{
|
||||
_accessLogNumber = 0;
|
||||
}
|
||||
internal static int GetAccessLogNumber()
|
||||
{
|
||||
return _accessLogNumber;
|
||||
}
|
||||
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
await next(context);
|
||||
|
||||
Interlocked.Increment(ref _accessLogNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public class AccessLogResetEventHandler : ILocalEventHandler<AccessLogResetArgs>,
|
||||
ITransientDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存前缀
|
||||
@@ -40,12 +65,27 @@ public class AccessLogMiddleware : IMiddleware, ITransientDependency
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
//该事件由job定时10秒触发
|
||||
public async Task HandleEventAsync(AccessLogResetArgs eventData)
|
||||
{
|
||||
await next(context);
|
||||
if (EnableRedisCache)
|
||||
{
|
||||
await RedisClient.IncrByAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}", 1);
|
||||
//分布式锁
|
||||
if (await RedisClient.SetNxAsync("AccessLogLock",true,TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
//自增长数
|
||||
var incrNumber= AccessLogMiddleware.GetAccessLogNumber();
|
||||
//立即重置,开始计算,方式丢失
|
||||
AccessLogMiddleware.ResetAccessLogNumber();
|
||||
if (incrNumber>0)
|
||||
{
|
||||
await RedisClient.IncrByAsync(
|
||||
$"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date:yyyyMMdd}", incrNumber);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using FreeRedis;
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.EventBus.Local;
|
||||
using Yi.Framework.Bbs.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Caches;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Jobs;
|
||||
|
||||
public class AccessLogCacheJob : HangfireBackgroundWorkerBase
|
||||
{
|
||||
private readonly ILocalEventBus _localEventBus;
|
||||
|
||||
public AccessLogCacheJob(ILocalEventBus localEventBus)
|
||||
{
|
||||
_localEventBus = localEventBus;
|
||||
RecurringJobId = "访问日志写入缓存";
|
||||
//每10秒执行一次,将本地缓存转入redis,防止丢数据
|
||||
CronExpression = "*/10 * * * * *";
|
||||
//
|
||||
// JobDetail = JobBuilder.Create<AccessLogCacheJob>().WithIdentity(nameof(AccessLogCacheJob))
|
||||
// .Build();
|
||||
|
||||
//每10秒执行一次,将本地缓存转入redis,防止丢数据
|
||||
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogCacheJob))
|
||||
// .WithSimpleSchedule((schedule) => { schedule.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever();; })
|
||||
// .Build();
|
||||
}
|
||||
|
||||
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
await _localEventBus.PublishAsync(new AccessLogResetArgs());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
using FreeRedis;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Caches;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
@@ -13,7 +11,7 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Jobs;
|
||||
|
||||
public class AccessLogStoreJob : QuartzBackgroundWorkerBase
|
||||
public class AccessLogStoreJob : HangfireBackgroundWorkerBase
|
||||
{
|
||||
private readonly ISqlSugarRepository<AccessLogAggregateRoot> _repository;
|
||||
|
||||
@@ -45,24 +43,29 @@ public class AccessLogStoreJob : QuartzBackgroundWorkerBase
|
||||
public AccessLogStoreJob(ISqlSugarRepository<AccessLogAggregateRoot> repository)
|
||||
{
|
||||
_repository = repository;
|
||||
JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
|
||||
.Build();
|
||||
|
||||
|
||||
RecurringJobId = "访问日志写入数据库";
|
||||
//每分钟执行一次
|
||||
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
|
||||
.WithCronSchedule("0 * * * * ?")
|
||||
.Build();
|
||||
CronExpression = "0 * * * * ?";
|
||||
// JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
|
||||
// .Build();
|
||||
// //每分钟执行一次
|
||||
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
|
||||
// .WithCronSchedule("0 * * * * ?")
|
||||
// .Build();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override async Task Execute(IJobExecutionContext context)
|
||||
|
||||
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
if (EnableRedisCache)
|
||||
{
|
||||
//当天的访问量
|
||||
var number =
|
||||
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}");
|
||||
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date:yyyyMMdd}");
|
||||
|
||||
|
||||
var entity = await _repository._DbQueryable.Where(x => x.AccessLogType == AccessLogTypeEnum.Request)
|
||||
@@ -81,7 +84,7 @@ public class AccessLogStoreJob : QuartzBackgroundWorkerBase
|
||||
}
|
||||
|
||||
//删除前一天的缓存
|
||||
await RedisClient.DelAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date.AddDays(-1)}");
|
||||
await RedisClient.DelAsync($"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date.AddDays(-1):yyyyMMdd}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Yi.Framework.Bbs.Domain.Managers;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Jobs;
|
||||
@@ -7,20 +6,25 @@ namespace Yi.Framework.Bbs.Application.Jobs;
|
||||
/// <summary>
|
||||
/// 每日任务job
|
||||
/// </summary>
|
||||
public class AssignmentExpireTimeOutJob : QuartzBackgroundWorkerBase
|
||||
public class AssignmentExpireTimeOutJob : HangfireBackgroundWorkerBase
|
||||
{
|
||||
private readonly AssignmentManager _assignmentManager;
|
||||
|
||||
public AssignmentExpireTimeOutJob(AssignmentManager assignmentManager)
|
||||
{
|
||||
_assignmentManager = assignmentManager;
|
||||
JobDetail = JobBuilder.Create<AssignmentExpireTimeOutJob>().WithIdentity(nameof(AssignmentExpireTimeOutJob)).Build();
|
||||
//每个小时整点执行一次
|
||||
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AssignmentExpireTimeOutJob)).WithCronSchedule("0 0 * * * ?")
|
||||
.Build();
|
||||
|
||||
RecurringJobId = "每日任务系统超时检测";
|
||||
//每分钟执行一次
|
||||
CronExpression = "0 * * * * ?";
|
||||
//
|
||||
// JobDetail = JobBuilder.Create<AssignmentExpireTimeOutJob>().WithIdentity(nameof(AssignmentExpireTimeOutJob)).Build();
|
||||
// //每个小时整点执行一次
|
||||
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AssignmentExpireTimeOutJob)).WithCronSchedule("0 0 * * * ?")
|
||||
// .Build();
|
||||
}
|
||||
|
||||
public override async Task Execute(IJobExecutionContext context)
|
||||
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
await _assignmentManager.ExpireTimeoutAsync();
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
using Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Yi.Framework.Bbs.Domain.Managers;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Jobs
|
||||
{
|
||||
public class InterestRecordsJob : QuartzBackgroundWorkerBase
|
||||
public class InterestRecordsJob : HangfireBackgroundWorkerBase
|
||||
{
|
||||
private BankManager _bankManager;
|
||||
public InterestRecordsJob(BankManager bankManager)
|
||||
{
|
||||
_bankManager = bankManager;
|
||||
JobDetail = JobBuilder.Create<InterestRecordsJob>().WithIdentity(nameof(InterestRecordsJob)).Build();
|
||||
|
||||
RecurringJobId = "银行利息积分刷新";
|
||||
//每个小时整点执行一次
|
||||
CronExpression = "0 0 * * * ?";
|
||||
|
||||
Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
|
||||
// JobDetail = JobBuilder.Create<InterestRecordsJob>().WithIdentity(nameof(InterestRecordsJob)).Build();
|
||||
//
|
||||
// //每个小时整点执行一次
|
||||
//
|
||||
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
|
||||
|
||||
//测试
|
||||
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob))
|
||||
@@ -23,7 +27,8 @@ namespace Yi.Framework.Bbs.Application.Jobs
|
||||
// .RepeatForever())
|
||||
//.Build();
|
||||
}
|
||||
public override async Task Execute(IJobExecutionContext context)
|
||||
|
||||
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
//创建一个记录,莫得了
|
||||
await _bankManager.GetCurrentInterestRate();
|
||||
|
||||
@@ -17,9 +17,11 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
|
||||
public class BbsForumAnalyseService : ApplicationService, IApplicationService
|
||||
{
|
||||
private ForumManager _forumManager;
|
||||
public BbsForumAnalyseService(ForumManager forumManager)
|
||||
private ISqlSugarRepository<AgreeEntity> _agreeRepository;
|
||||
public BbsForumAnalyseService(ForumManager forumManager, ISqlSugarRepository<AgreeEntity> agreeRepository)
|
||||
{
|
||||
_forumManager = forumManager;
|
||||
_agreeRepository = agreeRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,7 +40,7 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
|
||||
.Select((discuss, user, info) => new DiscussGetListOutputDto
|
||||
{
|
||||
Id = discuss.Id,
|
||||
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
// IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
@@ -52,6 +54,26 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
|
||||
|
||||
}, true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount);
|
||||
var discussId = output.Select(x => x.Id);
|
||||
//点赞字典,key为主题id,y为用户ids
|
||||
var agreeDic =
|
||||
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
|
||||
.GroupBy(x => x.DiscussId)
|
||||
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
|
||||
|
||||
//等级、是否点赞赋值
|
||||
output?.ForEach(x =>
|
||||
{
|
||||
if (CurrentUser.Id is not null)
|
||||
{
|
||||
//默认fasle
|
||||
if (agreeDic.TryGetValue(x.Id,out var userIds))
|
||||
{
|
||||
x.IsAgree = userIds.Contains(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,13 +27,19 @@ namespace Yi.Framework.Bbs.Application.Services
|
||||
var userEntity = await _bbsUserManager._userRepository.GetFirstAsync(x => x.UserName == userNameOrUserId);
|
||||
if (userEntity == null)
|
||||
{
|
||||
throw new Volo.Abp.UserFriendlyException("该用户不存在");
|
||||
throw new UserFriendlyException("该用户不存在");
|
||||
}
|
||||
userId= userEntity.Id;
|
||||
}
|
||||
|
||||
var output =await _bbsUserManager.GetBbsUserInfoAsync(userId);
|
||||
|
||||
//不是自己
|
||||
if (CurrentUser.Id != output.Id)
|
||||
{
|
||||
output.Phone = null;
|
||||
output.Email=null;
|
||||
}
|
||||
return output!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
[Authorize]
|
||||
public override async Task<CommentGetOutputDto> CreateAsync(CommentCreateInputVo input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.Content)|| input.Content=="<p><br></p>")
|
||||
{
|
||||
throw new UserFriendlyException("评论不能为空");
|
||||
}
|
||||
|
||||
var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);
|
||||
if (discuess is null)
|
||||
{
|
||||
|
||||
@@ -29,19 +29,27 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
/// <summary>
|
||||
/// Discuss应用服务实现,用于参数校验、领域服务业务组合、日志记录、事务处理、账户信息
|
||||
/// </summary>
|
||||
public class DiscussService : YiCrudAppService<DiscussAggregateRoot, DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>,
|
||||
public class DiscussService : YiCrudAppService<DiscussAggregateRoot, DiscussGetOutputDto, DiscussGetListOutputDto,
|
||||
Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>,
|
||||
IDiscussService
|
||||
{
|
||||
private ISqlSugarRepository<DiscussTopEntity> _discussTopEntityRepository;
|
||||
private ISqlSugarRepository<DiscussTopEntity> _discussTopRepository;
|
||||
private ISqlSugarRepository<AgreeEntity> _agreeRepository;
|
||||
private BbsUserManager _bbsUserManager;
|
||||
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager, ISqlSugarRepository<DiscussTopEntity> discussTopEntityRepository, ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus) : base(forumManager._discussRepository)
|
||||
|
||||
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager,
|
||||
ISqlSugarRepository<DiscussTopEntity> discussTopRepository,
|
||||
ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus,
|
||||
ISqlSugarRepository<AgreeEntity> agreeRepository) : base(forumManager._discussRepository)
|
||||
{
|
||||
_forumManager = forumManager;
|
||||
_plateEntityRepository = plateEntityRepository;
|
||||
_localEventBus = localEventBus;
|
||||
_discussTopEntityRepository = discussTopEntityRepository;
|
||||
_bbsUserManager=bbsUserManager;
|
||||
_agreeRepository = agreeRepository;
|
||||
_discussTopRepository = discussTopRepository;
|
||||
_bbsUserManager = bbsUserManager;
|
||||
}
|
||||
|
||||
private readonly ILocalEventBus _localEventBus;
|
||||
private ForumManager _forumManager { get; set; }
|
||||
|
||||
@@ -49,8 +57,6 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
private ISqlSugarRepository<PlateAggregateRoot> _plateEntityRepository { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 单查
|
||||
/// </summary>
|
||||
@@ -58,15 +64,16 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
/// <returns></returns>
|
||||
public async override Task<DiscussGetOutputDto> GetAsync(Guid id)
|
||||
{
|
||||
|
||||
//查询主题发布 浏览主题 事件,浏览数+1
|
||||
var item = await _forumManager._discussRepository._DbQueryable.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
|
||||
var item = await _forumManager._discussRepository._DbQueryable
|
||||
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
|
||||
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
|
||||
.LeftJoin<PlateAggregateRoot>((discuss, user, info, plate) => plate.Id == discuss.PlateId)
|
||||
.Select((discuss, user, info, plate) => new DiscussGetOutputDto
|
||||
{
|
||||
Id = discuss.Id,
|
||||
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null,
|
||||
x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
UserName = user.UserName,
|
||||
@@ -75,8 +82,8 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
Id = user.Id,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
Money=info.Money,
|
||||
Experience=info.Experience
|
||||
Money = info.Money,
|
||||
Experience = info.Experience
|
||||
},
|
||||
Plate = new Contracts.Dtos.Plate.PlateGetOutputDto()
|
||||
{
|
||||
@@ -85,7 +92,6 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
Code = plate.Code,
|
||||
Introduction = plate.Introduction,
|
||||
Logo = plate.Logo
|
||||
|
||||
}
|
||||
}, true)
|
||||
.SingleAsync(discuss => discuss.Id == id);
|
||||
@@ -93,7 +99,8 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
if (item is not null)
|
||||
{
|
||||
await VerifyDiscussPermissionAsync(item.Id);
|
||||
await _localEventBus.PublishAsync(new SeeDiscussEventArgs { DiscussId = item.Id, OldSeeNum = item.SeeNum });
|
||||
await _localEventBus.PublishAsync(new SeeDiscussEventArgs
|
||||
{ DiscussId = item.Id, OldSeeNum = item.SeeNum });
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -105,7 +112,8 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<PagedResultDto<DiscussGetListOutputDto>> GetListAsync([FromQuery] DiscussGetListInputVo input)
|
||||
public override async Task<PagedResultDto<DiscussGetListOutputDto>> GetListAsync(
|
||||
[FromQuery] DiscussGetListInputVo input)
|
||||
{
|
||||
//需要关联创建者用户
|
||||
RefAsync<int> total = 0;
|
||||
@@ -113,22 +121,19 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Title), x => x.Title.Contains(input.Title))
|
||||
.WhereIF(input.PlateId is not null, x => x.PlateId == input.PlateId)
|
||||
.WhereIF(input.IsTop is not null, x => x.IsTop == input.IsTop)
|
||||
.WhereIF(input.UserId is not null,x=>x.CreatorId==input.UserId)
|
||||
.WhereIF(input.UserId is not null, x => x.CreatorId == input.UserId)
|
||||
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
|
||||
.WhereIF(input.UserName is not null, (discuss, user)=>user.UserName==input.UserName!)
|
||||
|
||||
.WhereIF(input.UserName is not null, (discuss, user) => user.UserName == input.UserName!)
|
||||
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
|
||||
|
||||
.OrderByDescending(discuss => discuss.OrderNum)
|
||||
.OrderByIF(input.Type == QueryDiscussTypeEnum.New, discuss => discuss.CreationTime, OrderByType.Desc)
|
||||
.OrderByIF(input.Type == QueryDiscussTypeEnum.Host, discuss => discuss.SeeNum, OrderByType.Desc)
|
||||
.OrderByIF(input.Type == QueryDiscussTypeEnum.Suggest, discuss => discuss.AgreeNum, OrderByType.Desc)
|
||||
|
||||
.Select((discuss, user, info) => new DiscussGetListOutputDto
|
||||
{
|
||||
Id = discuss.Id,
|
||||
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
|
||||
// 优化查询,不使用子查询
|
||||
// IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
Id = user.Id,
|
||||
@@ -140,14 +145,32 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
Money = info.Money,
|
||||
Experience = info.Experience
|
||||
}
|
||||
|
||||
}, true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
var discussId = items.Select(x => x.Id);
|
||||
|
||||
//点赞字典,key为主题id,y为用户ids
|
||||
var agreeDic =
|
||||
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
|
||||
.GroupBy(x => x.DiscussId)
|
||||
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
|
||||
|
||||
//查询完主题之后,要过滤一下私有的主题信息
|
||||
items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty);
|
||||
|
||||
items?.ForEach(x => x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name);
|
||||
//等级、是否点赞赋值
|
||||
items?.ForEach(x =>
|
||||
{
|
||||
x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name;
|
||||
if (CurrentUser.Id is not null)
|
||||
{
|
||||
//默认fasle
|
||||
if (agreeDic.TryGetValue(x.Id,out var userIds))
|
||||
{
|
||||
x.IsAgree = userIds.Contains(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
});
|
||||
return new PagedResultDto<DiscussGetListOutputDto>(total, items);
|
||||
}
|
||||
|
||||
@@ -157,14 +180,16 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
/// <returns></returns>
|
||||
public async Task<List<DiscussGetListOutputDto>> GetListTopAsync()
|
||||
{
|
||||
var output = await _discussTopEntityRepository._DbQueryable.LeftJoin<DiscussAggregateRoot>((top, discuss) => top.DiscussId == discuss.Id)
|
||||
var output = await _discussTopRepository._DbQueryable
|
||||
.LeftJoin<DiscussAggregateRoot>((top, discuss) => top.DiscussId == discuss.Id)
|
||||
.LeftJoin<UserAggregateRoot>((top, discuss, user) => discuss.CreatorId == user.Id)
|
||||
.LeftJoin<BbsUserExtraInfoEntity>((top, discuss, user, info) => user.Id == info.UserId)
|
||||
.OrderByDescending(top => top.OrderNum)
|
||||
.Select((top, discuss, user, info) => new DiscussGetListOutputDto
|
||||
{
|
||||
Id = discuss.Id,
|
||||
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null,
|
||||
x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
|
||||
User = new BbsUserGetListOutputDto
|
||||
{
|
||||
Id = user.Id,
|
||||
@@ -206,6 +231,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
throw new UserFriendlyException(PlateConst.No_Exist);
|
||||
}
|
||||
|
||||
if (await _forumManager._discussRepository.IsAnyAsync(x => x.Title == input.Title))
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.Repeat);
|
||||
}
|
||||
|
||||
//如果开启了禁用创建主题
|
||||
if (plate.IsDisableCreateDiscuss == true)
|
||||
{
|
||||
@@ -233,6 +263,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.No_Exist);
|
||||
}
|
||||
|
||||
if (discuss.PermissionType == DiscussPermissionTypeEnum.Oneself)
|
||||
{
|
||||
if (discuss.CreatorId != CurrentUser.Id)
|
||||
@@ -240,9 +271,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
throw new UserFriendlyException(DiscussConst.Privacy);
|
||||
}
|
||||
}
|
||||
|
||||
if (discuss.PermissionType == DiscussPermissionTypeEnum.User)
|
||||
{
|
||||
if (discuss.CreatorId != CurrentUser.Id && !discuss.PermissionUserIds.Contains(CurrentUser.Id ?? Guid.Empty))
|
||||
if (discuss.CreatorId != CurrentUser.Id &&
|
||||
!discuss.PermissionUserIds.Contains(CurrentUser.Id ?? Guid.Empty))
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.Privacy);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
|
||||
|
||||
/// <summary>
|
||||
/// 大转盘
|
||||
/// Todo: 可放入领域层
|
||||
/// Todo: 可放入领域层,但是太简单了,不重要
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
@@ -33,12 +33,6 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
|
||||
var index = GetWheelIndex();
|
||||
var value = values[index] - 50;
|
||||
|
||||
////不存在负数钱钱
|
||||
//if (value < 0)
|
||||
//{
|
||||
// value = 0;
|
||||
//}
|
||||
|
||||
//修改钱钱,如果钱钱不足,直接会丢出去,那本次抽奖将无效
|
||||
await _localEventBus.PublishAsync(new MoneyChangeEventArgs { UserId = CurrentUser.Id!.Value, Number = value }, false);
|
||||
|
||||
@@ -47,7 +41,7 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
|
||||
|
||||
private int GetWheelIndex()
|
||||
{
|
||||
int[] probabilities = { 30, 40, 30, 15, 15, 10, 4, 3, 2, 1 };
|
||||
int[] probabilities = {5 , 30, 40, 30, 20, 10, 4, 3, 2, 1 };
|
||||
|
||||
int total = 0;
|
||||
foreach (var prob in probabilities)
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Yi.Framework.Bbs.Domain.Shared.Consts
|
||||
/// </summary>
|
||||
public class DiscussConst
|
||||
{
|
||||
public const string Repeat = "创建主题重复";
|
||||
public const string No_Exist = "传入的主题id不存在";
|
||||
|
||||
public const string Privacy = "【私密】您无该主题权限,可联系作者申请开放";
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
|
||||
public enum GoodsTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 申请类型
|
||||
/// </summary>
|
||||
Apply
|
||||
}
|
||||
@@ -19,5 +19,10 @@ namespace Yi.Framework.Bbs.Domain.Shared.Enums
|
||||
/// 广播
|
||||
/// </summary>
|
||||
Broadcast,
|
||||
|
||||
/// <summary>
|
||||
/// 钱钱
|
||||
/// </summary>
|
||||
Money
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
|
||||
public class AccessLogResetArgs
|
||||
{
|
||||
|
||||
}
|
||||
@@ -16,6 +16,13 @@ namespace Yi.Framework.Bbs.Domain.Shared.Etos
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public BbsNoticeEventArgs( NoticeTypeEnum noticeType, Guid acceptUserId, string message)
|
||||
{
|
||||
NoticeType = noticeType;
|
||||
AcceptUserId = acceptUserId;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送广播
|
||||
/// </summary>
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
|
||||
public override Guid Id { get; protected set; }
|
||||
public bool IsDeleted { get; set; }
|
||||
|
||||
[SugarColumn(Length = 500)]
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string Content { get; set; }
|
||||
|
||||
public Guid DiscussId { get; set; }
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
|
||||
[SugarTable("Discuss")]
|
||||
[SugarIndex($"index_{nameof(Title)}", nameof(Title), OrderByType.Asc)]
|
||||
[SugarIndex($"index_{nameof(PlateId)}", nameof(PlateId), OrderByType.Asc)]
|
||||
[SugarIndex($"index_{nameof(CreatorId)}", nameof(CreatorId), OrderByType.Asc)]
|
||||
[SugarIndex($"index_{nameof(CreationTime)}", nameof(CreationTime), OrderByType.Desc)]
|
||||
public class DiscussAggregateRoot : AggregateRoot<Guid>, ISoftDelete, IAuditedObject
|
||||
{
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Bbs.Domain.Entities.Shop;
|
||||
|
||||
/// <summary>
|
||||
/// 商品定义表
|
||||
/// </summary>
|
||||
[SugarTable("BbsGoods")]
|
||||
public class BbsGoodsAggregateRoot: AggregateRoot<Guid>, IHasCreationTime
|
||||
{
|
||||
/// <summary>
|
||||
/// 上架时间
|
||||
/// </summary>
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品类型
|
||||
/// </summary>
|
||||
public GoodsTypeEnum GoodsType{ get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下架时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每人限购数量
|
||||
/// </summary>
|
||||
public int LimitNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前库存数量
|
||||
/// </summary>
|
||||
public int StockNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品图片url
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Describe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编号
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所需钱钱
|
||||
/// </summary>
|
||||
public decimal NeedMoney { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所需价值
|
||||
/// </summary>
|
||||
public decimal NeedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所需积分
|
||||
/// </summary>
|
||||
public decimal NeedPoints { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
||||
namespace Yi.Framework.Bbs.Domain.Entities.Shop;
|
||||
|
||||
/// <summary>
|
||||
/// 商品申请记录表
|
||||
/// </summary>
|
||||
[SugarTable("BbsGoodsApply")]
|
||||
public class BbsGoodsApplyAggregateRoot: AggregateRoot<Guid>, IHasCreationTime
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品id
|
||||
/// </summary>
|
||||
public Guid GoodsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请时间
|
||||
/// </summary>
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申请人用户id
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 联系方式
|
||||
/// </summary>
|
||||
public string ContactInformation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.Bbs.Domain.Entities.Shop.ValueObjects;
|
||||
|
||||
public class ShippingAddress
|
||||
{
|
||||
|
||||
}
|
||||
@@ -25,23 +25,40 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
|
||||
}
|
||||
public async Task HandleEventAsync(BbsNoticeEventArgs eventData)
|
||||
{
|
||||
//离线存储
|
||||
var entity= await _repository.InsertReturnEntityAsync(new BbsNoticeAggregateRoot(eventData.NoticeType, eventData.Message, eventData.AcceptUserId));
|
||||
|
||||
|
||||
//是否需要离线存储
|
||||
bool isStore = true;
|
||||
var now = DateTime.Now;
|
||||
switch (eventData.NoticeType)
|
||||
{
|
||||
case Shared.Enums.NoticeTypeEnum.Personal:
|
||||
if (BbsNoticeHub.HubUserModels.TryGetValue(eventData.AcceptUserId.ToString(), out var hubUserModel))
|
||||
{
|
||||
_hubContext.Clients.Client(hubUserModel.ConnnectionId).SendAsync(NoticeTypeEnum.Personal.ToString(), eventData.Message,entity.CreationTime);
|
||||
_hubContext.Clients.Client(hubUserModel.ConnnectionId).SendAsync(NoticeTypeEnum.Personal.ToString(), eventData.Message,now);
|
||||
}
|
||||
break;
|
||||
case Shared.Enums.NoticeTypeEnum.Broadcast:
|
||||
_hubContext.Clients.All.SendAsync(NoticeTypeEnum.Broadcast.ToString(), eventData.Message);
|
||||
break;
|
||||
|
||||
case Shared.Enums.NoticeTypeEnum.Money:
|
||||
if (BbsNoticeHub.HubUserModels.TryGetValue(eventData.AcceptUserId.ToString(), out var hubUserModel2))
|
||||
{
|
||||
_hubContext.Clients.Client(hubUserModel2.ConnnectionId).SendAsync(NoticeTypeEnum.Money.ToString(), eventData.Message,now);
|
||||
}
|
||||
|
||||
isStore = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isStore)
|
||||
{ //离线存储
|
||||
var entity= await _repository.InsertReturnEntityAsync(new BbsNoticeAggregateRoot(eventData.NoticeType, eventData.Message, eventData.AcceptUserId){CreationTime = now});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.EventBus;
|
||||
using Volo.Abp.EventBus.Local;
|
||||
using Yi.Framework.Bbs.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Consts;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
@@ -11,9 +13,11 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
|
||||
public class MoneyChangeEventHandler : ILocalEventHandler<MoneyChangeEventArgs>, ITransientDependency
|
||||
{
|
||||
private ISqlSugarRepository<BbsUserExtraInfoEntity> _userInfoRepository;
|
||||
public MoneyChangeEventHandler(ISqlSugarRepository<BbsUserExtraInfoEntity> userInfoRepository)
|
||||
private ILocalEventBus _localEventBus;
|
||||
public MoneyChangeEventHandler(ISqlSugarRepository<BbsUserExtraInfoEntity> userInfoRepository, ILocalEventBus localEventBus)
|
||||
{
|
||||
_userInfoRepository = userInfoRepository;
|
||||
_localEventBus = localEventBus;
|
||||
}
|
||||
public async Task HandleEventAsync(MoneyChangeEventArgs eventData)
|
||||
{
|
||||
@@ -28,6 +32,9 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
|
||||
await _userInfoRepository._Db.Updateable<BbsUserExtraInfoEntity>()
|
||||
.SetColumns(it => it.Money == it.Money + eventData.Number)
|
||||
.Where(x => x.UserId == eventData.UserId).ExecuteCommandAsync();
|
||||
|
||||
|
||||
await _localEventBus.PublishAsync(new BbsNoticeEventArgs(NoticeTypeEnum.Money, eventData.UserId,eventData.Number.ToString()), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Volo.Abp.Domain.Services;
|
||||
|
||||
namespace Yi.Framework.Bbs.Domain.Managers;
|
||||
|
||||
/// <summary>
|
||||
/// bbs商品领域
|
||||
/// </summary>
|
||||
public class BbsShopManager: DomainService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -78,7 +78,12 @@ namespace Yi.Framework.ChatHub.Domain.Managers
|
||||
public async Task<ChatOnlineUserCacheItem?> GetUserAsync(Guid userId)
|
||||
{
|
||||
var key = new ChatOnlineUserCacheKey(CacheKeyPrefix);
|
||||
var cacheUser = System.Text.Json.JsonSerializer.Deserialize<ChatOnlineUserCacheItem>(await RedisClient.HGetAsync(key.GetKey(), key.GetField(userId)));
|
||||
var cacheUserOrNull= await RedisClient.HGetAsync(key.GetKey(), key.GetField(userId));
|
||||
if (cacheUserOrNull is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var cacheUser = System.Text.Json.JsonSerializer.Deserialize<ChatOnlineUserCacheItem>(cacheUserOrNull);
|
||||
return cacheUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
{
|
||||
public Guid Uuid { get; set; } = Guid.Empty;
|
||||
public byte[] Img { get; set; }
|
||||
|
||||
public bool IsEnableCaptcha { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,9 @@
|
||||
public class PhoneCaptchaImageDto
|
||||
{
|
||||
public string Phone { get; set; }
|
||||
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 唯一标识码
|
||||
@@ -29,5 +29,11 @@
|
||||
/// 验证码
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string? Nick{ get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Account;
|
||||
|
||||
public class RetrievePasswordDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 唯一标识码
|
||||
/// </summary>
|
||||
public string? Uuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 电话
|
||||
/// </summary>
|
||||
public long Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证码
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
public class UpdateIconDto
|
||||
{
|
||||
public string? Icon { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
|
||||
/// </summary>
|
||||
public class DictionaryCreateInputVo
|
||||
{
|
||||
public int OrderNum { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public string? ListClass { get; set; }
|
||||
public string? CssClass { get; set; }
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
|
||||
{
|
||||
public class DictionaryGetListOutputDto : EntityDto<Guid>
|
||||
{
|
||||
public int OrderNum { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public Guid? CreatorId { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
|
||||
{
|
||||
public class DictionaryGetOutputDto : EntityDto<Guid>
|
||||
{
|
||||
public int OrderNum { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public Guid? CreatorId { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
|
||||
{
|
||||
public class DictionaryUpdateInputVo
|
||||
{
|
||||
public int OrderNum { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public string? ListClass { get; set; }
|
||||
public string? CssClass { get; set; }
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
/// </summary>
|
||||
public class MenuCreateInputVo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid? Id { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public Guid? CreatorId { get; set; }
|
||||
public bool State { get; set; }
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
public string? Remark { get; set; }
|
||||
public string? Component { get; set; }
|
||||
public string? Query { get; set; }
|
||||
|
||||
public string? RouterName { get; set; }
|
||||
public int OrderNum { get; set; }
|
||||
//public List<MenuEntity>? Children { get; set; }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
public string? RouterName { get; set; }
|
||||
|
||||
//public List<MenuEntity>? Children { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Post
|
||||
public class PostUpdateInputVo
|
||||
{
|
||||
public bool? State { get; set; }
|
||||
public int OrderNum { get; set; }
|
||||
public string PostCode { get; set; }
|
||||
public string PostName { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
|
||||
@@ -6,16 +6,14 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Quartz;
|
||||
using Quartz.Logging;
|
||||
using Volo.Abp.BackgroundWorkers.Quartz;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Options;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Jobs
|
||||
{
|
||||
public class BackupDataBaseJob : QuartzBackgroundWorkerBase
|
||||
public class BackupDataBaseJob: HangfireBackgroundWorkerBase
|
||||
{
|
||||
private ISqlSugarDbContext _dbContext;
|
||||
private IOptions<RbacOptions> _options;
|
||||
@@ -24,13 +22,12 @@ namespace Yi.Framework.Rbac.Application.Jobs
|
||||
|
||||
_options = options;
|
||||
_dbContext = dbContext;
|
||||
JobDetail = JobBuilder.Create<BackupDataBaseJob>().WithIdentity(nameof(BackupDataBaseJob)).Build();
|
||||
|
||||
RecurringJobId = "数据库备份";
|
||||
//每天00点与24点进行备份
|
||||
Trigger = TriggerBuilder.Create().WithIdentity(nameof(BackupDataBaseJob)).WithCronSchedule("0 0 0,12 * * ? ").Build();
|
||||
//Trigger = TriggerBuilder.Create().WithIdentity(nameof(BackupDataBaseJob)).WithSimpleSchedule(x=>x.WithIntervalInSeconds(10)).Build();
|
||||
CronExpression = "0 0 0,12 * * ? ";
|
||||
}
|
||||
public override Task Execute(IJobExecutionContext context)
|
||||
public override Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
if (_options.Value.EnableDataBaseBackup)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Lazy.Captcha.Core;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -27,6 +27,7 @@ using Yi.Framework.Rbac.Domain.Repositories;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Caches;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Enums;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Etos;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Options;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
@@ -44,6 +45,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
private IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache;
|
||||
private UserManager _userManager;
|
||||
private IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AccountService(IUserRepository userRepository,
|
||||
ICurrentUser currentUser,
|
||||
IAccountManager accountManager,
|
||||
@@ -80,12 +82,12 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
/// 校验图片登录验证码,无需和账号绑定
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
private void ValidationImageCaptcha(LoginInputVo input)
|
||||
private void ValidationImageCaptcha(string? uuid,string? code )
|
||||
{
|
||||
if (_rbacOptions.EnableCaptcha)
|
||||
{
|
||||
//登录不想要验证码 ,可不校验
|
||||
if (!_captcha.Validate(input.Uuid, input.Code))
|
||||
if (!_captcha.Validate(uuid, code))
|
||||
{
|
||||
throw new UserFriendlyException("验证码错误");
|
||||
}
|
||||
@@ -107,7 +109,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
}
|
||||
|
||||
//校验验证码
|
||||
ValidationImageCaptcha(input);
|
||||
ValidationImageCaptcha(input.Uuid,input.Code);
|
||||
|
||||
UserAggregateRoot user = new();
|
||||
//校验
|
||||
@@ -155,37 +157,66 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
var uuid = _guidGenerator.Create();
|
||||
var captcha = _captcha.Generate(uuid.ToString());
|
||||
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid };
|
||||
var enableCaptcha = _rbacOptions.EnableCaptcha;
|
||||
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid,IsEnableCaptcha= enableCaptcha };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证电话号码
|
||||
/// </summary>
|
||||
/// <param name="str_handset"></param>
|
||||
private async Task ValidationPhone(string str_handset)
|
||||
private async Task ValidationPhone(string phone)
|
||||
{
|
||||
var res = Regex.IsMatch(str_handset, @"^\d{11}$");
|
||||
var res = Regex.IsMatch(phone, @"^\d{11}$");
|
||||
if (res == false)
|
||||
{
|
||||
throw new UserFriendlyException("手机号码格式错误!请检查");
|
||||
}
|
||||
|
||||
if (await _userRepository.IsAnyAsync(x => x.Phone.ToString() == str_handset))
|
||||
{
|
||||
throw new UserFriendlyException("该手机号已被注册!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册 手机验证码
|
||||
/// 手机验证码-注册
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("account/captcha-phone")]
|
||||
[AllowAnonymous]
|
||||
public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input)
|
||||
{
|
||||
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手机验证码-找回密码
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("account/captcha-phone/repassword")]
|
||||
public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input)
|
||||
{
|
||||
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手机验证码-需通过图形验证码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
public async Task<object> PostCaptchaPhone(PhoneCaptchaImageDto input)
|
||||
private async Task<object> PostCaptchaPhoneAsync(ValidationPhoneTypeEnum validationPhoneType,
|
||||
PhoneCaptchaImageDto input)
|
||||
{
|
||||
//验证uuid 和 验证码
|
||||
ValidationImageCaptcha(input.Uuid,input.Code);
|
||||
|
||||
await ValidationPhone(input.Phone);
|
||||
var value = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(input.Phone));
|
||||
|
||||
//注册的手机号验证,是不能已经注册过的
|
||||
if (validationPhoneType == ValidationPhoneTypeEnum.Register&& await _userRepository.IsAnyAsync(x => x.Phone.ToString() == input.Phone))
|
||||
{
|
||||
throw new UserFriendlyException("该手机号已被注册!");
|
||||
}
|
||||
|
||||
var value = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone));
|
||||
|
||||
//防止暴刷
|
||||
if (value is not null)
|
||||
@@ -200,7 +231,8 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
var uuid = Guid.NewGuid();
|
||||
await _aliyunManger.SendSmsAsync(input.Phone, code);
|
||||
|
||||
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(input.Phone), new CaptchaPhoneCacheItem(code),
|
||||
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone),
|
||||
new CaptchaPhoneCacheItem(code),
|
||||
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
|
||||
return new
|
||||
{
|
||||
@@ -211,19 +243,43 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
/// <summary>
|
||||
/// 校验电话验证码,需要与电话号码绑定
|
||||
/// </summary>
|
||||
private async Task ValidationPhoneCaptchaAsync(RegisterDto input)
|
||||
private async Task ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum validationPhoneType, long phone,
|
||||
string code)
|
||||
{
|
||||
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(input.Phone.ToString()));
|
||||
if (item is not null && item.Code.Equals($"{input.Code}"))
|
||||
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
|
||||
if (item is not null && item.Code.Equals($"{code}"))
|
||||
{
|
||||
//成功,需要清空
|
||||
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(input.Phone.ToString()));
|
||||
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(validationPhoneType, code.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new UserFriendlyException("验证码错误");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 找回密码
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[UnitOfWork]
|
||||
public async Task<string> PostRetrievePasswordAsync(RetrievePasswordDto input)
|
||||
{
|
||||
//校验验证码,根据电话号码获取 value,比对验证码已经uuid
|
||||
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code);
|
||||
|
||||
var entity = await _userRepository.GetFirstAsync(x => x.Phone == input.Phone);
|
||||
if (entity is null)
|
||||
{
|
||||
throw new UserFriendlyException("该手机号码未注册");
|
||||
}
|
||||
|
||||
await _accountManager.RestPasswordAsync(entity.Id, input.Password);
|
||||
|
||||
return entity.UserName;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册,需要验证码通过
|
||||
/// </summary>
|
||||
@@ -241,16 +297,16 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
if (_rbacOptions.EnableCaptcha)
|
||||
{
|
||||
//校验验证码,根据电话号码获取 value,比对验证码已经uuid
|
||||
await ValidationPhoneCaptchaAsync(input);
|
||||
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.Register, input.Phone, input.Code);
|
||||
}
|
||||
|
||||
//注册领域逻辑
|
||||
await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone);
|
||||
await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Nick);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 查询已登录的账户信息,已缓存
|
||||
/// 查询已登录的账户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Route("account")]
|
||||
@@ -270,9 +326,6 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户的前端路由
|
||||
/// 支持ruoyi/pure
|
||||
@@ -280,7 +333,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[Route("account/Vue3Router/{routerType?}")]
|
||||
public async Task<object> GetVue3Router([FromRoute]string? routerType)
|
||||
public async Task<object> GetVue3Router([FromRoute] string? routerType)
|
||||
{
|
||||
var userId = _currentUser.Id;
|
||||
if (_currentUser.Id is null)
|
||||
@@ -298,18 +351,19 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
}
|
||||
|
||||
object output = null;
|
||||
if (routerType is null ||routerType=="ruoyi")
|
||||
if (routerType is null || routerType == "ruoyi")
|
||||
{
|
||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||
output =
|
||||
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3RuoYiRouterBuild();
|
||||
}
|
||||
else if (routerType =="pure")
|
||||
else if (routerType == "pure")
|
||||
{
|
||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||
output =
|
||||
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3PureRouterBuild();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -379,7 +433,10 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
/// <returns></returns>
|
||||
public async Task<bool> UpdateIconAsync(UpdateIconDto input)
|
||||
{
|
||||
var entity = await _userRepository.GetByIdAsync(_currentUser.Id);
|
||||
Guid userId=input.UserId == null?_currentUser.GetId():input.UserId.Value;
|
||||
|
||||
var entity = await _userRepository.GetByIdAsync(userId);
|
||||
|
||||
if (entity.Icon == input.Icon)
|
||||
{
|
||||
return false;
|
||||
@@ -390,7 +447,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
|
||||
//发布更新头像任务事件
|
||||
await this.LocalEventBus.PublishAsync(
|
||||
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, _currentUser.GetId()), false);
|
||||
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, userId), false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,15 @@ namespace Yi.Framework.Rbac.Application.Services.Authentication
|
||||
/// </summary>
|
||||
public class AuthService : YiCrudAppService<AuthAggregateRoot, AuthOutputDto, Guid, AuthGetListInput>
|
||||
{
|
||||
private HttpContext HttpContext { get; set; }
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
private ILogger<AuthService> _logger;
|
||||
private ISqlSugarRepository<AuthAggregateRoot, Guid> _repository;
|
||||
private IAccountManager _accountManager;
|
||||
public AuthService(IAccountManager accountManager, IHttpContextAccessor httpContextAccessor, ILogger<AuthService> logger, ISqlSugarRepository<AuthAggregateRoot, Guid> repository) : base(repository)
|
||||
{
|
||||
_logger = logger;
|
||||
HttpContext = httpContextAccessor.HttpContext ?? throw new ApplicationException("未注册Http");
|
||||
//可能为空
|
||||
HttpContext = httpContextAccessor.HttpContext;
|
||||
_repository = repository;
|
||||
_accountManager = accountManager;
|
||||
}
|
||||
@@ -79,6 +80,10 @@ namespace Yi.Framework.Rbac.Application.Services.Authentication
|
||||
|
||||
private async Task<(string, string)> GetOpenIdAndNameAsync(string scheme)
|
||||
{
|
||||
if (HttpContext is null)
|
||||
{
|
||||
throw new AggregateException("HttpContext 参数为空");
|
||||
}
|
||||
var authenticateResult = await HttpContext.AuthenticateAsync(scheme);
|
||||
if (!authenticateResult.Succeeded)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Config;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services
|
||||
@@ -12,10 +13,12 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
/// <summary>
|
||||
/// Config服务实现
|
||||
/// </summary>
|
||||
public class ConfigService : YiCrudAppService<ConfigAggregateRoot, ConfigGetOutputDto, ConfigGetListOutputDto, Guid, ConfigGetListInputVo, ConfigCreateInputVo, ConfigUpdateInputVo>,
|
||||
public class ConfigService : YiCrudAppService<ConfigAggregateRoot, ConfigGetOutputDto, ConfigGetListOutputDto, Guid,
|
||||
ConfigGetListInputVo, ConfigCreateInputVo, ConfigUpdateInputVo>,
|
||||
IConfigService
|
||||
{
|
||||
private ISqlSugarRepository<ConfigAggregateRoot, Guid> _repository;
|
||||
|
||||
public ConfigService(ISqlSugarRepository<ConfigAggregateRoot, Guid> repository) : base(repository)
|
||||
{
|
||||
_repository = repository;
|
||||
@@ -30,11 +33,33 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.ConfigKey), x => x.ConfigKey.Contains(input.ConfigKey!))
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.ConfigKey),
|
||||
x => x.ConfigKey.Contains(input.ConfigKey!))
|
||||
.WhereIF(!string.IsNullOrEmpty(input.ConfigName), x => x.ConfigName!.Contains(input.ConfigName!))
|
||||
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||
.WhereIF(input.StartTime is not null && input.EndTime is not null,
|
||||
x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<ConfigGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
protected override async Task CheckCreateInputDtoAsync(ConfigCreateInputVo input)
|
||||
{
|
||||
var isExist =
|
||||
await _repository.IsAnyAsync(x => x.ConfigKey == input.ConfigKey);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(ConfigConst.Exist);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task CheckUpdateInputDtoAsync(ConfigAggregateRoot entity, ConfigUpdateInputVo input)
|
||||
{
|
||||
var isExist = await _repository._DbQueryable.Where(x => x.Id != entity.Id)
|
||||
.AnyAsync(x => x.ConfigKey == input.ConfigKey);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(ConfigConst.Exist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,11 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
public override async Task<PagedResultDto<DictionaryGetListOutputDto>> GetListAsync(DictionaryGetListInputVo input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var entities = await _repository._DbQueryable.WhereIF(input.DictType is not null, x => x.DictType == input.DictType)
|
||||
var entities = await _repository._DbQueryable
|
||||
.WhereIF(input.DictType is not null, x => x.DictType == input.DictType)
|
||||
.WhereIF(input.DictLabel is not null, x => x.DictLabel!.Contains(input.DictLabel!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.OrderByDescending(x => x.OrderNum)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<DictionaryGetListOutputDto>
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.DictionaryType;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services
|
||||
@@ -22,7 +23,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async override Task<PagedResultDto<DictionaryTypeGetListOutputDto>> GetListAsync(DictionaryTypeGetListInputVo input)
|
||||
public override async Task<PagedResultDto<DictionaryTypeGetListOutputDto>> GetListAsync(DictionaryTypeGetListInputVo input)
|
||||
{
|
||||
|
||||
RefAsync<int> total = 0;
|
||||
@@ -38,5 +39,25 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
Items = await MapToGetListOutputDtosAsync(entities)
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task CheckCreateInputDtoAsync(DictionaryTypeCreateInputVo input)
|
||||
{
|
||||
var isExist =
|
||||
await _repository.IsAnyAsync(x => x.DictType == input.DictType);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(DictionaryConst.Exist);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task CheckUpdateInputDtoAsync(DictionaryTypeAggregateRoot entity, DictionaryTypeUpdateInputVo input)
|
||||
{
|
||||
var isExist = await _repository._DbQueryable.Where(x => x.Id != entity.Id)
|
||||
.AnyAsync(x => x.DictType == input.DictType);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(DictionaryConst.Exist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new UserFriendlyException("文件不存在",code:"404");
|
||||
return new NotFoundResult();
|
||||
// throw new UserFriendlyException("文件不存在",code:"404");
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +67,6 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
// path = $"wwwroot/{FileTypeEnum.Thumbnail}/{file.Id}{Path.GetExtension(file.FileName)}";
|
||||
//}
|
||||
//路径为: 文件路径/文件id+文件扩展名
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new UserFriendlyException("本地文件不存在", "404");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Yi.Framework.Rbac.Application.Services.Monitor
|
||||
[HttpGet("monitor-server/info")]
|
||||
public object GetInfo()
|
||||
{
|
||||
int cpuNum = Environment.ProcessorCount;
|
||||
|
||||
string computerName = Environment.MachineName;
|
||||
string osName = RuntimeInformation.OSDescription;
|
||||
string osArch = RuntimeInformation.OSArchitecture.ToString();
|
||||
@@ -35,9 +35,10 @@ namespace Yi.Framework.Rbac.Application.Services.Monitor
|
||||
string programRunTime = DateTimeHelper.FormatTime(long.Parse((DateTime.Now - programStartTime).TotalMilliseconds.ToString().Split('.')[0]));
|
||||
var data = new
|
||||
{
|
||||
cpu = ComputerHelper.GetComputerInfo(),
|
||||
memory = ComputerHelper.GetMemoryMetrics(),
|
||||
cpu = ComputerHelper.GetCPUMetrics(),
|
||||
disk = ComputerHelper.GetDiskInfos(),
|
||||
sys = new { cpuNum, computerName, osName, osArch, serverIP, runTime = sysRunTime },
|
||||
sys = new {computerName, osName, osArch, serverIP, runTime = sysRunTime },
|
||||
app = new
|
||||
{
|
||||
name = _hostEnvironment.EnvironmentName,
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Quartz;
|
||||
using Quartz.Impl.Matchers;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Timing;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Task;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services.Monitor
|
||||
{
|
||||
public class TaskService : ApplicationService, ITaskService
|
||||
{
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly IClock _clock;
|
||||
public TaskService(ISchedulerFactory schedulerFactory, IClock clock)
|
||||
{
|
||||
_clock = clock;
|
||||
_schedulerFactory = schedulerFactory;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 单查job
|
||||
/// </summary>
|
||||
/// <param name="jobId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("task/{jobId}")]
|
||||
public async Task<TaskGetOutput> GetAsync([FromRoute] string jobId)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
|
||||
var jobDetail = await scheduler.GetJobDetail(new JobKey(jobId));
|
||||
var trigger = (await scheduler.GetTriggersOfJob(new JobKey(jobId))).First();
|
||||
//状态
|
||||
var state = await scheduler.GetTriggerState(trigger.Key);
|
||||
|
||||
|
||||
var output = new TaskGetOutput
|
||||
{
|
||||
JobId = jobDetail.Key.Name,
|
||||
GroupName = jobDetail.Key.Group,
|
||||
JobType = jobDetail.JobType.Name,
|
||||
Properties = Newtonsoft.Json.JsonConvert.SerializeObject(jobDetail.JobDataMap),
|
||||
Concurrent = !jobDetail.ConcurrentExecutionDisallowed,
|
||||
Description = jobDetail.Description,
|
||||
LastRunTime = _clock.Normalize(trigger.GetPreviousFireTimeUtc()?.DateTime ?? DateTime.MinValue),
|
||||
NextRunTime = _clock.Normalize(trigger.GetNextFireTimeUtc()?.DateTime ?? DateTime.MinValue),
|
||||
AssemblyName = jobDetail.JobType.Assembly.GetName().Name,
|
||||
Status = state.ToString()
|
||||
};
|
||||
|
||||
if (trigger is ISimpleTrigger simple)
|
||||
{
|
||||
output.TriggerArgs = Math.Round(simple.RepeatInterval.TotalMinutes, 2).ToString() + "分钟";
|
||||
output.Type = JobTypeEnum.Millisecond;
|
||||
output.Millisecond = simple.RepeatInterval.TotalMilliseconds;
|
||||
}
|
||||
else if (trigger is ICronTrigger cron)
|
||||
{
|
||||
output.TriggerArgs = cron.CronExpressionString!;
|
||||
output.Type = JobTypeEnum.Cron;
|
||||
output.Cron = cron.CronExpressionString;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多查job
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<PagedResultDto<TaskGetListOutput>> GetListAsync([FromQuery] TaskGetListInput input)
|
||||
{
|
||||
var items = new List<TaskGetOutput>();
|
||||
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
|
||||
var groups = await scheduler.GetJobGroupNames();
|
||||
|
||||
foreach (var groupName in groups)
|
||||
{
|
||||
foreach (var jobKey in await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)))
|
||||
{
|
||||
string jobName = jobKey.Name;
|
||||
string jobGroup = jobKey.Group;
|
||||
var triggers = (await scheduler.GetTriggersOfJob(jobKey)).First();
|
||||
items.Add(await GetAsync(jobName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var output = items.Skip((input.SkipCount - 1) * input.MaxResultCount).Take(input.MaxResultCount)
|
||||
.OrderByDescending(x => x.LastRunTime)
|
||||
.ToList();
|
||||
return new PagedResultDto<TaskGetListOutput>(items.Count(), output.Adapt<List<TaskGetListOutput>>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建job
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateAsync(TaskCreateInput input)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
|
||||
//设置启动时执行一次,然后最大只执行一次
|
||||
|
||||
|
||||
//jobBuilder
|
||||
var jobClassType = Assembly.Load(input.AssemblyName).GetTypes().Where(x => x.Name == input.JobType).FirstOrDefault();
|
||||
|
||||
if (jobClassType is null)
|
||||
{
|
||||
throw new UserFriendlyException($"程序集:{input.AssemblyName},{input.JobType} 不存在");
|
||||
}
|
||||
|
||||
var jobBuilder = JobBuilder.Create(jobClassType).WithIdentity(new JobKey(input.JobId, input.GroupName))
|
||||
.WithDescription(input.Description);
|
||||
if (!input.Concurrent)
|
||||
{
|
||||
jobBuilder.DisallowConcurrentExecution();
|
||||
}
|
||||
|
||||
//triggerBuilder
|
||||
TriggerBuilder triggerBuilder = null;
|
||||
switch (input.Type)
|
||||
{
|
||||
case JobTypeEnum.Cron:
|
||||
triggerBuilder =
|
||||
TriggerBuilder.Create()
|
||||
.WithCronSchedule(input.Cron);
|
||||
|
||||
|
||||
|
||||
|
||||
break;
|
||||
case JobTypeEnum.Millisecond:
|
||||
triggerBuilder =
|
||||
TriggerBuilder.Create().StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithInterval(TimeSpan.FromMilliseconds(input.Millisecond ?? 10000))
|
||||
.RepeatForever()
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
//作业计划,单个jobBuilder与多个triggerBuilder组合
|
||||
await scheduler.ScheduleJob(jobBuilder.Build(), triggerBuilder.Build());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除job
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteAsync(IEnumerable<string> id)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
await scheduler.DeleteJobs(id.Select(x => new JobKey(x)).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停job
|
||||
/// </summary>
|
||||
/// <param name="jobId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task PauseAsync(string jobId)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
await scheduler.PauseJob(new JobKey(jobId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始job
|
||||
/// </summary>
|
||||
/// <param name="jobId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task StartAsync(string jobId)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
await scheduler.ResumeJob(new JobKey(jobId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新job
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task UpdateAsync(string id, TaskUpdateInput input)
|
||||
{
|
||||
await DeleteAsync(new List<string>() { id });
|
||||
await CreateAsync(input.Adapt<TaskCreateInput>());
|
||||
}
|
||||
|
||||
[HttpPost("task/run-once/{id}")]
|
||||
public async Task RunOnceAsync([FromRoute] string id)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
var jobDetail = await scheduler.GetJobDetail(new JobKey(id));
|
||||
|
||||
var jobBuilder = JobBuilder.Create(jobDetail.JobType).WithIdentity(new JobKey(Guid.NewGuid().ToString()));
|
||||
//设置启动时执行一次,然后最大只执行一次
|
||||
var trigger = TriggerBuilder.Create().WithIdentity(Guid.NewGuid().ToString()).StartNow()
|
||||
.WithSimpleSchedule(x => x
|
||||
.WithIntervalInHours(1)
|
||||
.WithRepeatCount(1))
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(jobBuilder.Build(), trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,6 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
return new PagedResultDto<NoticeGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 发送在线消息
|
||||
/// </summary>
|
||||
|
||||
@@ -20,10 +20,12 @@ namespace Yi.Framework.Rbac.Application.Services.RecordLog
|
||||
public override async Task<PagedResultDto<LoginLogGetListOutputDto>> GetListAsync(LoginLogGetListInputVo input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
//if (input.Sorting.IsNullOrWhiteSpace())
|
||||
// input.Sorting = $"{nameof(LoginLogAggregateRoot.CreationTime)} Desc";
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.LoginIp), x => x.LoginIp.Contains(input.LoginIp!))
|
||||
.WhereIF(!string.IsNullOrEmpty(input.LoginUser), x => x.LoginUser!.Contains(input.LoginUser!))
|
||||
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||
.OrderByDescending(it => it.CreationTime) //降序
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<LoginLogGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
@@ -24,9 +24,12 @@ namespace Yi.Framework.Rbac.Application.Services.RecordLog
|
||||
public override async Task<PagedResultDto<OperationLogGetListOutputDto>> GetListAsync(OperationLogGetListInputVo input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
//if (input.Sorting.IsNullOrWhiteSpace())
|
||||
// input.Sorting = $"{nameof(OperationLogEntity.CreationTime)} Desc";
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.OperUser), x => x.OperUser.Contains(input.OperUser!))
|
||||
.WhereIF(input.OperType is not null, x => x.OperType == input.OperType)
|
||||
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||
.OrderByDescending(it => it.CreationTime) //降序
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<OperationLogGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Dept;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Repositories;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services.System
|
||||
{
|
||||
/// <summary>
|
||||
/// Dept服务实现
|
||||
/// </summary>
|
||||
public class DeptService : YiCrudAppService<DeptAggregateRoot, DeptGetOutputDto, DeptGetListOutputDto, Guid, DeptGetListInputVo, DeptCreateInputVo, DeptUpdateInputVo>, IDeptService
|
||||
public class DeptService : YiCrudAppService<DeptAggregateRoot, DeptGetOutputDto, DeptGetListOutputDto, Guid,
|
||||
DeptGetListInputVo, DeptCreateInputVo, DeptUpdateInputVo>, IDeptService
|
||||
{
|
||||
private IDeptRepository _deptRepository;
|
||||
public DeptService(IDeptRepository deptRepository) : base(deptRepository)
|
||||
{ _deptRepository = deptRepository; }
|
||||
private IDeptRepository _repository;
|
||||
|
||||
public DeptService(IDeptRepository repository) : base(repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
[RemoteService(false)]
|
||||
public async Task<List<Guid>> GetChildListAsync(Guid deptId)
|
||||
{
|
||||
return await _deptRepository.GetChildListAsync(deptId);
|
||||
return await _repository.GetChildListAsync(deptId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +35,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
//[Route("{roleId}")]
|
||||
public async Task<List<DeptGetListOutputDto>> GetRoleIdAsync(Guid roleId)
|
||||
{
|
||||
var entities = await _deptRepository.GetListRoleIdAsync(roleId);
|
||||
var entities = await _repository.GetListRoleIdAsync(roleId);
|
||||
return await MapToGetListOutputDtosAsync(entities);
|
||||
}
|
||||
|
||||
@@ -44,7 +47,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
public override async Task<PagedResultDto<DeptGetListOutputDto>> GetListAsync(DeptGetListInputVo input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var entities = await _deptRepository._DbQueryable
|
||||
var entities = await _repository._DbQueryable
|
||||
.WhereIF(!string.IsNullOrEmpty(input.DeptName), u => u.DeptName.Contains(input.DeptName!))
|
||||
.WhereIF(input.State is not null, u => u.State == input.State)
|
||||
.OrderBy(u => u.OrderNum, OrderByType.Asc)
|
||||
@@ -55,5 +58,25 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
TotalCount = total
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task CheckCreateInputDtoAsync(DeptCreateInputVo input)
|
||||
{
|
||||
var isExist =
|
||||
await _repository.IsAnyAsync(x => x.DeptCode == input.DeptCode);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(DeptConst.Exist);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task CheckUpdateInputDtoAsync(DeptAggregateRoot entity, DeptUpdateInputVo input)
|
||||
{
|
||||
var isExist = await _repository._DbQueryable.Where(x => x.Id != entity.Id)
|
||||
.AnyAsync(x => x.DeptCode == input.DeptCode);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(DeptConst.Exist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Menu;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services.System
|
||||
@@ -23,15 +23,12 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
|
||||
public override async Task<PagedResultDto<MenuGetListOutputDto>> GetListAsync(MenuGetListInputVo input)
|
||||
{
|
||||
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.MenuName), x => x.MenuName.Contains(input.MenuName!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.Where(x=>x.MenuSource==input.MenuSource)
|
||||
.OrderByDescending(x => x.OrderNum)
|
||||
.ToListAsync();
|
||||
//.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<MenuGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
@@ -46,15 +43,5 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
|
||||
return await MapToGetListOutputDtosAsync(entities);
|
||||
}
|
||||
|
||||
public override Task<MenuGetOutputDto> UpdateAsync(Guid id, MenuUpdateInputVo input)
|
||||
{
|
||||
return base.UpdateAsync(id, input);
|
||||
}
|
||||
|
||||
public override Task<MenuGetOutputDto> CreateAsync(MenuCreateInputVo input)
|
||||
{
|
||||
return base.CreateAsync(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Post;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services.System
|
||||
@@ -12,10 +12,12 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <summary>
|
||||
/// Post服务实现
|
||||
/// </summary>
|
||||
public class PostService : YiCrudAppService<PostAggregateRoot, PostGetOutputDto, PostGetListOutputDto, Guid, PostGetListInputVo, PostCreateInputVo, PostUpdateInputVo>,
|
||||
public class PostService : YiCrudAppService<PostAggregateRoot, PostGetOutputDto, PostGetListOutputDto, Guid,
|
||||
PostGetListInputVo, PostCreateInputVo, PostUpdateInputVo>,
|
||||
IPostService
|
||||
{
|
||||
private readonly ISqlSugarRepository<PostAggregateRoot, Guid> _repository;
|
||||
|
||||
public PostService(ISqlSugarRepository<PostAggregateRoot, Guid> repository) : base(repository)
|
||||
{
|
||||
_repository = repository;
|
||||
@@ -25,10 +27,32 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.PostName), x => x.PostName.Contains(input.PostName!))
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.PostName),
|
||||
x => x.PostName.Contains(input.PostName!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.OrderByDescending(x => x.OrderNum)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<PostGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
protected override async Task CheckCreateInputDtoAsync(PostCreateInputVo input)
|
||||
{
|
||||
var isExist =
|
||||
await _repository.IsAnyAsync(x => x.PostCode == input.PostCode);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(PostConst.Exist);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task CheckUpdateInputDtoAsync(PostAggregateRoot entity, PostUpdateInputVo input)
|
||||
{
|
||||
var isExist = await _repository._DbQueryable.Where(x => x.Id != entity.Id)
|
||||
.AnyAsync(x => x.PostCode == input.PostCode);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(RoleConst.Exist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Managers;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Enums;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
@@ -19,10 +20,13 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <summary>
|
||||
/// Role服务实现
|
||||
/// </summary>
|
||||
public class RoleService : YiCrudAppService<RoleAggregateRoot, RoleGetOutputDto, RoleGetListOutputDto, Guid, RoleGetListInputVo, RoleCreateInputVo, RoleUpdateInputVo>,
|
||||
public class RoleService : YiCrudAppService<RoleAggregateRoot, RoleGetOutputDto, RoleGetListOutputDto, Guid,
|
||||
RoleGetListInputVo, RoleCreateInputVo, RoleUpdateInputVo>,
|
||||
IRoleService
|
||||
{
|
||||
public RoleService(RoleManager roleManager, ISqlSugarRepository<RoleDeptEntity> roleDeptRepository, ISqlSugarRepository<UserRoleEntity> userRoleRepository, ISqlSugarRepository<RoleAggregateRoot, Guid> repository) : base(repository)
|
||||
public RoleService(RoleManager roleManager, ISqlSugarRepository<RoleDeptEntity> roleDeptRepository,
|
||||
ISqlSugarRepository<UserRoleEntity> userRoleRepository,
|
||||
ISqlSugarRepository<RoleAggregateRoot, Guid> repository) : base(repository)
|
||||
{
|
||||
(_roleManager, _roleDeptRepository, _userRoleRepository, _repository) =
|
||||
(roleManager, roleDeptRepository, userRoleRepository, repository);
|
||||
@@ -41,20 +45,22 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
if (input.DataScope == DataScopeEnum.CUSTOM)
|
||||
{
|
||||
await _roleDeptRepository.DeleteAsync(x => x.RoleId == input.RoleId);
|
||||
var insertEntities = input.DeptIds.Select(x => new RoleDeptEntity { DeptId = x, RoleId = input.RoleId }).ToList();
|
||||
var insertEntities = input.DeptIds.Select(x => new RoleDeptEntity { DeptId = x, RoleId = input.RoleId })
|
||||
.ToList();
|
||||
await _roleDeptRepository.InsertRangeAsync(insertEntities);
|
||||
}
|
||||
|
||||
var entity = new RoleAggregateRoot() { DataScope = input.DataScope };
|
||||
EntityHelper.TrySetId(entity, () => input.RoleId);
|
||||
await _repository._Db.Updateable(entity).UpdateColumns(x => x.DataScope).ExecuteCommandAsync();
|
||||
|
||||
}
|
||||
|
||||
public override async Task<PagedResultDto<RoleGetListOutputDto>> GetListAsync(RoleGetListInputVo input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.RoleCode), x => x.RoleCode.Contains(input.RoleCode!))
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.RoleCode),
|
||||
x => x.RoleCode.Contains(input.RoleCode!))
|
||||
.WhereIF(!string.IsNullOrEmpty(input.RoleName), x => x.RoleName.Contains(input.RoleName!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
@@ -68,15 +74,16 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <returns></returns>
|
||||
public override async Task<RoleGetOutputDto> CreateAsync(RoleCreateInputVo input)
|
||||
{
|
||||
RoleGetOutputDto outputDto;
|
||||
//using (var uow = _unitOfWorkManager.CreateContext())
|
||||
//{
|
||||
var isExist =
|
||||
await _repository.IsAnyAsync(x => x.RoleCode == input.RoleCode || x.RoleName == input.RoleName);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(RoleConst.Exist);
|
||||
}
|
||||
|
||||
var entity = await MapToEntityAsync(input);
|
||||
await _repository.InsertAsync(entity);
|
||||
outputDto = await MapToGetOutputDtoAsync(entity);
|
||||
await _roleManager.GiveRoleSetMenuAsync(new List<Guid> { entity.Id }, input.MenuIds);
|
||||
// uow.Commit();
|
||||
//}
|
||||
var outputDto = await MapToGetOutputDtoAsync(entity);
|
||||
|
||||
return outputDto;
|
||||
}
|
||||
@@ -89,18 +96,20 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <returns></returns>
|
||||
public override async Task<RoleGetOutputDto> UpdateAsync(Guid id, RoleUpdateInputVo input)
|
||||
{
|
||||
var dto = new RoleGetOutputDto();
|
||||
//using (var uow = _unitOfWorkManager.CreateContext())
|
||||
//{
|
||||
var entity = await _repository.GetByIdAsync(id);
|
||||
|
||||
var isExist = await _repository._DbQueryable.Where(x => x.Id != entity.Id).AnyAsync(x => x.RoleCode == input.RoleCode || x.RoleName == input.RoleName);
|
||||
if (isExist)
|
||||
{
|
||||
throw new UserFriendlyException(RoleConst.Exist);
|
||||
}
|
||||
|
||||
await MapToEntityAsync(input, entity);
|
||||
await _repository.UpdateAsync(entity);
|
||||
|
||||
await _roleManager.GiveRoleSetMenuAsync(new List<Guid> { id }, input.MenuIds);
|
||||
|
||||
dto = await MapToGetOutputDtoAsync(entity);
|
||||
// uow.Commit();
|
||||
//}
|
||||
var dto = await MapToGetOutputDtoAsync(entity);
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -134,7 +143,8 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <param name="isAllocated">是否在该角色下</param>
|
||||
/// <returns></returns>
|
||||
[Route("role/auth-user/{roleId}/{isAllocated}")]
|
||||
public async Task<PagedResultDto<UserGetListOutputDto>> GetAuthUserByRoleIdAsync([FromRoute] Guid roleId, [FromRoute] bool isAllocated, [FromQuery] RoleAuthUserGetListInput input)
|
||||
public async Task<PagedResultDto<UserGetListOutputDto>> GetAuthUserByRoleIdAsync([FromRoute] Guid roleId,
|
||||
[FromRoute] bool isAllocated, [FromQuery] RoleAuthUserGetListInput input)
|
||||
{
|
||||
PagedResultDto<UserGetListOutputDto> output;
|
||||
//角色下已授权用户
|
||||
@@ -147,10 +157,12 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
{
|
||||
output = await GetNotAllocatedAuthUserByRoleIdAsync(roleId, input);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private async Task<PagedResultDto<UserGetListOutputDto>> GetAllocatedAuthUserByRoleIdAsync(Guid roleId, RoleAuthUserGetListInput input)
|
||||
private async Task<PagedResultDto<UserGetListOutputDto>> GetAllocatedAuthUserByRoleIdAsync(Guid roleId,
|
||||
RoleAuthUserGetListInput input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var output = await _userRoleRepository._DbQueryable
|
||||
@@ -163,11 +175,13 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
return new PagedResultDto<UserGetListOutputDto>(total, output);
|
||||
}
|
||||
|
||||
private async Task<PagedResultDto<UserGetListOutputDto>> GetNotAllocatedAuthUserByRoleIdAsync(Guid roleId, RoleAuthUserGetListInput input)
|
||||
private async Task<PagedResultDto<UserGetListOutputDto>> GetNotAllocatedAuthUserByRoleIdAsync(Guid roleId,
|
||||
RoleAuthUserGetListInput input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var entities = await _userRoleRepository._Db.Queryable<UserAggregateRoot>()
|
||||
.Where(u => SqlFunc.Subqueryable<UserRoleEntity>().Where(x => x.RoleId == roleId).Where(x => x.UserId == u.Id).NotAny())
|
||||
.Where(u => SqlFunc.Subqueryable<UserRoleEntity>().Where(x => x.RoleId == roleId)
|
||||
.Where(x => x.UserId == u.Id).NotAny())
|
||||
.WhereIF(!string.IsNullOrEmpty(input.UserName), u => u.UserName.Contains(input.UserName))
|
||||
.WhereIF(input.Phone is not null, u => u.Phone.ToString().Contains(input.Phone.ToString()))
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
@@ -181,9 +195,10 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateAuthUserAsync(RoleAuthUserCreateOrDeleteInput input)
|
||||
public async Task CreateAuthUserAsync([FromBody] RoleAuthUserCreateOrDeleteInput input)
|
||||
{
|
||||
var userRoleEntities = input.UserIds.Select(u => new UserRoleEntity { RoleId = input.RoleId, UserId = u }).ToList();
|
||||
var userRoleEntities = input.UserIds.Select(u => new UserRoleEntity { RoleId = input.RoleId, UserId = u })
|
||||
.ToList();
|
||||
await _userRoleRepository.InsertRangeAsync(userRoleEntities);
|
||||
}
|
||||
|
||||
@@ -193,11 +208,12 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteAuthUserAsync(RoleAuthUserCreateOrDeleteInput input)
|
||||
public async Task DeleteAuthUserAsync([FromBody] RoleAuthUserCreateOrDeleteInput input)
|
||||
{
|
||||
await _userRoleRepository._Db.Deleteable<UserRoleEntity>().Where(x => x.RoleId == input.RoleId)
|
||||
.Where(x => input.UserIds.Contains(x.UserId))
|
||||
.ExecuteCommandAsync(); ;
|
||||
.ExecuteCommandAsync();
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[Permission("system:user:list")]
|
||||
public override async Task<UserGetOutputDto> GetAsync(Guid id)
|
||||
{
|
||||
//使用导航树形查询
|
||||
@@ -153,12 +154,12 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
|
||||
if (await _repository.IsAnyAsync(u => input.UserName!.Equals(u.UserName) && !id.Equals(u.Id)))
|
||||
{
|
||||
throw new UserFriendlyException("用户已经存在,更新失败");
|
||||
throw new UserFriendlyException(UserConst.Exist);
|
||||
}
|
||||
|
||||
var entity = await _repository.GetByIdAsync(id);
|
||||
//更新密码,特殊处理
|
||||
if (input.Password is not null)
|
||||
if (!string.IsNullOrWhiteSpace(input.Password))
|
||||
{
|
||||
entity.EncryPassword.Password = input.Password;
|
||||
entity.BuildPassword();
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Uow;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Options;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试文档控制器
|
||||
/// </summary>
|
||||
public class TestServcie : ApplicationService
|
||||
{
|
||||
private IRepository<StudentEntity> _repository;
|
||||
private IUnitOfWorkManager _unitOfWork;
|
||||
private ISqlSugarRepository<StudentEntity> _sqlsugarRepository;
|
||||
|
||||
public IOptions<JwtOptions> options { get; set; }
|
||||
public TestServcie(IRepository<StudentEntity> repository, IUnitOfWorkManager unitOfWork, ISqlSugarRepository<StudentEntity> sqlsugarRepository, IRepository<StudentEntity, Guid> repository2)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_repository = repository;
|
||||
_sqlsugarRepository = sqlsugarRepository;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 你好,多线程
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetTaskTest()
|
||||
{
|
||||
var tasks = Enumerable.Range(0, 2).Select(x =>
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
using (var uow = _unitOfWork.Begin(true))
|
||||
{
|
||||
// await _repository.GetListAsync();
|
||||
await _sqlsugarRepository._DbQueryable.ToListAsync();
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
});
|
||||
}).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
return "你哈";
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public async Task<List<StudentEntity>> GetTest()
|
||||
{
|
||||
//using (var uow = _unitOfWork.Begin(true))
|
||||
//{
|
||||
var data = await _repository.GetListAsync();
|
||||
var data2 = await _repository.GetListAsync();
|
||||
//await uow.CompleteAsync();
|
||||
return data;
|
||||
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//[UnitOfWork]
|
||||
public async Task<StudentEntity> PostTest()
|
||||
{
|
||||
//using (var uow = _unitOfWork.Begin())
|
||||
//{
|
||||
var stu = new StudentEntity() { Name = $"{DateTime.Now.ToString()}你好" };
|
||||
|
||||
var data = await _repository.InsertAsync(stu);
|
||||
//await uow.CompleteAsync();
|
||||
return data;
|
||||
//}
|
||||
}
|
||||
|
||||
public async Task<StudentEntity> PostError()
|
||||
{
|
||||
throw new ApplicationException();
|
||||
}
|
||||
|
||||
public async Task<StudentEntity> PostUserError()
|
||||
{
|
||||
throw new UserFriendlyException("直接爆炸");
|
||||
}
|
||||
|
||||
public string Login()
|
||||
{
|
||||
var data = options.Value;
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(data.SecurityKey));
|
||||
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim("name","admin")
|
||||
};
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: data.Issuer,
|
||||
audience: data.Audience,
|
||||
claims: claims,
|
||||
expires: DateTime.Now.AddSeconds(60 * 60 * 2),
|
||||
notBefore: DateTime.Now,
|
||||
signingCredentials: creds);
|
||||
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
|
||||
|
||||
return returnToken;
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,15 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lazy.Captcha.Core" Version="2.0.7" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.7" />
|
||||
<PackageReference Include="Volo.Abp.BackgroundWorkers.Quartz" Version="$(AbpVersion)" />
|
||||
<PackageReference Include="Volo.Abp.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" />
|
||||
<ProjectReference Include="..\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Yi.Framework.Rbac.Domain\Yi.Framework.Rbac.Domain.csproj" />
|
||||
<ProjectReference Include="..\..\..\framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user