mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-03 08:10:51 +08:00
Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8374c81860 | ||
|
|
579f60e789 | ||
|
|
40b5f33c4e | ||
|
|
978a7fab4c | ||
|
|
f7790c46d2 | ||
|
|
9fc5b521e5 | ||
|
|
775e31c5e9 | ||
|
|
3339e30014 | ||
|
|
4ed44a2a8f | ||
|
|
6134e76030 | ||
|
|
e1ea210fe9 | ||
|
|
d9022a0383 | ||
|
|
0b0c1405ea | ||
|
|
e393e1f525 | ||
|
|
ad78cb1bcd | ||
|
|
e886614641 | ||
|
|
bc83362b35 | ||
|
|
b648f09f16 | ||
|
|
eb8d1626ea | ||
|
|
db94cd32d5 | ||
|
|
71fd5a13fc | ||
|
|
67c7ef37e6 | ||
|
|
2e22f4ea67 | ||
|
|
8be36f088b | ||
|
|
b985c2c784 | ||
|
|
e39d381f08 | ||
|
|
7694c7f97b | ||
|
|
eadb5eb216 | ||
|
|
cf5c46b2ce | ||
|
|
6e9dd669ba | ||
|
|
60ef93b510 | ||
|
|
e3aada0fff | ||
|
|
bbe1a44788 | ||
|
|
bfe0f346c8 | ||
|
|
8f10146d39 | ||
|
|
5f402488d4 | ||
|
|
07c48479af | ||
|
|
4bc2cebd60 | ||
|
|
dc242420f8 | ||
|
|
0656e3f536 | ||
|
|
cfffcda068 | ||
|
|
187885fdb9 | ||
|
|
f67b60dd82 | ||
|
|
92a2421a9b | ||
|
|
556d32729a | ||
|
|
4a3bd18bac | ||
|
|
de8f23bf2f | ||
|
|
3691a74d7e | ||
|
|
2aba4eccee | ||
|
|
3e6d02eccc | ||
|
|
2cf244058b | ||
|
|
971f137a21 | ||
|
|
b1a245c2a2 | ||
|
|
0c1ad1f4e5 | ||
|
|
e2a675054c | ||
|
|
0ad49e9b9d |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -20,7 +20,7 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[L]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
@@ -267,9 +267,11 @@ dist
|
||||
.vscode
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
|
||||
Logs
|
||||
logs
|
||||
/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)
|
||||
81
README.md
81
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>
|
||||
@@ -174,7 +174,22 @@ js Vue3.2
|
||||
<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>
|
||||
@@ -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,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,27 @@
|
||||
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)
|
||||
{
|
||||
await backgroundWorkerManager.AddAsync(work);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -174,7 +187,7 @@ namespace Yi.Framework.Core.Helper
|
||||
var isUnix = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
return isUnix;
|
||||
}
|
||||
|
||||
|
||||
public static string GetCPURate()
|
||||
{
|
||||
string cpuRate;
|
||||
@@ -221,8 +234,69 @@ 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>
|
||||
/// 内存信息
|
||||
/// </summary>
|
||||
@@ -236,10 +310,7 @@ namespace Yi.Framework.Core.Helper
|
||||
public double Free { get; set; }
|
||||
|
||||
public string UsedRam { get; set; }
|
||||
/// <summary>
|
||||
/// CPU使用率%
|
||||
/// </summary>
|
||||
public string 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 (lines != null && lines.Length > 0)
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length >= 3)
|
||||
var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length >= 2)
|
||||
{
|
||||
metrics.Total = double.Parse(memory[0]);
|
||||
metrics.Used = double.Parse(memory[1]);
|
||||
metrics.Free = double.Parse(memory[2]);//m
|
||||
metrics.Total = Math.Round(double.Parse(memory[0]) / 1024, 0);
|
||||
|
||||
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,9 +8,11 @@ using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Yi.Framework.Ddd.Application
|
||||
{
|
||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TEntityDto : IEntityDto<TKey>
|
||||
public abstract class
|
||||
YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey,
|
||||
PagedAndSortedResultRequestDto>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TEntityDto : IEntityDto<TKey>
|
||||
{
|
||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
||||
{
|
||||
@@ -49,16 +51,53 @@ 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>
|
||||
where TGetListOutputDto : IEntityDto<TKey>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TGetOutputDto : IEntityDto<TKey>
|
||||
where TGetListOutputDto : IEntityDto<TKey>
|
||||
{
|
||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -146,4 +187,4 @@ namespace Yi.Framework.Ddd.Application
|
||||
//await Repository.InsertManyAsync(entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
/// </summary>
|
||||
public bool EnabledDbSeed { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开启驼峰转下划线
|
||||
/// </summary>
|
||||
public bool EnableUnderLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启codefirst
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete), true).Where(whereExpression).ExecuteCommandAsync() > 0;
|
||||
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete.IsDeleted), true).Where(whereExpression).ExecuteCommandAsync() > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace Yi.Framework.SqlSugarCore
|
||||
//设置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()
|
||||
@@ -72,6 +78,9 @@ namespace Yi.Framework.SqlSugarCore
|
||||
p.IsNullable = true;
|
||||
}
|
||||
|
||||
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
|
||||
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName);// 驼峰转下划线
|
||||
|
||||
EntityService(c, p);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
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 SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
@@ -17,6 +14,7 @@ using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
using Check = Volo.Abp.Check;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
@@ -26,31 +24,34 @@ namespace Yi.Framework.SqlSugarCore
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
|
||||
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
|
||||
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
private ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||
protected 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);
|
||||
private IEntityChangeEventHelper EntityChangeEventHelper =>
|
||||
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
public AbpDbConnectionOptions ConnectionOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbConnectionOptions>>().Value;
|
||||
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||
|
||||
public void SetSqlSugarClient(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;
|
||||
@@ -62,7 +63,10 @@ namespace Yi.Framework.SqlSugarCore
|
||||
options.ConnectionString = GetCurrentConnectionString();
|
||||
options.DbType = GetCurrentDbType();
|
||||
}));
|
||||
//统一使用aop处理
|
||||
connectionCreator.SetDbAop(SqlSugarClient);
|
||||
//替换默认序列化器
|
||||
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,24 +75,15 @@ namespace Yi.Framework.SqlSugarCore
|
||||
/// <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();
|
||||
var connectionString =
|
||||
connectionStringResolver.ResolveAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
|
||||
//没有检测到使用多租户功能,默认使用默认库即可
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
|
||||
connectionString = defautlUrl;
|
||||
Check.NotNull(Options.Url, "dbUrl未配置");
|
||||
}
|
||||
|
||||
return connectionString!;
|
||||
}
|
||||
|
||||
@@ -102,7 +97,8 @@ namespace Yi.Framework.SqlSugarCore
|
||||
return dbTypeFromTenantName.Value;
|
||||
}
|
||||
}
|
||||
Volo.Abp.Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
|
||||
Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
return Options.DbType!.Value;
|
||||
}
|
||||
|
||||
@@ -136,7 +132,6 @@ namespace Yi.Framework.SqlSugarCore
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 上下文对象扩展
|
||||
/// </summary>
|
||||
@@ -148,21 +143,23 @@ namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
|
||||
}
|
||||
|
||||
if (IsMultiTenantFilterEnabled)
|
||||
{
|
||||
//表达式不能放方法
|
||||
Guid? tenantId = CurrentTenant?.Id;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == tenantId);
|
||||
//表达式里只能有具体值,不能运算
|
||||
var expressionCurrentTenant = CurrentTenant.Id ?? null;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
|
||||
}
|
||||
|
||||
CustomDataFilter(sqlSugarClient);
|
||||
}
|
||||
|
||||
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -184,48 +181,61 @@ namespace Yi.Framework.SqlSugarCore
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
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 (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
|
||||
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 (oldValue is null || DateTime.MinValue.Equals(oldValue))
|
||||
if (DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
//类型为guid
|
||||
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
if (CurrentUser.Id is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//插入时,需要租户id,先预留
|
||||
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
{
|
||||
if (CurrentTenant is not null)
|
||||
if (CurrentTenant.Id is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentTenant.Id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -238,6 +248,7 @@ namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.UpdateByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
@@ -254,8 +265,8 @@ namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.DeleteByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
@@ -265,14 +276,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
foreach (var entityValue in entityValues)
|
||||
{
|
||||
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -291,7 +301,6 @@ namespace Yi.Framework.SqlSugarCore
|
||||
sb.AppendLine("===============================");
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -315,14 +324,16 @@ namespace Yi.Framework.SqlSugarCore
|
||||
/// <param name="column"></param>
|
||||
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
|
||||
{
|
||||
if (property.Name == "ConcurrencyStamp")
|
||||
if (property.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
column.IsEnableUpdateVersionValidation = true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(ExtraPropertyDictionary))
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
|
||||
if (property.Name == nameof(Entity<object>.Id))
|
||||
{
|
||||
column.IsPrimarykey = true;
|
||||
@@ -337,11 +348,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
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
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
|
||||
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
@@ -353,19 +366,14 @@ namespace Yi.Framework.SqlSugarCore
|
||||
|
||||
case DbType.SqlServer:
|
||||
//SqlServer
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.bak"/*服务器路径*/);//第一个参数库名
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
|
||||
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("其他数据库备份未实现");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
public class NonPublicPropertiesResolver : DefaultContractResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// 重写获取属性,存在get set方法就可以写入
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <param name="memberSerialization"></param>
|
||||
/// <returns></returns>
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
var prop = base.CreateProperty(member, memberSerialization);
|
||||
if (member is PropertyInfo pi)
|
||||
{
|
||||
prop.Readable = (pi.GetMethod != null);
|
||||
prop.Writable = (pi.SetMethod != null);
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
public class SqlSugarNonPublicSerializer : ISerializeService
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认的序列化服务
|
||||
/// </summary>
|
||||
private readonly ISerializeService _serializeService = DefaultServices.Serialize;
|
||||
|
||||
public string SerializeObject(object value)
|
||||
{
|
||||
//保留原有实现
|
||||
return _serializeService.SerializeObject(value);
|
||||
}
|
||||
|
||||
public string SugarSerializeObject(object value)
|
||||
{ //保留原有实现
|
||||
return _serializeService.SugarSerializeObject(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写对象反序列化支持NoPublic访问器
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T DeserializeObject<T>(string value)
|
||||
{
|
||||
if (typeof(T).FullName.StartsWith("System.Text.Json."))
|
||||
{
|
||||
// 动态创建一个 JsonSerializer 实例
|
||||
Type serializerType =typeof(T).Assembly.GetType("System.Text.Json.JsonSerializer");
|
||||
|
||||
var methods = serializerType
|
||||
.GetMethods().Where(it=>it.Name== "Deserialize")
|
||||
.Where(it=>it.GetParameters().Any(z=>z.ParameterType==typeof(string))).First();
|
||||
|
||||
// 调用 SerializeObject 方法序列化对象
|
||||
T json = (T)methods.MakeGenericMethod(typeof(T))
|
||||
.Invoke(null, new object[] { value, null });
|
||||
return json;
|
||||
}
|
||||
var jSetting = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect
|
||||
};
|
||||
return JsonConvert.DeserializeObject<T>(value, jSetting);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
|
||||
public ISqlSugarDbContext GetDbContext()
|
||||
{
|
||||
|
||||
return _sqlsugarDbContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,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;
|
||||
|
||||
@@ -25,7 +25,8 @@ 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>();
|
||||
|
||||
@@ -39,7 +40,12 @@ namespace Yi.Framework.SqlSugarCore
|
||||
service.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
|
||||
|
||||
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
|
||||
//替换Sqlsugar默认序列化器,用来解决.Select()不支持嵌套对象/匿名对象的非公有访问器 值无法绑定,如Id属性
|
||||
context.Services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
|
||||
|
||||
var dbConfig = section.Get<DbConnOptions>();
|
||||
//将默认db传递给abp连接字符串模块
|
||||
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -50,8 +56,8 @@ namespace Yi.Framework.SqlSugarCore
|
||||
//进行CodeFirst
|
||||
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 +71,14 @@ namespace Yi.Framework.SqlSugarCore
|
||||
sb.AppendLine("===============================");
|
||||
|
||||
|
||||
_logger.LogInformation(sb.ToString());
|
||||
logger.LogInformation(sb.ToString());
|
||||
//Todo:准备支持多租户种子数据及CodeFirst
|
||||
|
||||
if (options.EnabledCodeFirst)
|
||||
{
|
||||
CodeFirst(service);
|
||||
}
|
||||
|
||||
if (options.EnabledDbSeed)
|
||||
{
|
||||
await DataSeedAsync(service);
|
||||
@@ -80,7 +87,6 @@ namespace Yi.Framework.SqlSugarCore
|
||||
|
||||
private void CodeFirst(IServiceProvider service)
|
||||
{
|
||||
|
||||
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
||||
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
||||
|
||||
@@ -95,11 +101,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)
|
||||
@@ -108,4 +114,4 @@ namespace Yi.Framework.SqlSugarCore
|
||||
await dataSeeder.SeedAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
// using (var uow = UnitOfWorkManager.Begin(true,isTransactional:false))
|
||||
// {
|
||||
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
|
||||
// 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>
|
||||
/// 缓存前缀
|
||||
@@ -39,13 +64,28 @@ public class AccessLogMiddleware : IMiddleware, ITransientDependency
|
||||
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
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 = "银行利息积分刷新";
|
||||
//每个小时整点执行一次
|
||||
|
||||
Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
|
||||
CronExpression = "0 0 * * * ?";
|
||||
|
||||
// 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 (input.Content.Length<=6)
|
||||
{
|
||||
throw new UserFriendlyException("评论长度至少大于6");
|
||||
}
|
||||
|
||||
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>,
|
||||
IDiscussService
|
||||
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,42 +64,43 @@ 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(),
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
UserName = user.UserName,
|
||||
Nick = user.Nick,
|
||||
Icon = user.Icon,
|
||||
Id = user.Id,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
Money=info.Money,
|
||||
Experience=info.Experience
|
||||
},
|
||||
Plate = new Contracts.Dtos.Plate.PlateGetOutputDto()
|
||||
{
|
||||
Name = plate.Name,
|
||||
Id = plate.Id,
|
||||
Code = plate.Code,
|
||||
Introduction = plate.Introduction,
|
||||
Logo = plate.Logo
|
||||
|
||||
}
|
||||
}, true)
|
||||
.SingleAsync(discuss => discuss.Id == id);
|
||||
.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(),
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
UserName = user.UserName,
|
||||
Nick = user.Nick,
|
||||
Icon = user.Icon,
|
||||
Id = user.Id,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
Money = info.Money,
|
||||
Experience = info.Experience
|
||||
},
|
||||
Plate = new Contracts.Dtos.Plate.PlateGetOutputDto()
|
||||
{
|
||||
Name = plate.Name,
|
||||
Id = plate.Id,
|
||||
Code = plate.Code,
|
||||
Introduction = plate.Introduction,
|
||||
Logo = plate.Logo
|
||||
}
|
||||
}, true)
|
||||
.SingleAsync(discuss => discuss.Id == id);
|
||||
|
||||
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,49 +112,65 @@ 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;
|
||||
var items = await _forumManager._discussRepository._DbQueryable
|
||||
.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)
|
||||
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
|
||||
.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(),
|
||||
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Nick = user.Nick,
|
||||
Icon = user.Icon,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
Money = info.Money,
|
||||
Experience = info.Experience
|
||||
}
|
||||
|
||||
}, true)
|
||||
.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)
|
||||
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
|
||||
.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(),
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Nick = user.Nick,
|
||||
Icon = user.Icon,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
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,13 +271,15 @@ 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]
|
||||
@@ -32,13 +32,7 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
|
||||
int[] values = new int[10] { 0, 10, 30, 50, 60, 80, 90, 100, 200, 666 };
|
||||
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 = "【私密】您无该主题权限,可联系作者申请开放";
|
||||
|
||||
@@ -5,24 +5,25 @@ public enum AssignmentRequirementTypeEnum
|
||||
/// <summary>
|
||||
/// 主题
|
||||
/// </summary>
|
||||
Discuss=2,
|
||||
|
||||
Discuss = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 评论
|
||||
/// </summary>
|
||||
Comment=4,
|
||||
|
||||
Comment = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 点赞
|
||||
/// </summary>
|
||||
Agree=8,
|
||||
|
||||
/// <summary>
|
||||
/// 更新个人中心
|
||||
/// </summary>
|
||||
UpdateProfile=16
|
||||
|
||||
|
||||
Agree = 8,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 更新昵称
|
||||
/// </summary>
|
||||
UpdateNick = 16,
|
||||
|
||||
/// <summary>
|
||||
/// 更新头像
|
||||
/// </summary>
|
||||
UpdateIcon = 32,
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
|
||||
|
||||
//查询主题的信息
|
||||
var discussAndAgreeDto = await _agreeRepository._DbQueryable
|
||||
.Where(agree=>agree.Id==agreeEntity.Id)
|
||||
.LeftJoin<DiscussAggregateRoot>((agree, discuss) => agree.DiscussId == discuss.Id)
|
||||
.Select((agree, discuss) =>
|
||||
new
|
||||
|
||||
@@ -33,21 +33,27 @@ public class AssignmentEventHandler : ILocalEventHandler<AssignmentEventArgs>, I
|
||||
case AssignmentRequirementTypeEnum.Discuss:
|
||||
SetCurrentStepNumber(AssignmentRequirementTypeEnum.Discuss, currentAssignmentList);
|
||||
break;
|
||||
|
||||
|
||||
//发表评论
|
||||
case AssignmentRequirementTypeEnum.Comment:
|
||||
SetCurrentStepNumber(AssignmentRequirementTypeEnum.Comment, currentAssignmentList);
|
||||
break;
|
||||
|
||||
|
||||
//点赞
|
||||
case AssignmentRequirementTypeEnum.Agree:
|
||||
SetCurrentStepNumber(AssignmentRequirementTypeEnum.Agree, currentAssignmentList);
|
||||
break;
|
||||
|
||||
//更新个人信息
|
||||
case AssignmentRequirementTypeEnum.UpdateProfile:
|
||||
//这里还需判断是否更新了
|
||||
|
||||
//更新昵称
|
||||
case AssignmentRequirementTypeEnum.UpdateNick:
|
||||
SetCurrentStepNumber(AssignmentRequirementTypeEnum.UpdateNick, currentAssignmentList);
|
||||
break;
|
||||
|
||||
//更新头像
|
||||
case AssignmentRequirementTypeEnum.UpdateIcon:
|
||||
SetCurrentStepNumber(AssignmentRequirementTypeEnum.UpdateIcon, currentAssignmentList);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@@ -72,7 +78,7 @@ public class AssignmentEventHandler : ILocalEventHandler<AssignmentEventArgs>, I
|
||||
x.CurrentStepNumber < x.TotalStepNumber)
|
||||
{
|
||||
x.CurrentStepNumber += 1;
|
||||
if (x.CurrentStepNumber==x.TotalStepNumber)
|
||||
if (x.CurrentStepNumber == x.TotalStepNumber)
|
||||
{
|
||||
x.AssignmentState = AssignmentStateEnum.Completed;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
switch (eventData.NoticeType)
|
||||
|
||||
|
||||
//是否需要离线存储
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,70 @@
|
||||
using Yi.Framework.Bbs.Domain.Entities.Assignment;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Bbs.Domain.Entities.Assignment;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Bbs.Domain.Managers.AssignmentProviders;
|
||||
|
||||
/// <summary>
|
||||
/// 新手任务提供者
|
||||
/// </summary>
|
||||
public class NoviceProvider : IAssignmentProvider
|
||||
[ExposeServices(typeof(IAssignmentProvider))]
|
||||
public class NoviceProvider : IAssignmentProvider
|
||||
{
|
||||
public async Task<List<AssignmentDefineAggregateRoot>> GetCanReceiveListAsync(AssignmentContext context)
|
||||
{
|
||||
List<AssignmentDefineAggregateRoot> output = new List<AssignmentDefineAggregateRoot>();
|
||||
//新手任务是要有前置依赖关系的,链表类型依赖
|
||||
throw new NotImplementedException();
|
||||
|
||||
//先获取到对应任务定义列表,新手任务
|
||||
var assignmentDefines = context.AllAssignmentDefine.Where(x => x.AssignmentType == AssignmentTypeEnum.Novice)
|
||||
.ToList();
|
||||
var assignmentDefineIds = assignmentDefines.Select(x => x.Id).ToList();
|
||||
|
||||
//根路径
|
||||
var rootAssignmentDefine = assignmentDefines.Where(x => x.PreAssignmentId == null).OrderBy(x=>x.OrderNum).FirstOrDefault();
|
||||
//代表没有定义新手任务
|
||||
if (rootAssignmentDefine is null)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
//1:查询该用户有正在进行的新手任务,如果有跳过
|
||||
if (context.CurrentUserAssignments
|
||||
.Where(x => assignmentDefineIds.Contains(x.AssignmentDefineId))
|
||||
.Any(x => x.AssignmentState == AssignmentStateEnum.Progress))
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
//2: 查询该用户是否有完成的新手任务,如果没有,直接返回根节点,如果有,则根据链表选择最后的节点
|
||||
var assignmentFilterIds = context.CurrentUserAssignments
|
||||
.Where(x => assignmentDefineIds.Contains(x.AssignmentDefineId))
|
||||
.Where(x =>
|
||||
//已经完成的
|
||||
x.AssignmentState == AssignmentStateEnum.End||
|
||||
x.AssignmentState==AssignmentStateEnum.Completed
|
||||
)
|
||||
.Select(x => x.AssignmentDefineId)
|
||||
.ToList();
|
||||
|
||||
if (assignmentFilterIds.Count == 0)
|
||||
{
|
||||
output.Add(rootAssignmentDefine);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
//该用户接受的最后一个新手任务
|
||||
var lastAssignment = assignmentDefines.Where(x => assignmentFilterIds.Contains(x.Id))
|
||||
.OrderByDescending(x => x.OrderNum).First();
|
||||
|
||||
//包含比该用户还要大的任务
|
||||
if (assignmentDefines.Any(x => x.OrderNum > lastAssignment.OrderNum))
|
||||
{
|
||||
output.Add(assignmentDefines.FirstOrDefault(x => x.OrderNum == lastAssignment.OrderNum + 1));
|
||||
return output;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@ using Yi.Framework.Ddd.Application.Contracts;
|
||||
namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置查询参数
|
||||
/// 配置查询参数
|
||||
/// </summary>
|
||||
public class ConfigGetListInputVo : PagedAllResultRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置名称
|
||||
/// 配置名称
|
||||
/// </summary>
|
||||
public string? ConfigName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置键
|
||||
/// 配置键
|
||||
/// </summary>
|
||||
public string? ConfigKey { get; set; }
|
||||
|
||||
|
||||
@@ -5,14 +5,11 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dept
|
||||
/// </summary>
|
||||
public class DeptCreateInputVo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public Guid? CreatorId { get; set; }
|
||||
public bool State { get; set; }
|
||||
public string DeptName { get; set; } = string.Empty;
|
||||
public string DeptCode { get; set; } = string.Empty;
|
||||
public string DeptName { get; set; }
|
||||
public string DeptCode { get; set; }
|
||||
public string? Leader { get; set; }
|
||||
public Guid ParentId { get; set; }
|
||||
public Guid? ParentId { get; set; }=Guid.Empty;
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dept
|
||||
{
|
||||
public class DeptUpdateInputVo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public Guid? CreatorId { get; set; }
|
||||
public bool State { get; set; }
|
||||
public string DeptName { get; set; } = string.Empty;
|
||||
public string DeptCode { get; set; } = string.Empty;
|
||||
public string? Leader { get; set; }
|
||||
public Guid ParentId { get; set; }
|
||||
public Guid? ParentId { get; set; }=Guid.Empty;
|
||||
public string? Remark { 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; }
|
||||
@@ -24,5 +24,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
public string? Component { get; set; }
|
||||
public string? Query { get; set; }
|
||||
public int OrderNum { get; set; }
|
||||
public MenuSourceEnum MenuSource { get; set; } = MenuSourceEnum.Ruoyi;
|
||||
public string? RouterName { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
{
|
||||
@@ -7,6 +8,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
|
||||
public bool? State { get; set; }
|
||||
public string? MenuName { get; set; }
|
||||
|
||||
public MenuSourceEnum MenuSource { get; set; } = MenuSourceEnum.Ruoyi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
|
||||
public string? Component { get; set; }
|
||||
public string? Query { get; set; }
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
//public List<MenuEntity>? Children { get; set; }
|
||||
public string? RouterName { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,9 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Post
|
||||
/// </summary>
|
||||
public class PostCreateInputVo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public long? CreatorId { get; set; }
|
||||
public bool? State { get; set; }
|
||||
public string PostCode { get; set; } = string.Empty;
|
||||
public string PostName { get; set; } = string.Empty;
|
||||
public string PostCode { get; set; }
|
||||
public string PostName { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Post
|
||||
{
|
||||
public class PostUpdateInputVo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreationTime { get; set; } = DateTime.Now;
|
||||
public Guid? CreatorId { get; set; }
|
||||
public bool? State { get; set; }
|
||||
public string PostCode { get; set; } = string.Empty;
|
||||
public string PostName { get; set; } = string.Empty;
|
||||
public int OrderNum { get; set; }
|
||||
public string PostCode { get; set; }
|
||||
public string PostName { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application.Contracts\Yi.Framework.Ddd.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\bbs\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj" />
|
||||
<ProjectReference Include="..\Yi.Framework.Rbac.Domain.Shared\Yi.Framework.Rbac.Domain.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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,18 +1,24 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Lazy.Captcha.Core;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Authorization;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.EventBus.Local;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.Uow;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Account;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Entities;
|
||||
@@ -21,14 +27,16 @@ 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;
|
||||
|
||||
namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
|
||||
public class AccountService : ApplicationService, IAccountService
|
||||
{
|
||||
protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
|
||||
private IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> _phoneCache;
|
||||
private readonly ICaptcha _captcha;
|
||||
private readonly IGuidGenerator _guidGenerator;
|
||||
@@ -36,6 +44,8 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
private readonly IAliyunManger _aliyunManger;
|
||||
private IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache;
|
||||
private UserManager _userManager;
|
||||
private IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AccountService(IUserRepository userRepository,
|
||||
ICurrentUser currentUser,
|
||||
IAccountManager accountManager,
|
||||
@@ -46,7 +56,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
IGuidGenerator guidGenerator,
|
||||
IOptions<RbacOptions> options,
|
||||
IAliyunManger aliyunManger,
|
||||
UserManager userManager)
|
||||
UserManager userManager, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_currentUser = currentUser;
|
||||
@@ -59,6 +69,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
_aliyunManger = aliyunManger;
|
||||
_userCache = userCache;
|
||||
_userManager = userManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,16 +77,17 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
private ICurrentUser _currentUser;
|
||||
private IAccountManager _accountManager;
|
||||
private ISqlSugarRepository<MenuAggregateRoot> _menuRepository;
|
||||
|
||||
/// <summary>
|
||||
/// 校验图片登录验证码,无需和账号绑定
|
||||
/// </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("验证码错误");
|
||||
}
|
||||
@@ -83,7 +95,6 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
@@ -98,16 +109,27 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
}
|
||||
|
||||
//校验验证码
|
||||
ValidationImageCaptcha(input);
|
||||
ValidationImageCaptcha(input.Uuid,input.Code);
|
||||
|
||||
UserAggregateRoot user = new();
|
||||
//校验
|
||||
await _accountManager.LoginValidationAsync(input.UserName, input.Password, x => user = x);
|
||||
|
||||
var userInfo = new UserRoleMenuDto();
|
||||
//获取token
|
||||
var accessToken = await _accountManager.GetTokenByUserIdAsync(user.Id);
|
||||
var accessToken = await _accountManager.GetTokenByUserIdAsync(user.Id, (info) => userInfo = info);
|
||||
var refreshToken = _accountManager.CreateRefreshToken(user.Id);
|
||||
|
||||
//这里抛出一个登录的事件,也可以在全部流程走完,在应用层组装
|
||||
if (_httpContextAccessor.HttpContext is not null)
|
||||
{
|
||||
var loginEntity = new LoginLogAggregateRoot().GetInfoByHttpContext(_httpContextAccessor.HttpContext);
|
||||
var loginEto = loginEntity.Adapt<LoginEventArgs>();
|
||||
loginEto.UserName = userInfo.User.UserName;
|
||||
loginEto.UserId = userInfo.User.Id;
|
||||
await LocalEventBus.PublishAsync(loginEto);
|
||||
}
|
||||
|
||||
return new { Token = accessToken, RefreshToken = refreshToken };
|
||||
}
|
||||
|
||||
@@ -130,50 +152,78 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
/// 生成验证码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
|
||||
[AllowAnonymous]
|
||||
public async Task<CaptchaImageDto> GetCaptchaImageAsync()
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw new UserFriendlyException($"{input.Phone}已发送过验证码,10分钟后可重试");
|
||||
}
|
||||
|
||||
//生成一个4位数的验证码
|
||||
//发送短信,同时生成uuid
|
||||
////key: 电话号码 value:验证码+uuid
|
||||
@@ -181,7 +231,9 @@ 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), new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
|
||||
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone),
|
||||
new CaptchaPhoneCacheItem(code),
|
||||
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
|
||||
return new
|
||||
{
|
||||
Uuid = uuid
|
||||
@@ -191,18 +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>
|
||||
@@ -216,23 +293,24 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
throw new UserFriendlyException("该系统暂未开放注册功能");
|
||||
}
|
||||
|
||||
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")]
|
||||
[Authorize]
|
||||
|
||||
public async Task<UserRoleMenuDto> GetAsync()
|
||||
{
|
||||
//通过鉴权jwt获取到用户的id
|
||||
@@ -241,6 +319,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
throw new UserFriendlyException("用户未登录");
|
||||
}
|
||||
|
||||
//此处优先从缓存中获取
|
||||
var output = await _userManager.GetInfoAsync(userId.Value);
|
||||
return output;
|
||||
@@ -249,18 +328,19 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户的前端路由
|
||||
/// 支持ruoyi/pure
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[Route("account/Vue3Router")]
|
||||
public async Task<List<Vue3RouterDto>> GetVue3Router()
|
||||
[Route("account/Vue3Router/{routerType?}")]
|
||||
public async Task<object> GetVue3Router([FromRoute] string? routerType)
|
||||
{
|
||||
var userId = _currentUser.Id;
|
||||
if (_currentUser.Id is null)
|
||||
{
|
||||
throw new AbpAuthorizationException("用户未登录");
|
||||
|
||||
}
|
||||
|
||||
var data = await _userManager.GetInfoAsync(userId!.Value);
|
||||
var menus = data.Menus.ToList();
|
||||
|
||||
@@ -269,9 +349,22 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
menus = ObjectMapper.Map<List<MenuAggregateRoot>, List<MenuDto>>(await _menuRepository.GetListAsync());
|
||||
}
|
||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||
List<Vue3RouterDto> routers = ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3RouterBuild();
|
||||
return routers;
|
||||
|
||||
object output = null;
|
||||
if (routerType is null || routerType == "ruoyi")
|
||||
{
|
||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||
output =
|
||||
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3RuoYiRouterBuild();
|
||||
}
|
||||
else if (routerType == "pure")
|
||||
{
|
||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||
output =
|
||||
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3PureRouterBuild();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -287,6 +380,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
return false;
|
||||
// throw new UserFriendlyException("用户已退出");
|
||||
}
|
||||
|
||||
await _userCache.RemoveAsync(new UserInfoCacheKey(userId.Value));
|
||||
//Jwt去中心化登出,只需用记录日志即可
|
||||
return true;
|
||||
@@ -303,11 +397,14 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
throw new UserFriendlyException("无效更新!输入的数据,新密码不能与老密码相同");
|
||||
}
|
||||
|
||||
if (_currentUser.Id is null)
|
||||
{
|
||||
throw new UserFriendlyException("用户未登录");
|
||||
}
|
||||
await _accountManager.UpdatePasswordAsync(_currentUser.Id ?? Guid.Empty, input.NewPassword, input.OldPassword);
|
||||
|
||||
await _accountManager.UpdatePasswordAsync(_currentUser.Id ?? Guid.Empty, input.NewPassword,
|
||||
input.OldPassword);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -324,6 +421,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
||||
{
|
||||
throw new UserFriendlyException("重置密码不能为空!");
|
||||
}
|
||||
|
||||
await _accountManager.RestPasswordAsync(userId, input.Password);
|
||||
return true;
|
||||
}
|
||||
@@ -335,11 +433,22 @@ 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;
|
||||
}
|
||||
|
||||
entity.Icon = input.Icon;
|
||||
await _userRepository.UpdateAsync(entity);
|
||||
|
||||
//发布更新头像任务事件
|
||||
await this.LocalEventBus.PublishAsync(
|
||||
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, userId), false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>,
|
||||
IConfigService
|
||||
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!))
|
||||
.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)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
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)
|
||||
.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,10 +30,12 @@ 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)
|
||||
.WhereIF(input.DictLabel is not null, x => x.DictLabel!.Contains(input.DictLabel!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
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>
|
||||
{
|
||||
TotalCount = total,
|
||||
|
||||
@@ -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,16 +47,36 @@ 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
|
||||
.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)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
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)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<DeptGetListOutputDto>
|
||||
{
|
||||
Items = await MapToGetListOutputDtosAsync(entities),
|
||||
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,14 +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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
IPostService
|
||||
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!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
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,13 +20,16 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <summary>
|
||||
/// Role服务实现
|
||||
/// </summary>
|
||||
public class RoleService : YiCrudAppService<RoleAggregateRoot, RoleGetOutputDto, RoleGetListOutputDto, Guid, RoleGetListInputVo, RoleCreateInputVo, RoleUpdateInputVo>,
|
||||
IRoleService
|
||||
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);
|
||||
(roleManager, roleDeptRepository, userRoleRepository, repository);
|
||||
}
|
||||
|
||||
private ISqlSugarRepository<RoleAggregateRoot, Guid> _repository;
|
||||
@@ -41,23 +45,25 @@ 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);
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<RoleGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
@@ -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,30 +157,34 @@ 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
|
||||
.LeftJoin<UserAggregateRoot>((ur, u) => ur.UserId == u.Id && ur.RoleId == roleId)
|
||||
.Where((ur, u) => ur.RoleId == roleId)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.UserName), (ur, u) => u.UserName.Contains(input.UserName))
|
||||
.WhereIF(input.Phone is not null, (ur, u) => u.Phone.ToString().Contains(input.Phone.ToString()))
|
||||
.Select((ur, u) => new UserGetListOutputDto { Id = u.Id }, true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
.LeftJoin<UserAggregateRoot>((ur, u) => ur.UserId == u.Id && ur.RoleId == roleId)
|
||||
.Where((ur, u) => ur.RoleId == roleId)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.UserName), (ur, u) => u.UserName.Contains(input.UserName))
|
||||
.WhereIF(input.Phone is not null, (ur, u) => u.Phone.ToString().Contains(input.Phone.ToString()))
|
||||
.Select((ur, u) => new UserGetListOutputDto { Id = u.Id }, true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
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())
|
||||
.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);
|
||||
.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);
|
||||
var output = entities.Adapt<List<UserGetListOutputDto>>();
|
||||
return new PagedResultDto<UserGetListOutputDto>(total, output);
|
||||
}
|
||||
@@ -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();
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.EventBus.Local;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
@@ -24,13 +26,20 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
/// <summary>
|
||||
/// User服务实现
|
||||
/// </summary>
|
||||
public class UserService : YiCrudAppService<UserAggregateRoot, UserGetOutputDto, UserGetListOutputDto, Guid, UserGetListInputVo, UserCreateInputVo, UserUpdateInputVo>, IUserService
|
||||
public class UserService : YiCrudAppService<UserAggregateRoot, UserGetOutputDto, UserGetListOutputDto, Guid,
|
||||
UserGetListInputVo, UserCreateInputVo, UserUpdateInputVo>, IUserService
|
||||
//IUserService
|
||||
{
|
||||
public UserService(ISqlSugarRepository<UserAggregateRoot, Guid> repository, UserManager userManager, IUserRepository userRepository, ICurrentUser currentUser, IDeptService deptService, ILocalEventBus localEventBus, IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> userCache) : base(repository)
|
||||
protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
|
||||
|
||||
public UserService(ISqlSugarRepository<UserAggregateRoot, Guid> repository, UserManager userManager,
|
||||
IUserRepository userRepository, ICurrentUser currentUser, IDeptService deptService,
|
||||
ILocalEventBus localEventBus,
|
||||
IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> userCache) : base(repository)
|
||||
=>
|
||||
(_userManager, _userRepository, _currentUser, _deptService, _repository, _localEventBus) =
|
||||
(userManager, userRepository, currentUser, deptService, repository, localEventBus);
|
||||
(_userManager, _userRepository, _currentUser, _deptService, _repository, _localEventBus) =
|
||||
(userManager, userRepository, currentUser, deptService, repository, localEventBus);
|
||||
|
||||
private UserManager _userManager { get; set; }
|
||||
private ISqlSugarRepository<UserAggregateRoot, Guid> _repository;
|
||||
private IUserRepository _userRepository { get; set; }
|
||||
@@ -39,6 +48,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
private ICurrentUser _currentUser { get; set; }
|
||||
|
||||
private ILocalEventBus _localEventBus;
|
||||
|
||||
/// <summary>
|
||||
/// 查询用户
|
||||
/// </summary>
|
||||
@@ -56,22 +66,21 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
|
||||
|
||||
List<Guid> ids = input.Ids?.Split(",").Select(x => Guid.Parse(x)).ToList();
|
||||
var outPut = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.UserName), x => x.UserName.Contains(input.UserName!))
|
||||
.WhereIF(input.Phone is not null, x => x.Phone.ToString()!.Contains(input.Phone.ToString()!))
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name!.Contains(input.Name!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||
var outPut = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.UserName),
|
||||
x => x.UserName.Contains(input.UserName!))
|
||||
.WhereIF(input.Phone is not null, x => x.Phone.ToString()!.Contains(input.Phone.ToString()!))
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name!.Contains(input.Name!))
|
||||
.WhereIF(input.State is not null, x => x.State == input.State)
|
||||
.WhereIF(input.StartTime is not null && input.EndTime is not null,
|
||||
x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||
|
||||
//这个为过滤当前部门,加入数据权限后,将由数据权限控制
|
||||
.WhereIF(input.DeptId is not null, x => deptIds.Contains(x.DeptId ?? Guid.Empty))
|
||||
|
||||
.WhereIF(ids is not null, x => ids.Contains(x.Id))
|
||||
|
||||
|
||||
.LeftJoin<DeptAggregateRoot>((user, dept) => user.DeptId == dept.Id)
|
||||
.OrderByDescending(user => user.CreationTime)
|
||||
.Select((user, dept) => new UserGetListOutputDto(), true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
//这个为过滤当前部门,加入数据权限后,将由数据权限控制
|
||||
.WhereIF(input.DeptId is not null, x => deptIds.Contains(x.DeptId ?? Guid.Empty))
|
||||
.WhereIF(ids is not null, x => ids.Contains(x.Id))
|
||||
.LeftJoin<DeptAggregateRoot>((user, dept) => user.DeptId == dept.Id)
|
||||
.OrderByDescending(user => user.CreationTime)
|
||||
.Select((user, dept) => new UserGetListOutputDto(), true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
|
||||
var result = new PagedResultDto<UserGetListOutputDto>();
|
||||
result.Items = outPut;
|
||||
@@ -96,7 +105,6 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
[Permission("system:user:add")]
|
||||
public async override Task<UserGetOutputDto> CreateAsync(UserCreateInputVo input)
|
||||
{
|
||||
|
||||
var entitiy = await MapToEntityAsync(input);
|
||||
|
||||
await _userManager.CreateAsync(entitiy);
|
||||
@@ -119,10 +127,12 @@ 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)
|
||||
{
|
||||
//使用导航树形查询
|
||||
var entity = await _repository._DbQueryable.Includes(u => u.Roles).Includes(u => u.Posts).Includes(u => u.Dept).InSingleAsync(id);
|
||||
var entity = await _repository._DbQueryable.Includes(u => u.Roles).Includes(u => u.Posts)
|
||||
.Includes(u => u.Dept).InSingleAsync(id);
|
||||
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
@@ -141,17 +151,20 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
{
|
||||
throw new UserFriendlyException(UserConst.Name_Not_Allowed);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
await MapToEntityAsync(input, entity);
|
||||
|
||||
var res1 = await _repository.UpdateAsync(entity);
|
||||
@@ -173,6 +186,14 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
|
||||
await _repository.UpdateAsync(entity);
|
||||
var dto = await MapToGetOutputDtoAsync(entity);
|
||||
//发布更新昵称任务事件
|
||||
if (input.Nick != entity.Icon)
|
||||
{
|
||||
await this.LocalEventBus.PublishAsync(
|
||||
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateNick, _currentUser.GetId(), input.Nick),
|
||||
false);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -192,15 +213,16 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
{
|
||||
throw new ApplicationException("用户未存在");
|
||||
}
|
||||
|
||||
entity.State = state;
|
||||
await _repository.UpdateAsync(entity);
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
|
||||
[OperLog("删除用户", OperEnum.Delete)]
|
||||
[Permission("system:user:delete")]
|
||||
public override async Task DeleteAsync(Guid id)
|
||||
{
|
||||
|
||||
await base.DeleteAsync(id);
|
||||
}
|
||||
|
||||
@@ -216,4 +238,4 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
||||
return base.PostImportExcelAsync(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user