mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-03 00:00:58 +08:00
Compare commits
332 Commits
pure-dev
...
digital-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f203c53e19 | ||
|
|
8a9a0c8396 | ||
|
|
813b08bf1c | ||
|
|
bce9b58265 | ||
|
|
85223629c1 | ||
|
|
9a73789788 | ||
|
|
25929483c3 | ||
|
|
0e90c54dbb | ||
|
|
5dea4ab18c | ||
|
|
7187397687 | ||
|
|
bf8454a963 | ||
|
|
2b2ac9b924 | ||
|
|
78b874936c | ||
|
|
b9866af6cd | ||
|
|
350e4a5753 | ||
|
|
2544d03434 | ||
|
|
d1c1eed52e | ||
|
|
2329cfd1da | ||
|
|
9960c63f59 | ||
|
|
680f9ae246 | ||
|
|
7994e66283 | ||
|
|
2c9d344b76 | ||
|
|
6a9e28700b | ||
|
|
811e5c1a8c | ||
|
|
545cef34a9 | ||
|
|
950c89a8bc | ||
|
|
7b5bc0fe3e | ||
|
|
e05514bc41 | ||
|
|
438abf6cea | ||
|
|
5d5ebb559b | ||
|
|
3be5675828 | ||
|
|
6482218a68 | ||
|
|
8d6824b03d | ||
|
|
69ab65dbc6 | ||
|
|
4b0b0e4451 | ||
|
|
5d21fd0f7c | ||
|
|
0184015ba8 | ||
|
|
ffb329a0d9 | ||
|
|
03a712fcfe | ||
|
|
652c2b6fd0 | ||
|
|
f0093499d1 | ||
|
|
c00ada5aee | ||
|
|
6c409bfa00 | ||
|
|
1c7637d28b | ||
|
|
48dc89c43d | ||
|
|
b60cc50508 | ||
|
|
4e66762036 | ||
|
|
ff4e1dd182 | ||
|
|
36442072ba | ||
|
|
ea134f52be | ||
|
|
fe97ba1c19 | ||
|
|
2cd8b73aa3 | ||
|
|
07a5b69511 | ||
|
|
da0e207b44 | ||
|
|
fb0edc0ee0 | ||
|
|
8a26a4aeec | ||
|
|
723ce1bd5d | ||
|
|
8408b39fbb | ||
|
|
5dfaf75440 | ||
|
|
bb3b3702e1 | ||
|
|
81138fcaef | ||
|
|
6be5398114 | ||
|
|
365ae8ef0a | ||
|
|
3932b24fda | ||
|
|
f44737216f | ||
|
|
e4180a0c1a | ||
|
|
356938d6d3 | ||
|
|
1090907178 | ||
|
|
da2f7073f9 | ||
|
|
57fe0e702c | ||
|
|
90d4a98e1e | ||
|
|
d047f8aa32 | ||
|
|
f656ec32c1 | ||
|
|
3415543175 | ||
|
|
4191bc86ef | ||
|
|
1cc0ef916f | ||
|
|
fdbee4562a | ||
|
|
5d793344cd | ||
|
|
559a45c917 | ||
|
|
cadbc09846 | ||
|
|
bcaca0b782 | ||
|
|
5cee7319c6 | ||
|
|
e960db0d3e | ||
|
|
eb2c05e9df | ||
|
|
353a6b9d0c | ||
|
|
5d2d269f11 | ||
|
|
9acb157fae | ||
|
|
4198b53996 | ||
|
|
d8286fb005 | ||
|
|
a7bf5e8873 | ||
|
|
fdec9ed6b8 | ||
|
|
a2e2072634 | ||
|
|
84cd83894b | ||
|
|
32e4145927 | ||
|
|
18dd177961 | ||
|
|
e94c65d24a | ||
|
|
e72c0d4480 | ||
|
|
1a7f1c3d15 | ||
|
|
ace5374813 | ||
|
|
41f91ea12d | ||
|
|
91bf5f93cd | ||
|
|
9445fa8005 | ||
|
|
6b491d1246 | ||
|
|
644045b307 | ||
|
|
0bf53a1c0d | ||
|
|
6b47ae232d | ||
|
|
536c3cc56b | ||
|
|
b75a8cb60d | ||
|
|
05ca1aa224 | ||
|
|
02d45bb6a7 | ||
|
|
8e805a4cf8 | ||
|
|
43c4c03832 | ||
|
|
bf2bcd1395 | ||
|
|
f9217dc066 | ||
|
|
dad4ca4ab4 | ||
|
|
b282ee8273 | ||
|
|
3dcb7d0a39 | ||
|
|
6703897fb1 | ||
|
|
eef2ed0d64 | ||
|
|
17bc4ade84 | ||
|
|
aea0896356 | ||
|
|
7c13ed6497 | ||
|
|
93dea4fa46 | ||
|
|
83fb93da11 | ||
|
|
3fbaffe9a2 | ||
|
|
3b93e8b8ec | ||
|
|
24cf087320 | ||
|
|
ae82a2d1cf | ||
|
|
2412bc1da4 | ||
|
|
42b00515eb | ||
|
|
f3c5d0862b | ||
|
|
e832921edf | ||
|
|
0c0ead26c0 | ||
|
|
f9a018638b | ||
|
|
d5ca8ddf1e | ||
|
|
650c29e75a | ||
|
|
ed5c20c612 | ||
|
|
49f1d1a8fa | ||
|
|
a87d6345c2 | ||
|
|
d83db53acb | ||
|
|
c944bd3b0e | ||
|
|
751cc3cadb | ||
|
|
80fe1116a8 | ||
|
|
81f9fd7473 | ||
|
|
9aaa88ef51 | ||
|
|
e2091eb986 | ||
|
|
22a8703978 | ||
|
|
1468a7b878 | ||
|
|
fe7211860f | ||
|
|
fddf80e74a | ||
|
|
5054391f6b | ||
|
|
5e096a277c | ||
|
|
ef2d00a254 | ||
|
|
d38159f68b | ||
|
|
dd29c9a2fa | ||
|
|
ca1b8a728d | ||
|
|
6b647cf4ea | ||
|
|
a21f2342d8 | ||
|
|
0e6f79c28e | ||
|
|
ab6563899c | ||
|
|
894d4eb051 | ||
|
|
f82122edf0 | ||
|
|
8d9c5bb762 | ||
|
|
76d94c0bc9 | ||
|
|
7a916fc78e | ||
|
|
73db2a202a | ||
|
|
31dceec787 | ||
|
|
a2106bba3e | ||
|
|
f9890bdc7f | ||
|
|
b321283f99 | ||
|
|
505e4b6586 | ||
|
|
b2efd065be | ||
|
|
050af30acb | ||
|
|
8a0c0de8a1 | ||
|
|
677dca2231 | ||
|
|
47ca08e432 | ||
|
|
8c940126b5 | ||
|
|
1cb396aa14 | ||
|
|
a47d271a33 | ||
|
|
363be13d12 | ||
|
|
3bc044e148 | ||
|
|
b8e6dfcd99 | ||
|
|
4b320c2af2 | ||
|
|
dc28d701ba | ||
|
|
71b7b7cc79 | ||
|
|
77c423f421 | ||
|
|
f6be4ad7ac | ||
|
|
8a157ba472 | ||
|
|
f6cbe899c6 | ||
|
|
7598c8319f | ||
|
|
a798f36529 | ||
|
|
779e84213e | ||
|
|
254975fcd3 | ||
|
|
e5773df1ab | ||
|
|
c2290f95cf | ||
|
|
6eb72c0303 | ||
|
|
59d9674aeb | ||
|
|
c4e79e46cf | ||
|
|
36ed5400be | ||
|
|
6378c69764 | ||
|
|
b44db20938 | ||
|
|
61cd7b42d4 | ||
|
|
77d64796e0 | ||
|
|
a4001c21b1 | ||
|
|
4fadba27dc | ||
|
|
9e1d01774f | ||
|
|
7eab4dd5b1 | ||
|
|
be4f0a2a90 | ||
|
|
0e6d380b7e | ||
|
|
1a73f7bef3 | ||
|
|
c45c17748e | ||
|
|
a518e7b210 | ||
|
|
57ad7ae1a3 | ||
|
|
e2dae1c4ab | ||
|
|
998d97b669 | ||
|
|
453d95a460 | ||
|
|
0d00f91b31 | ||
|
|
d3b87e8984 | ||
|
|
fcaad5c6cc | ||
|
|
8ca741792a | ||
|
|
0f687a7e34 | ||
|
|
736995c35b | ||
|
|
4ae548cc5b | ||
|
|
d55545849a | ||
|
|
942868f17f | ||
|
|
8a57bf52f9 | ||
|
|
260d87c97e | ||
|
|
cb1bac25a3 | ||
|
|
ef20ef7014 | ||
|
|
b88cad7b80 | ||
|
|
22ba44c271 | ||
|
|
ae2cc7ad9b | ||
|
|
3383e86064 | ||
|
|
a0ef3af155 | ||
|
|
c880f32d33 | ||
|
|
e8fcab4c6b | ||
|
|
7b20b68b6a | ||
|
|
974f264272 | ||
|
|
bcbb2b5139 | ||
|
|
1c6a795061 | ||
|
|
36246c2945 | ||
|
|
7af54f600f | ||
|
|
e09aaa2dc7 | ||
|
|
e3178d7579 | ||
|
|
b59dfbc3fd | ||
|
|
0f21688b3c | ||
|
|
801e30c1dc | ||
|
|
7049175827 | ||
|
|
f27a5a135b | ||
|
|
82ad9e249a | ||
|
|
fcff711a04 | ||
|
|
0c78b8d868 | ||
|
|
b6b54164a8 | ||
|
|
983daddebc | ||
|
|
605db9340c | ||
|
|
36ea04bd70 | ||
|
|
6f691e45d8 | ||
|
|
2c6558874d | ||
|
|
d60b432f0c | ||
|
|
adb9849650 | ||
|
|
48abbbf83e | ||
|
|
9e5361338c | ||
|
|
c5ecd71c6e | ||
|
|
b09bbba21b | ||
|
|
2db543573c | ||
|
|
cadd4df5d0 | ||
|
|
427de4b42f | ||
|
|
79cb82ea24 | ||
|
|
1b472c4ad7 | ||
|
|
21ab4950c8 | ||
|
|
1b2977d591 | ||
|
|
c54cf0bca2 | ||
|
|
935c990fe4 | ||
|
|
44f94f8398 | ||
|
|
8380cb1084 | ||
|
|
83fc4f46b2 | ||
|
|
9a97134a37 | ||
|
|
912010ed70 | ||
|
|
09b0bd8b09 | ||
|
|
c0dece8936 | ||
|
|
571b610417 | ||
|
|
658339047c | ||
|
|
13120712b1 | ||
|
|
10e1fad7f3 | ||
|
|
d7629763ef | ||
|
|
94ee0fb058 | ||
|
|
d4e8ce9c89 | ||
|
|
707e241f25 | ||
|
|
58fa94e8b8 | ||
|
|
72d307503e | ||
|
|
d9fd9163e4 | ||
|
|
21807c3a66 | ||
|
|
f499d2d8a9 | ||
|
|
0f9958bb26 | ||
|
|
8e66a9880c | ||
|
|
38e112fb06 | ||
|
|
bb0e48cd41 | ||
|
|
fd0edd93ea | ||
|
|
6359696bde | ||
|
|
1fee392989 | ||
|
|
dfdced9644 | ||
|
|
e50c1c374a | ||
|
|
dbcd051aae | ||
|
|
96571bb999 | ||
|
|
0620f6b6e3 | ||
|
|
ae163167b6 | ||
|
|
ba0bb32b5f | ||
|
|
b15e789b0b | ||
|
|
8c7afa2e7a | ||
|
|
87e30b9edf | ||
|
|
887ebe6f2f | ||
|
|
099581dddc | ||
|
|
3e84890765 | ||
|
|
d2fb0791d9 | ||
|
|
a2f22007cf | ||
|
|
d4dd531ac4 | ||
|
|
f7790c46d2 | ||
|
|
775e31c5e9 | ||
|
|
e1ea210fe9 | ||
|
|
d9022a0383 | ||
|
|
0b0c1405ea | ||
|
|
eb8d1626ea | ||
|
|
db94cd32d5 | ||
|
|
67c7ef37e6 | ||
|
|
2e22f4ea67 | ||
|
|
b985c2c784 | ||
|
|
dc242420f8 | ||
|
|
0656e3f536 | ||
|
|
cfffcda068 | ||
|
|
187885fdb9 | ||
|
|
f67b60dd82 | ||
|
|
92a2421a9b |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -20,7 +20,7 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[L]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
@@ -269,5 +269,10 @@ dist
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
|
||||
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Development.json
|
||||
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json
|
||||
/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/appsettings.Development.json
|
||||
database_backup
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Staging.json
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
|
||||
|
||||
package-lock.json
|
||||
|
||||
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,22 @@ 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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "digital-collectibles", "digital-collectibles", "{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application\Yi.Framework.DigitalCollectibles.Application.csproj", "{236B88D4-F018-4A5F-A506-7458F2308C70}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application.Contracts", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj", "{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain\Yi.Framework.DigitalCollectibles.Domain.csproj", "{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain.Shared", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain.Shared\Yi.Framework.DigitalCollectibles.Domain.Shared.csproj", "{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.SqlSugarCore", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.SqlSugarCore\Yi.Framework.DigitalCollectibles.SqlSugarCore.csproj", "{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.WeChat.MiniProgram", "framework\Yi.Framework.WeChat.MiniProgram\Yi.Framework.WeChat.MiniProgram.csproj", "{81CEA2ED-917B-41D8-BE0D-39A785B050C0}"
|
||||
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 +278,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 +406,34 @@ 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
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.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 +467,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 +503,14 @@ 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}
|
||||
{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}
|
||||
|
||||
@@ -9,84 +9,89 @@ using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Options;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class SwaggerAddExtensions
|
||||
{
|
||||
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services, Action<SwaggerGenOptions>? action=null)
|
||||
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services,
|
||||
Action<SwaggerGenOptions>? action = null)
|
||||
{
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();
|
||||
|
||||
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
|
||||
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
|
||||
|
||||
var mvcSettings =
|
||||
mvcOptions.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
|
||||
|
||||
|
||||
services.AddAbpSwaggerGen(
|
||||
options =>
|
||||
{
|
||||
if (action is not null)
|
||||
options =>
|
||||
{
|
||||
action.Invoke(options);
|
||||
}
|
||||
|
||||
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
|
||||
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
|
||||
{
|
||||
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
|
||||
if (action is not null)
|
||||
{
|
||||
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
|
||||
action.Invoke(options);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分组名称过滤 API 文档
|
||||
options.DocInclusionPredicate((docName, apiDesc) =>
|
||||
{
|
||||
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
|
||||
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
|
||||
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
|
||||
{
|
||||
var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault();
|
||||
if (settingOrNull is not null)
|
||||
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
|
||||
{
|
||||
return docName == settingOrNull.RemoteServiceName;
|
||||
options.SwaggerDoc(setting.RemoteServiceName,
|
||||
new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
options.CustomSchemaIds(type => type.FullName);
|
||||
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
|
||||
if (basePath is not null)
|
||||
{
|
||||
foreach (var item in Directory.GetFiles(basePath, "*.xml"))
|
||||
// 根据分组名称过滤 API 文档
|
||||
options.DocInclusionPredicate((docName, apiDesc) =>
|
||||
{
|
||||
options.IncludeXmlComments(item, true);
|
||||
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
|
||||
{
|
||||
var settingOrNull = mvcSettings
|
||||
.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly)
|
||||
.FirstOrDefault();
|
||||
if (settingOrNull is not null)
|
||||
{
|
||||
return docName == settingOrNull.RemoteServiceName;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
options.CustomSchemaIds(type => type.FullName);
|
||||
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
|
||||
if (basePath is not null)
|
||||
{
|
||||
foreach (var item in Directory.GetFiles(basePath, "*.xml"))
|
||||
{
|
||||
options.IncludeXmlComments(item, true);
|
||||
}
|
||||
}
|
||||
|
||||
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "直接输入Token即可",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer"
|
||||
});
|
||||
var scheme = new OpenApiSecurityScheme()
|
||||
{
|
||||
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
|
||||
};
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
||||
{
|
||||
[scheme] = new string[0]
|
||||
});
|
||||
|
||||
options.OperationFilter<AddRequiredHeaderParameter>();
|
||||
options.SchemaFilter<EnumSchemaFilter>();
|
||||
}
|
||||
);
|
||||
|
||||
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "直接输入Token即可",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer"
|
||||
});
|
||||
var scheme = new OpenApiSecurityScheme()
|
||||
{
|
||||
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
|
||||
};
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
||||
{
|
||||
[scheme] = new string[0]
|
||||
});
|
||||
|
||||
options.OperationFilter<AddRequiredHeaderParameter>();
|
||||
options.SchemaFilter<EnumSchemaFilter>();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -103,7 +108,6 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="context"></param>
|
||||
|
||||
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
||||
{
|
||||
if (context.Type.IsEnum)
|
||||
@@ -112,7 +116,7 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
model.Type = "string";
|
||||
model.Format = null;
|
||||
|
||||
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
Enum.GetNames(context.Type)
|
||||
.ToList()
|
||||
@@ -121,9 +125,10 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
Enum e = (Enum)Enum.Parse(context.Type, name);
|
||||
var descrptionOrNull = GetEnumDescription(e);
|
||||
model.Enum.Add(new OpenApiString(name));
|
||||
stringBuilder.Append($"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />");
|
||||
stringBuilder.Append(
|
||||
$"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />");
|
||||
});
|
||||
model.Description= stringBuilder.ToString();
|
||||
model.Description = stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,13 +138,13 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
return attributes.Length > 0 ? attributes[0].Description : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class AddRequiredHeaderParameter : IOperationFilter
|
||||
{
|
||||
public static string HeaderKey { get; set; } = "__tenant";
|
||||
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
if (operation.Parameters == null)
|
||||
@@ -150,8 +155,8 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
AllowEmptyValue = true,
|
||||
Description="租户id或者租户名称(可空为默认租户)"
|
||||
Description = "租户id或者租户名称(可空为默认租户)"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.AspNetCore.WebClientInfo;
|
||||
|
||||
namespace Yi.Framework.AspNetCore;
|
||||
|
||||
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
|
||||
{
|
||||
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
|
||||
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string? GetClientIpAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpContext = HttpContextAccessor.HttpContext;
|
||||
|
||||
var headers = httpContext?.Request?.Headers;
|
||||
|
||||
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
|
||||
{
|
||||
httpContext.Connection.RemoteIpAddress =
|
||||
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
|
||||
}
|
||||
|
||||
return httpContext?.Connection?.RemoteIpAddress?.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogException(ex, LogLevel.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 异常元数据
|
||||
/// </summary>
|
||||
public sealed class ExceptionMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int StatusCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public object ErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(没被复写过的 ErrorCode )
|
||||
/// </summary>
|
||||
public object OriginErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误对象(信息)
|
||||
/// </summary>
|
||||
public object Errors { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外数据
|
||||
/// </summary>
|
||||
public object Data { get; internal set; }
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
/// <summary>
|
||||
/// 友好异常拦截器
|
||||
/// </summary>
|
||||
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常拦截
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnExceptionAsync(ExceptionContext context)
|
||||
{
|
||||
|
||||
// 排除 WebSocket 请求处理
|
||||
if (context.HttpContext.IsWebSocketRequest()) return;
|
||||
|
||||
// 如果异常在其他地方被标记了处理,那么这里不再处理
|
||||
if (context.ExceptionHandled) return;
|
||||
|
||||
// 解析异常信息
|
||||
var exceptionMetadata = GetExceptionMetadata(context);
|
||||
|
||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||
// 执行规范化异常处理
|
||||
context.Result = unifyResult.OnException(context, exceptionMetadata);
|
||||
|
||||
// 创建日志记录器
|
||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
|
||||
|
||||
// 记录拦截日常
|
||||
logger.LogError(context.Exception, context.Exception.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取异常元数据
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static ExceptionMetadata GetExceptionMetadata(ActionContext context)
|
||||
{
|
||||
object errorCode = default;
|
||||
object originErrorCode = default;
|
||||
object errors = default;
|
||||
object data = default;
|
||||
var statusCode = StatusCodes.Status500InternalServerError;
|
||||
var isValidationException = false; // 判断是否是验证异常
|
||||
var isFriendlyException = false;
|
||||
|
||||
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
|
||||
var exception = context is ExceptionContext exContext
|
||||
? exContext.Exception
|
||||
: (
|
||||
context is ActionExecutedContext edContext
|
||||
? edContext.Exception
|
||||
: default
|
||||
);
|
||||
|
||||
// 判断是否是友好异常
|
||||
if (exception is UserFriendlyException friendlyException)
|
||||
{
|
||||
int statusCode2 = 500;
|
||||
int.TryParse(friendlyException.Code, out statusCode2);
|
||||
isFriendlyException = true;
|
||||
errorCode = friendlyException.Code;
|
||||
originErrorCode = friendlyException.Code;
|
||||
statusCode = statusCode2==0?403:statusCode2;
|
||||
isValidationException = false;
|
||||
errors = friendlyException.Message;
|
||||
data = friendlyException.Data;
|
||||
}
|
||||
|
||||
return new ExceptionMetadata
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
ErrorCode = errorCode,
|
||||
OriginErrorCode = originErrorCode,
|
||||
Errors = errors,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化结构(请求成功)过滤器
|
||||
/// </summary>
|
||||
public class SucceededUnifyResultFilter : IAsyncActionFilter, IOrderedFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 过滤器排序
|
||||
/// </summary>
|
||||
private const int FilterOrder = 8888;
|
||||
|
||||
/// <summary>
|
||||
/// 排序属性
|
||||
/// </summary>
|
||||
public int Order => FilterOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 处理规范化结果
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
// 执行 Action 并获取结果
|
||||
var actionExecutedContext = await next();
|
||||
|
||||
// 排除 WebSocket 请求处理
|
||||
if (actionExecutedContext.HttpContext.IsWebSocketRequest()) return;
|
||||
|
||||
// 处理已经含有状态码结果的 Result
|
||||
if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult &&
|
||||
statusCodeResult.StatusCode != null)
|
||||
{
|
||||
// 小于 200 或者 大于 299 都不是成功值,直接跳过
|
||||
if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299)
|
||||
{
|
||||
// 处理规范化结果
|
||||
if (!CheckStatusCodeNonUnify(context.HttpContext, out var unifyRes))
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
var statusCode = statusCodeResult.StatusCode.Value;
|
||||
|
||||
// 解决刷新 Token 时间和 Token 时间相近问题
|
||||
if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized
|
||||
&& httpContext.Response.Headers.ContainsKey("access-token")
|
||||
&& httpContext.Response.Headers.ContainsKey("x-access-token"))
|
||||
{
|
||||
httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden;
|
||||
}
|
||||
|
||||
// 如果 Response 已经完成输出,则禁止写入
|
||||
if (httpContext.Response.HasStarted) return;
|
||||
await unifyRes.OnResponseStatusCodes(httpContext, statusCode,
|
||||
httpContext.RequestServices.GetService<IOptions<UnifyResultSettingsOptions>>()?.Value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果出现异常,则不会进入该过滤器
|
||||
if (actionExecutedContext.Exception != null) return;
|
||||
|
||||
// 获取控制器信息
|
||||
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
||||
|
||||
// 判断是否支持 MVC 规范化处理,检测配置而已
|
||||
// if (!UnifyContext.CheckSupportMvcController(context.HttpContext, actionDescriptor, out _)) return;
|
||||
|
||||
// 判断是否跳过规范化处理,检测NonUnifyAttribute而已
|
||||
if (CheckSucceededNonUnify(actionDescriptor.MethodInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||
|
||||
// 处理 BadRequestObjectResult 类型规范化处理
|
||||
if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult)
|
||||
{
|
||||
// 解析验证消息
|
||||
var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value);
|
||||
|
||||
var result = unifyResult.OnValidateFailed(context, validationMetadata);
|
||||
if (result != null) actionExecutedContext.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
IActionResult result = default;
|
||||
|
||||
// 检查是否是有效的结果(可进行规范化的结果)
|
||||
if (CheckVaildResult(actionExecutedContext.Result, out var data))
|
||||
{
|
||||
result = unifyResult.OnSucceeded(actionExecutedContext, data);
|
||||
}
|
||||
|
||||
// 如果是不能规范化的结果类型,则跳过
|
||||
if (result == null) return;
|
||||
|
||||
actionExecutedContext.Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取验证错误信息
|
||||
/// </summary>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
private static ValidationMetadata GetValidationMetadata(object errors)
|
||||
{
|
||||
ModelStateDictionary _modelState = null;
|
||||
object validationResults = null;
|
||||
(string message, string firstErrorMessage, string firstErrorProperty) = (default, default, default);
|
||||
|
||||
// 判断是否是集合类型
|
||||
if (errors is IEnumerable && errors is not string)
|
||||
{
|
||||
// 如果是模型验证字典类型
|
||||
if (errors is ModelStateDictionary modelState)
|
||||
{
|
||||
_modelState = modelState;
|
||||
// 将验证错误信息转换成字典并序列化成 Json
|
||||
validationResults = modelState.Where(u => modelState[u.Key].ValidationState == ModelValidationState.Invalid)
|
||||
.ToDictionary(u => u.Key, u => modelState[u.Key].Errors.Select(c => c.ErrorMessage).ToArray());
|
||||
}
|
||||
// 如果是 ValidationProblemDetails 特殊类型
|
||||
else if (errors is ValidationProblemDetails validation)
|
||||
{
|
||||
validationResults = validation.Errors
|
||||
.ToDictionary(u => u.Key, u => u.Value.ToArray());
|
||||
}
|
||||
// 如果是字典类型
|
||||
else if (errors is Dictionary<string, string[]> dicResults)
|
||||
{
|
||||
validationResults = dicResults;
|
||||
}
|
||||
|
||||
message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true
|
||||
});
|
||||
firstErrorMessage = (validationResults as Dictionary<string, string[]>).First().Value[0];
|
||||
firstErrorProperty = (validationResults as Dictionary<string, string[]>).First().Key;
|
||||
}
|
||||
// 其他类型
|
||||
else
|
||||
{
|
||||
validationResults = firstErrorMessage = message = errors?.ToString();
|
||||
}
|
||||
|
||||
return new ValidationMetadata
|
||||
{
|
||||
ValidationResult = validationResults,
|
||||
Message = message,
|
||||
ModelState = _modelState,
|
||||
FirstErrorProperty = firstErrorProperty,
|
||||
FirstErrorMessage = firstErrorMessage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否是有效的结果(可进行规范化的结果)
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private bool CheckVaildResult(IActionResult result, out object data)
|
||||
{
|
||||
data = default;
|
||||
|
||||
// 排除以下结果,跳过规范化处理
|
||||
var isDataResult = result switch
|
||||
{
|
||||
ViewResult => false,
|
||||
PartialViewResult => false,
|
||||
FileResult => false,
|
||||
ChallengeResult => false,
|
||||
SignInResult => false,
|
||||
SignOutResult => false,
|
||||
RedirectToPageResult => false,
|
||||
RedirectToRouteResult => false,
|
||||
RedirectResult => false,
|
||||
RedirectToActionResult => false,
|
||||
LocalRedirectResult => false,
|
||||
ForbidResult => false,
|
||||
ViewComponentResult => false,
|
||||
PageResult => false,
|
||||
NotFoundResult => false,
|
||||
NotFoundObjectResult => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// 目前支持返回值 ActionResult
|
||||
if (isDataResult) data = result switch
|
||||
{
|
||||
// 处理内容结果
|
||||
ContentResult content => content.Content,
|
||||
// 处理对象结果
|
||||
ObjectResult obj => obj.Value,
|
||||
// 处理 JSON 对象
|
||||
JsonResult json => json.Value,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return isDataResult;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 检查短路状态码(>=400)是否进行规范化处理
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="unifyResult"></param>
|
||||
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
|
||||
internal static bool CheckStatusCodeNonUnify(HttpContext context, out IUnifyResultProvider unifyResult)
|
||||
{
|
||||
// 获取终点路由特性
|
||||
var endpointFeature = context.Features.Get<IEndpointFeature>();
|
||||
if (endpointFeature == null) return (unifyResult = null) == null;
|
||||
|
||||
// 判断是否跳过规范化处理
|
||||
var isSkip = context.GetEndpoint()?.Metadata?.GetMetadata<NonUnifyAttribute>()!= null
|
||||
|| endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null
|
||||
|| context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase)
|
||||
|| context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isSkip == true) unifyResult = null;
|
||||
else
|
||||
{
|
||||
unifyResult = context.RequestServices.GetRequiredService<IUnifyResultProvider>();
|
||||
}
|
||||
|
||||
return unifyResult == null || isSkip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查请求成功是否进行规范化处理
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="isWebRequest"></param>
|
||||
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
|
||||
private bool CheckSucceededNonUnify(MethodInfo method, bool isWebRequest = true)
|
||||
{
|
||||
// 判断是否跳过规范化处理
|
||||
var isSkip = method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|
||||
|| method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData");
|
||||
|
||||
if (!isWebRequest)
|
||||
{
|
||||
return isSkip;
|
||||
}
|
||||
return isSkip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化结果提供器
|
||||
/// </summary>
|
||||
public interface IUnifyResultProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnSucceeded(ActionExecutedContext context, object data);
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 拦截返回状态码
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = default);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 禁止规范化处理
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
public sealed class NonUnifyAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// RESTful 风格返回值
|
||||
/// </summary>
|
||||
[Dependency(TryRegister = true)]
|
||||
[ExposeServices(typeof(IUnifyResultProvider))]
|
||||
public class RESTfulResultProvider : IUnifyResultProvider,ITransientDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置响应状态码
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
public static void SetResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
if (unifyResultSettings == null) return;
|
||||
|
||||
// 篡改响应状态码
|
||||
if (unifyResultSettings.AdaptStatusCodes != null && unifyResultSettings.AdaptStatusCodes.Length > 0)
|
||||
{
|
||||
var adaptStatusCode = unifyResultSettings.AdaptStatusCodes.FirstOrDefault(u => u[0] == statusCode);
|
||||
if (adaptStatusCode != null && adaptStatusCode.Length > 0 && adaptStatusCode[0] > 0)
|
||||
{
|
||||
context.Response.StatusCode = adaptStatusCode[1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果为 null,则所有请求错误的状态码设置为 200
|
||||
if (unifyResultSettings.Return200StatusCodes == null) context.Response.StatusCode = 200;
|
||||
// 否则只有里面的才设置为 200
|
||||
else if (unifyResultSettings.Return200StatusCodes.Contains(statusCode)) context.Response.StatusCode = 200;
|
||||
else { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnSucceeded(ActionExecutedContext context, object data)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败/业务异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 特定状态码返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
// 设置响应状态码
|
||||
SetResponseStatusCodes(context, statusCode, unifyResultSettings);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
// 处理 401 状态码
|
||||
case StatusCodes.Status401Unauthorized:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 Unauthorized"));
|
||||
break;
|
||||
// 处理 403 状态码
|
||||
case StatusCodes.Status403Forbidden:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 Forbidden"));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回 RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="succeeded"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
public static RESTfulResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)
|
||||
{
|
||||
return new RESTfulResult<object>
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Succeeded = succeeded,
|
||||
Data = data,
|
||||
Errors = errors,
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class RESTfulResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int? StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行成功
|
||||
/// </summary>
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public object Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public object Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳
|
||||
/// </summary>
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
|
||||
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化接口
|
||||
/// 由于太多人反应,想兼容一套类似furion的返回情况,200状态码包一层更符合国内习惯,既然如此,不如直接搬过来
|
||||
/// </summary>
|
||||
public static class UnifyResultExtensions
|
||||
{
|
||||
public static IServiceCollection AddFurionUnifyResultApi(this IServiceCollection services)
|
||||
{
|
||||
//成功规范接口
|
||||
services.AddTransient<SucceededUnifyResultFilter>();
|
||||
//异常规范接口
|
||||
services.AddTransient<FriendlyExceptionFilter>();
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Filters.AddService<SucceededUnifyResultFilter>(99);
|
||||
options.Filters.AddService<FriendlyExceptionFilter>(100);
|
||||
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化配置选项
|
||||
/// </summary>
|
||||
public sealed class UnifyResultSettingsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置返回 200 状态码列表
|
||||
/// <para>默认:401,403,如果设置为 null,则标识所有状态码都返回 200 </para>
|
||||
/// </summary>
|
||||
public int[] Return200StatusCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适配(篡改)Http 状态码(只支持短路状态码,比如 401,403,500 等)
|
||||
/// </summary>
|
||||
public int[][] AdaptStatusCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持 MVC 控制台规范化处理
|
||||
/// </summary>
|
||||
public bool? SupportMvcController { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项后期配置
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public void PostConfigure(UnifyResultSettingsOptions options, IConfiguration configuration)
|
||||
{
|
||||
options.Return200StatusCodes ??= new[] { 401, 403 };
|
||||
options.SupportMvcController ??= false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 验证信息元数据
|
||||
/// </summary>
|
||||
public sealed class ValidationMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证结果
|
||||
/// </summary>
|
||||
/// <remarks>返回字典或字符串类型</remarks>
|
||||
public object ValidationResult { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常消息
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态
|
||||
/// </summary>
|
||||
public ModelStateDictionary ModelState { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public object ErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(没被复写过的 ErrorCode )
|
||||
/// </summary>
|
||||
public object OriginErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int? StatusCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首个错误属性
|
||||
/// </summary>
|
||||
public string FirstErrorProperty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首个错误消息
|
||||
/// </summary>
|
||||
public string FirstErrorMessage { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外数据
|
||||
/// </summary>
|
||||
public object Data { get; internal set; }
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.AspNetCore.WebClientInfo;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
using Yi.Framework.AspNetCore.Mvc;
|
||||
@@ -22,6 +23,11 @@ namespace Yi.Framework.AspNetCore
|
||||
)]
|
||||
public class YiFrameworkAspNetCoreModule : AbpModule
|
||||
{
|
||||
|
||||
public override void PostConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var services = context.Services;
|
||||
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
|
||||
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Hangfire.Server;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Threading;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
|
||||
{
|
||||
private const string CurrentJobUow = "HangfireUnitOfWork";
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
public void OnPerforming(PerformingContext context)
|
||||
{
|
||||
var uow = _unitOfWorkManager.Begin();
|
||||
context.Items.Add(CurrentJobUow, uow);
|
||||
}
|
||||
|
||||
public void OnPerformed(PerformedContext context)
|
||||
{
|
||||
AsyncHelper.RunSync(()=>OnPerformedAsync(context));
|
||||
}
|
||||
|
||||
private async Task OnPerformedAsync(PerformedContext context)
|
||||
{
|
||||
if (context.Items.TryGetValue(CurrentJobUow, out var obj)
|
||||
&& obj is IUnitOfWork uow)
|
||||
{
|
||||
if (context.Exception == null && !uow.IsCompleted)
|
||||
{
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await uow.RollbackAsync();
|
||||
}
|
||||
uow.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.BackgroundWorkers;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
|
||||
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
|
||||
{
|
||||
public override void PreConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
|
||||
}
|
||||
|
||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||
{
|
||||
//定时任务自动注入,Abp默认只有在Quartz才实现
|
||||
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
|
||||
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
|
||||
|
||||
foreach (var work in works)
|
||||
{
|
||||
//如果为空,默认使用服务器本地utc时间
|
||||
work.TimeZone ??= TimeZoneInfo.Local;
|
||||
await backgroundWorkerManager.AddAsync(work);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
var services = context.ServiceProvider;
|
||||
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
|
||||
{
|
||||
protected override bool IsConventionalRegistrationDisabled(Type type)
|
||||
{
|
||||
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type);
|
||||
}
|
||||
|
||||
protected override List<Type> GetExposedServiceTypes(Type type)
|
||||
{
|
||||
return new List<Type>()
|
||||
{
|
||||
typeof(IHangfireBackgroundWorker)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using Hangfire.Dashboard;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Users;
|
||||
|
||||
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||
|
||||
public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
|
||||
{
|
||||
private const string Bearer = "Bearer: ";
|
||||
private string RequireUser { get; set; } = "cc";
|
||||
private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10);
|
||||
private IServiceProvider _serviceProvider;
|
||||
|
||||
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public YiTokenAuthorizationFilter SetRequireUser(string userName)
|
||||
{
|
||||
RequireUser = userName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime)
|
||||
{
|
||||
ExpiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool Authorize(DashboardContext context)
|
||||
{
|
||||
var httpContext = context.GetHttpContext();
|
||||
var _currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
|
||||
//如果验证通过,设置cookies
|
||||
if (_currentUser.IsAuthenticated)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟
|
||||
};
|
||||
|
||||
|
||||
var authorization = httpContext.Request.Headers["Authorization"].ToString();
|
||||
if (!string.IsNullOrWhiteSpace(authorization))
|
||||
{
|
||||
var token = httpContext.Request.Headers["Authorization"].ToString().Substring(Bearer.Length - 1);
|
||||
httpContext.Response.Cookies.Append("Token", token, cookieOptions);
|
||||
}
|
||||
|
||||
if (_currentUser.UserName == RequireUser)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
SetChallengeResponse(httpContext);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetChallengeResponse(HttpContext httpContext)
|
||||
{
|
||||
httpContext.Response.StatusCode = 401;
|
||||
httpContext.Response.ContentType = "text/html; charset=utf-8";
|
||||
string html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Token 输入</title>
|
||||
<script>
|
||||
function sendToken() {
|
||||
// 获取输入的 token
|
||||
var token = document.getElementById("tokenInput").value;
|
||||
token = token.replace('Bearer ','');
|
||||
// 构建请求 URL
|
||||
var url = "/hangfire";
|
||||
// 发送 GET 请求
|
||||
fetch(url,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // 设置内容类型为 JSON
|
||||
'Authorization': 'Bearer '+encodeURIComponent(token), // 设置授权头,例如使用 Bearer token
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.text(); // 或使用 response.json() 如果返回的是 JSON
|
||||
}
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(data => {
|
||||
// 处理成功返回的数据
|
||||
document.open();
|
||||
document.write(data);
|
||||
document.close();
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
alert("请求失败: " + error.message);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="text-align: center;">
|
||||
<h1>Yi-hangfire</h1>
|
||||
<h1>输入您的Token,我们将验证您是否为管理员</h1>
|
||||
<textarea id="tokenInput" placeholder="请输入 token" style="width: 80%;height: 120px;margin: 0 10%;"></textarea>
|
||||
<button onclick="sendToken()">校验</button>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
httpContext.Response.WriteAsync(html);
|
||||
}
|
||||
|
||||
public Task<bool> AuthorizeAsync(DashboardContext context)
|
||||
{
|
||||
return Task.FromResult(Authorize(context));
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,10 @@ namespace Yi.Framework.Core.Enums
|
||||
/// </summary>
|
||||
public enum FileTypeEnum
|
||||
{
|
||||
File,
|
||||
Image,
|
||||
Thumbnail,
|
||||
Excel,
|
||||
Temp
|
||||
file,
|
||||
image,
|
||||
thumbnail,
|
||||
excel,
|
||||
temp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 double CPURate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总内存 GB
|
||||
/// </summary>
|
||||
@@ -306,20 +377,25 @@ namespace Yi.Framework.Core.Helper
|
||||
/// <returns></returns>
|
||||
public MemoryMetrics GetUnixMetrics()
|
||||
{
|
||||
string output = ShellHelper.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'");
|
||||
string output = ShellHelper.Bash(@"
|
||||
# 从 /proc/meminfo 文件中提取总内存
|
||||
total_mem=$(cat /proc/meminfo | grep -i ""MemTotal"" | awk '{print $2}')
|
||||
# 从 /proc/meminfo 文件中提取剩余内存
|
||||
free_mem=$(cat /proc/meminfo | grep -i ""MemFree"" | awk '{print $2}')
|
||||
# 显示提取的信息
|
||||
echo $total_mem $used_mem $free_mem
|
||||
");
|
||||
var metrics = new MemoryMetrics();
|
||||
var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (lines.Length <= 0) return metrics;
|
||||
|
||||
if (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;
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace Yi.Framework.Core.Helper
|
||||
{
|
||||
var extension = Path.GetExtension(fileName);
|
||||
if (ImageType.Contains(extension.ToLower()))
|
||||
return FileTypeEnum.Image;
|
||||
return FileTypeEnum.File;
|
||||
return FileTypeEnum.image;
|
||||
return FileTypeEnum.file;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using SqlSugar;
|
||||
using ArgumentException = System.ArgumentException;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
@@ -19,7 +20,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
/// </summary>
|
||||
public bool EnabledDbSeed { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开启驼峰转下划线
|
||||
/// </summary>
|
||||
public bool EnableUnderLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启codefirst
|
||||
@@ -50,6 +54,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
/// 开启Saas多租户
|
||||
/// </summary>
|
||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
public interface ISqlSugarDbConnectionCreator
|
||||
{
|
||||
DbConnOptions Options { get; }
|
||||
Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
|
||||
Action<object, DataAfterModel> DataExecuted { get; set; }
|
||||
Action<object, DataFilterModel> DataExecuting { get; set; }
|
||||
Action<string, SugarParameter[]> OnLogExecuting { get; set; }
|
||||
Action<string, SugarParameter[]> OnLogExecuted { get; set; }
|
||||
Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
|
||||
|
||||
ConnectionConfig Build(Action<ConnectionConfig>? action = null);
|
||||
void SetDbAop(ISqlSugarClient currentDb);
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
public interface ISqlSugarDbContext
|
||||
{
|
||||
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||
/// <summary>
|
||||
/// SqlSugarDb
|
||||
/// </summary>
|
||||
ISqlSugarClient SqlSugarClient { get; }
|
||||
DbConnOptions Options { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 数据库备份
|
||||
/// </summary>
|
||||
void BackupDataBase();
|
||||
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
public interface ISqlSugarDbContextDependencies
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行顺序
|
||||
/// </summary>
|
||||
int ExecutionOrder { get; }
|
||||
|
||||
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
|
||||
void DataExecuted(object oldValue, DataAfterModel entityInfo);
|
||||
void DataExecuting(object oldValue, DataFilterModel entityInfo);
|
||||
|
||||
void OnLogExecuting(string sql, SugarParameter[] pars);
|
||||
void OnLogExecuted(string sql, SugarParameter[] pars);
|
||||
|
||||
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
|
||||
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity> where TEntity : class, IEntity,new ()
|
||||
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity>,IUnitOfWorkEnabled where TEntity : class, IEntity,new ()
|
||||
{
|
||||
ISqlSugarClient _Db { get; }
|
||||
ISugarQueryable<TEntity> _DbQueryable { get; }
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Entities.Events;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Uow;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
public class DefaultSqlSugarDbContext : SqlSugarDbContext
|
||||
{
|
||||
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||
public IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
|
||||
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
|
||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||
|
||||
protected IEntityChangeEventHelper EntityChangeEventHelper =>
|
||||
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
|
||||
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
if (IsSoftDeleteFilterEnabled)
|
||||
{
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
|
||||
}
|
||||
|
||||
if (IsMultiTenantFilterEnabled)
|
||||
{
|
||||
//表达式里只能有具体值,不能运算
|
||||
var expressionCurrentTenant = CurrentTenant.Id ?? null;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
//审计日志
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.UpdateByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
|
||||
{
|
||||
if (!DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
{
|
||||
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.InsertByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
|
||||
{
|
||||
//类型为guid
|
||||
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
//主键为空或者为默认最小值
|
||||
if (Guid.Empty.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(GuidGenerator.Create());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
|
||||
{
|
||||
//为空或者为默认最小值
|
||||
if (DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
{
|
||||
//类型为guid
|
||||
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||
{
|
||||
if (CurrentUser.Id is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
{
|
||||
if (CurrentTenant.Id is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentTenant.Id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//实体变更领域事件
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.UpdateByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//软删除,发布的是删除事件
|
||||
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
||||
{
|
||||
if (softDelete.IsDeleted == true)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DataFilterType.DeleteByObject:
|
||||
// if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
// {
|
||||
//这里sqlsugar有个特殊,删除会返回批量的结果
|
||||
//这里sqlsugar有第二个特殊,删除事件是行级事件
|
||||
if (entityInfo.EntityValue is IEnumerable entityValues)
|
||||
{
|
||||
foreach (var entityValue in entityValues)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//实体领域事件-所有操作类型
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
var eventReport = CreateEventReport(entityInfo.EntityValue);
|
||||
PublishEntityEvents(eventReport);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==========Yi-SQL执行:==========");
|
||||
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
|
||||
sb.AppendLine("===============================");
|
||||
Logger.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||
{
|
||||
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
|
||||
{
|
||||
entityColumnInfo.IsEnableUpdateVersionValidation = true;
|
||||
}
|
||||
|
||||
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
|
||||
{
|
||||
entityColumnInfo.IsIgnore = true;
|
||||
}
|
||||
|
||||
if (propertyInfo.Name == nameof(Entity<object>.Id))
|
||||
{
|
||||
entityColumnInfo.IsPrimarykey = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建领域事件报告
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual EntityEventReport? CreateEventReport(object entity)
|
||||
{
|
||||
var eventReport = new EntityEventReport();
|
||||
|
||||
//判断是否为领域事件-聚合根
|
||||
var generatesDomainEventsEntity = entity as IGeneratesDomainEvents;
|
||||
if (generatesDomainEventsEntity == null)
|
||||
{
|
||||
return eventReport;
|
||||
}
|
||||
|
||||
var localEvents = generatesDomainEventsEntity.GetLocalEvents()?.ToArray();
|
||||
if (localEvents != null && localEvents.Any())
|
||||
{
|
||||
eventReport.DomainEvents.AddRange(
|
||||
localEvents.Select(
|
||||
eventRecord => new DomainEventEntry(
|
||||
entity,
|
||||
eventRecord.EventData,
|
||||
eventRecord.EventOrder
|
||||
)
|
||||
)
|
||||
);
|
||||
generatesDomainEventsEntity.ClearLocalEvents();
|
||||
}
|
||||
|
||||
var distributedEvents = generatesDomainEventsEntity.GetDistributedEvents()?.ToArray();
|
||||
if (distributedEvents != null && distributedEvents.Any())
|
||||
{
|
||||
eventReport.DistributedEvents.AddRange(
|
||||
distributedEvents.Select(
|
||||
eventRecord => new DomainEventEntry(
|
||||
entity,
|
||||
eventRecord.EventData,
|
||||
eventRecord.EventOrder)
|
||||
)
|
||||
);
|
||||
generatesDomainEventsEntity.ClearDistributedEvents();
|
||||
}
|
||||
|
||||
return eventReport;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布领域事件
|
||||
/// </summary>
|
||||
/// <param name="changeReport"></param>
|
||||
private void PublishEntityEvents(EntityEventReport changeReport)
|
||||
{
|
||||
foreach (var localEvent in changeReport.DomainEvents)
|
||||
{
|
||||
UnitOfWorkManager.Current?.AddOrReplaceLocalEvent(
|
||||
new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder)
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var distributedEvent in changeReport.DistributedEvents)
|
||||
{
|
||||
UnitOfWorkManager.Current?.AddOrReplaceDistributedEvent(
|
||||
new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Nito.AsyncEx;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Linq;
|
||||
@@ -13,9 +17,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
|
||||
{
|
||||
public ISqlSugarClient _Db => GetDbContextAsync().Result;
|
||||
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
|
||||
|
||||
public ISugarQueryable<TEntity> _DbQueryable => GetDbContextAsync().Result.Queryable<TEntity>();
|
||||
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
|
||||
|
||||
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
|
||||
public IAsyncQueryableExecuter AsyncExecuter { get; }
|
||||
@@ -33,9 +37,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
/// <returns></returns>
|
||||
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
|
||||
{
|
||||
|
||||
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
|
||||
//await Console.Out.WriteLineAsync("获取的id:" + db.ContextID);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -372,6 +374,20 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
|
||||
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
|
||||
{
|
||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>())//带版本号乐观锁更新
|
||||
{
|
||||
try
|
||||
{
|
||||
int num = await (await GetDbSimpleClientAsync())
|
||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||
return num>0;
|
||||
}
|
||||
catch (VersionExceptions ex)
|
||||
{
|
||||
|
||||
throw new AbpDbConcurrencyException($"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||
}
|
||||
}
|
||||
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public static class SqlSugarCoreExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增db对象,可支持多个
|
||||
/// </summary>
|
||||
/// <param name="service"></param>
|
||||
/// <param name="serviceLifetime"></param>
|
||||
/// <typeparam name="TDbContext"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies
|
||||
{
|
||||
service.AddTransient<ISqlSugarDbContextDependencies, TDbContext>();
|
||||
return service;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增db对象,可支持多个
|
||||
/// </summary>
|
||||
/// <param name="service"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <typeparam name="TDbContext"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, Action<DbConnOptions> options) where TDbContext : class, ISqlSugarDbContextDependencies
|
||||
{
|
||||
service.Configure<DbConnOptions>(options.Invoke);
|
||||
service.AddYiDbContext<TDbContext>();
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
|
||||
public class SqlSugarDbConnectionCreator: ISqlSugarDbConnectionCreator,ITransientDependency
|
||||
{
|
||||
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
|
||||
{
|
||||
Options = options.Value;
|
||||
}
|
||||
public DbConnOptions Options { get; }
|
||||
|
||||
public void SetDbAop(ISqlSugarClient currentDb)
|
||||
{
|
||||
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
|
||||
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
|
||||
currentDb.Aop.DataExecuting = this.DataExecuting;
|
||||
currentDb.Aop.DataExecuted = this.DataExecuted;
|
||||
OnSqlSugarClientConfig(currentDb);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ConnectionConfig Build(Action<ConnectionConfig>? action=null)
|
||||
{
|
||||
var dbConnOptions = Options;
|
||||
#region 组装options
|
||||
if (dbConnOptions.DbType is null)
|
||||
{
|
||||
throw new ArgumentException("DbType配置为空");
|
||||
}
|
||||
var slavaConFig = new List<SlaveConnectionConfig>();
|
||||
if (dbConnOptions.EnabledReadWrite)
|
||||
{
|
||||
if (dbConnOptions.ReadUrl is null)
|
||||
{
|
||||
throw new ArgumentException("读写分离为空");
|
||||
}
|
||||
|
||||
var readCon = dbConnOptions.ReadUrl;
|
||||
|
||||
readCon.ForEach(s =>
|
||||
{
|
||||
//如果是动态saas分库,这里的连接串都不能写死,需要动态添加,这里只配置共享库的连接
|
||||
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 组装连接config
|
||||
var connectionConfig = new ConnectionConfig()
|
||||
{
|
||||
ConfigId= ConnectionStrings.DefaultConnectionStringName,
|
||||
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
|
||||
ConnectionString = dbConnOptions.Url,
|
||||
IsAutoCloseConnection = true,
|
||||
SlaveConnectionConfigs = slavaConFig,
|
||||
//设置codefirst非空值判断
|
||||
ConfigureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityService = (c, p) =>
|
||||
{
|
||||
if (new NullabilityInfoContext()
|
||||
.Create(c).WriteState is NullabilityState.Nullable)
|
||||
{
|
||||
p.IsNullable = true;
|
||||
}
|
||||
|
||||
EntityService(c, p);
|
||||
}
|
||||
},
|
||||
//这里多租户有个坑,无效的
|
||||
AopEvents = new AopEvents
|
||||
{
|
||||
DataExecuted = DataExecuted,
|
||||
DataExecuting = DataExecuting,
|
||||
OnLogExecuted = OnLogExecuted,
|
||||
OnLogExecuting = OnLogExecuting
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (action is not null)
|
||||
{
|
||||
action.Invoke(connectionConfig);
|
||||
}
|
||||
#endregion
|
||||
return connectionConfig;
|
||||
}
|
||||
[DisablePropertyInjection]
|
||||
public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<object, DataAfterModel> DataExecuted { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<object, DataFilterModel> DataExecuting { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,376 +1,49 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Security.Policy;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Reflection;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Entities.Events;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
|
||||
{
|
||||
public class SqlSugarDbContext : ISqlSugarDbContext
|
||||
protected IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
|
||||
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
|
||||
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
|
||||
|
||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||
|
||||
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
public AbpDbConnectionOptions ConnectionOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbConnectionOptions>>().Value;
|
||||
|
||||
public ISerializeService SerializeService=> LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||
|
||||
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
SqlSugarClient = sqlSugarClient;
|
||||
}
|
||||
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
|
||||
_dbConnectionCreator = connectionCreator;
|
||||
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
|
||||
connectionCreator.EntityService = EntityService;
|
||||
connectionCreator.DataExecuting = DataExecuting;
|
||||
connectionCreator.DataExecuted = DataExecuted;
|
||||
connectionCreator.OnLogExecuting = OnLogExecuting;
|
||||
connectionCreator.OnLogExecuted = OnLogExecuted;
|
||||
SqlSugarClient = new SqlSugarClient(connectionCreator.Build(action: options =>
|
||||
{
|
||||
options.ConnectionString = GetCurrentConnectionString();
|
||||
options.DbType = GetCurrentDbType();
|
||||
}));
|
||||
connectionCreator.SetDbAop(SqlSugarClient);
|
||||
//替换默认序列化器
|
||||
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// db切换多库支持
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string GetCurrentConnectionString()
|
||||
{
|
||||
var defautlUrl = Options.Url ?? ConnectionOptions.GetConnectionStringOrNull(ConnectionStrings.DefaultConnectionStringName);
|
||||
//如果未开启多租户,返回db url 或者 默认连接字符串
|
||||
if (!Options.EnabledSaasMultiTenancy)
|
||||
{
|
||||
return defautlUrl;
|
||||
}
|
||||
|
||||
//开启了多租户
|
||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
||||
var connectionString = connectionStringResolver.ResolveAsync().GetAwaiter().GetResult();
|
||||
|
||||
|
||||
//没有检测到使用多租户功能,默认使用默认库即可
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
|
||||
connectionString = defautlUrl;
|
||||
}
|
||||
return connectionString!;
|
||||
}
|
||||
|
||||
protected virtual DbType GetCurrentDbType()
|
||||
{
|
||||
if (CurrentTenant.Name is not null)
|
||||
{
|
||||
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
|
||||
if (dbTypeFromTenantName is not null)
|
||||
{
|
||||
return dbTypeFromTenantName.Value;
|
||||
}
|
||||
}
|
||||
Volo.Abp.Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
return Options.DbType!.Value;
|
||||
}
|
||||
|
||||
//根据租户name进行匹配db类型: Test_Sqlite,[来自AI]
|
||||
private DbType? GetDbTypeFromTenantName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找下划线的位置
|
||||
int underscoreIndex = name.LastIndexOf('_');
|
||||
|
||||
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取 枚举 部分
|
||||
string enumString = name.Substring(underscoreIndex + 1);
|
||||
|
||||
// 尝试将 尾缀 转换为枚举
|
||||
if (Enum.TryParse<DbType>(enumString, out DbType result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 条件不满足时返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 上下文对象扩展
|
||||
/// </summary>
|
||||
/// <param name="sqlSugarClient"></param>
|
||||
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
//需自定义扩展
|
||||
if (IsSoftDeleteFilterEnabled)
|
||||
{
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
|
||||
}
|
||||
if (IsMultiTenantFilterEnabled)
|
||||
{
|
||||
//表达式不能放方法
|
||||
Guid? tenantId = CurrentTenant?.Id;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == tenantId);
|
||||
}
|
||||
CustomDataFilter(sqlSugarClient);
|
||||
}
|
||||
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
|
||||
}
|
||||
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
/// <param name="oldValue"></param>
|
||||
/// <param name="entityInfo"></param>
|
||||
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
//审计日志
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.UpdateByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
|
||||
{
|
||||
if (!DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
|
||||
{
|
||||
//主键为空或者为默认最小值
|
||||
if (Guid.Empty.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(GuidGenerator.Create());
|
||||
}
|
||||
}
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
|
||||
{
|
||||
//为空或者为默认最小值
|
||||
if (oldValue is null || DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
|
||||
//插入时,需要租户id,先预留
|
||||
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
{
|
||||
if (CurrentTenant is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentTenant.Id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//领域事件
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
break;
|
||||
case DataFilterType.UpdateByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//软删除,发布的是删除事件
|
||||
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
||||
{
|
||||
if (softDelete.IsDeleted == true)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case DataFilterType.DeleteByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//这里sqlsugar有个特殊,删除会返回批量的结果
|
||||
if (entityInfo.EntityValue is IEnumerable entityValues)
|
||||
{
|
||||
foreach (var entityValue in entityValues)
|
||||
{
|
||||
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="pars"></param>
|
||||
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==========Yi-SQL执行:==========");
|
||||
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
|
||||
sb.AppendLine("===============================");
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="pars"></param>
|
||||
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体配置
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="column"></param>
|
||||
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
|
||||
{
|
||||
if (property.Name == "ConcurrencyStamp")
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
if (property.PropertyType == typeof(ExtraPropertyDictionary))
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
if (property.Name == nameof(Entity<object>.Id))
|
||||
{
|
||||
column.IsPrimarykey = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void BackupDataBase()
|
||||
{
|
||||
string directoryName = "database_backup";
|
||||
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
switch (Options.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
//MySql
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.sql");//mysql 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.Sqlite:
|
||||
//Sqlite
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.SqlServer:
|
||||
//SqlServer
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.bak"/*服务器路径*/);//第一个参数库名
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("其他数据库备份未实现");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
this.LazyServiceProvider = lazyServiceProvider;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected ISqlSugarClient SqlSugarClient { get;private set; }
|
||||
public int ExecutionOrder => 0;
|
||||
|
||||
public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
SqlSugarClient = sqlSugarClient;
|
||||
CustomDataFilter(sqlSugarClient);
|
||||
}
|
||||
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Threading;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
using Check = Volo.Abp.Check;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public class SqlSugarDbContextFactory : ISqlSugarDbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
|
||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||
|
||||
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
|
||||
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
|
||||
|
||||
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
|
||||
|
||||
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
|
||||
var connectionString = GetCurrentConnectionString();
|
||||
|
||||
var connectionConfig =BuildConnectionConfig(action: options =>
|
||||
{
|
||||
options.ConnectionString = connectionString;
|
||||
options.DbType = GetCurrentDbType();
|
||||
});
|
||||
// var connectionConfig = ConnectionConfigCache.GetOrAdd(connectionString, (_) =>
|
||||
// BuildConnectionConfig(action: options =>
|
||||
// {
|
||||
// options.ConnectionString = connectionString;
|
||||
// options.DbType = GetCurrentDbType();
|
||||
// }));
|
||||
SqlSugarClient = new SqlSugarClient(connectionConfig);
|
||||
//生命周期,以下都可以直接使用sqlsugardb了
|
||||
|
||||
// Aop及多租户连接字符串和类型,需要单独设置
|
||||
// Aop操作不能进行缓存
|
||||
SetDbAop(SqlSugarClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建Aop-sqlsugaraop在多租户模式中,需单独设置
|
||||
/// </summary>
|
||||
/// <param name="sqlSugarClient"></param>
|
||||
protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
//替换默认序列化器
|
||||
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||
|
||||
//将所有,ISqlSugarDbContextDependencies进行累加
|
||||
Action<string, SugarParameter[]> onLogExecuting = null;
|
||||
Action<string, SugarParameter[]> onLogExecuted = null;
|
||||
Action<object, DataFilterModel> dataExecuting = null;
|
||||
Action<object, DataAfterModel> dataExecuted = null;
|
||||
Action<ISqlSugarClient> onSqlSugarClientConfig = null;
|
||||
|
||||
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
|
||||
{
|
||||
onLogExecuting += dependency.OnLogExecuting;
|
||||
onLogExecuted += dependency.OnLogExecuted;
|
||||
dataExecuting += dependency.DataExecuting;
|
||||
dataExecuted += dependency.DataExecuted;
|
||||
|
||||
onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig;
|
||||
}
|
||||
|
||||
//最先存放db操作
|
||||
onSqlSugarClientConfig(sqlSugarClient);
|
||||
|
||||
sqlSugarClient.Aop.OnLogExecuting =onLogExecuting;
|
||||
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
|
||||
|
||||
sqlSugarClient.Aop.DataExecuting =dataExecuting;
|
||||
sqlSugarClient.Aop.DataExecuted =dataExecuted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建连接配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig>? action = null)
|
||||
{
|
||||
var dbConnOptions = Options;
|
||||
|
||||
#region 组装options
|
||||
|
||||
if (dbConnOptions.DbType is null)
|
||||
{
|
||||
throw new ArgumentException("DbType配置为空");
|
||||
}
|
||||
|
||||
var slavaConFig = new List<SlaveConnectionConfig>();
|
||||
if (dbConnOptions.EnabledReadWrite)
|
||||
{
|
||||
if (dbConnOptions.ReadUrl is null)
|
||||
{
|
||||
throw new ArgumentException("读写分离为空");
|
||||
}
|
||||
|
||||
var readCon = dbConnOptions.ReadUrl;
|
||||
|
||||
readCon.ForEach(s =>
|
||||
{
|
||||
//如果是动态saas分库,这里的连接串都不能写死,需要动态添加,这里只配置共享库的连接
|
||||
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 组装连接config
|
||||
|
||||
var connectionConfig = new ConnectionConfig()
|
||||
{
|
||||
ConfigId = ConnectionStrings.DefaultConnectionStringName,
|
||||
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
|
||||
ConnectionString = dbConnOptions.Url,
|
||||
IsAutoCloseConnection = true,
|
||||
SlaveConnectionConfigs = slavaConFig,
|
||||
//设置codefirst非空值判断
|
||||
ConfigureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
// 处理表
|
||||
EntityNameService = (type, entity) =>
|
||||
{
|
||||
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
|
||||
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
|
||||
},
|
||||
EntityService = (c, p) =>
|
||||
{
|
||||
if (new NullabilityInfoContext()
|
||||
.Create(c).WriteState is NullabilityState.Nullable)
|
||||
{
|
||||
p.IsNullable = true;
|
||||
}
|
||||
|
||||
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
|
||||
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName); // 驼峰转下划线
|
||||
|
||||
//将所有,ISqlSugarDbContextDependencies的EntityService进行累加
|
||||
//额外的实体服务需要这里配置,
|
||||
|
||||
Action<PropertyInfo, EntityColumnInfo> entityService = null;
|
||||
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
|
||||
{
|
||||
entityService += dependency.EntityService;
|
||||
}
|
||||
|
||||
entityService(c, p);
|
||||
}
|
||||
},
|
||||
//这里多租户有个坑,这里配置是无效的
|
||||
// AopEvents = new AopEvents
|
||||
// {
|
||||
// DataExecuted = DataExecuted,
|
||||
// DataExecuting = DataExecuting,
|
||||
// OnLogExecuted = OnLogExecuted,
|
||||
// OnLogExecuting = OnLogExecuting
|
||||
// }
|
||||
};
|
||||
|
||||
if (action is not null)
|
||||
{
|
||||
action.Invoke(connectionConfig);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return connectionConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// db切换多库支持
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string GetCurrentConnectionString()
|
||||
{
|
||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
||||
var connectionString =
|
||||
AsyncHelper.RunSync(() => connectionStringResolver.ResolveAsync());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
Check.NotNull(Options.Url, "dbUrl未配置");
|
||||
}
|
||||
|
||||
return connectionString!;
|
||||
}
|
||||
|
||||
protected virtual DbType GetCurrentDbType()
|
||||
{
|
||||
if (CurrentTenant.Name is not null)
|
||||
{
|
||||
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
|
||||
if (dbTypeFromTenantName is not null)
|
||||
{
|
||||
return dbTypeFromTenantName.Value;
|
||||
}
|
||||
}
|
||||
|
||||
Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
return Options.DbType!.Value;
|
||||
}
|
||||
|
||||
//根据租户name进行匹配db类型: Test_Sqlite,[来自AI]
|
||||
private DbType? GetDbTypeFromTenantName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找下划线的位置
|
||||
int underscoreIndex = name.LastIndexOf('_');
|
||||
|
||||
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取 枚举 部分
|
||||
string enumString = name.Substring(underscoreIndex + 1);
|
||||
|
||||
// 尝试将 尾缀 转换为枚举
|
||||
if (Enum.TryParse<DbType>(enumString, out DbType result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 条件不满足时返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void BackupDataBase()
|
||||
{
|
||||
string directoryName = "database_backup";
|
||||
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
|
||||
switch (Options.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
//MySql
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
|
||||
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.Sqlite:
|
||||
//Sqlite
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.SqlServer:
|
||||
//SqlServer
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
|
||||
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("其他数据库备份未实现");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public static class SqlsugarCoreExtensions
|
||||
{
|
||||
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
|
||||
{
|
||||
service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
|
||||
return service;
|
||||
}
|
||||
public static IServiceCollection TryAddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
|
||||
{
|
||||
service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
|
||||
return service;
|
||||
}
|
||||
|
||||
|
||||
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, Action<DbConnOptions> options) where DbContext : class, ISqlSugarDbContext
|
||||
{
|
||||
|
||||
service.Configure<DbConnOptions>(ops =>
|
||||
{
|
||||
options.Invoke(ops);
|
||||
});
|
||||
service.AddYiDbContext<DbContext>();
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,4 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
DbContext = dbContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
|
||||
public ISqlSugarDbContext GetDbContext()
|
||||
{
|
||||
|
||||
return _sqlsugarDbContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
{
|
||||
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
||||
{
|
||||
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
@@ -28,8 +26,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
IConnectionStringResolver connectionStringResolver,
|
||||
ICancellationTokenProvider cancellationTokenProvider,
|
||||
ICurrentTenant currentTenant,
|
||||
ISqlSugarDbConnectionCreator dbConnectionCreator
|
||||
ICurrentTenant currentTenant
|
||||
)
|
||||
{
|
||||
UnitOfWorkManager = unitOfWorkManager;
|
||||
@@ -37,10 +34,8 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
CancellationTokenProvider = cancellationTokenProvider;
|
||||
CurrentTenant = currentTenant;
|
||||
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
|
||||
_dbConnectionCreator = dbConnectionCreator;
|
||||
}
|
||||
|
||||
//private static object _databaseApiLock = new object();
|
||||
|
||||
public virtual async Task<TDbContext> GetDbContextAsync()
|
||||
{
|
||||
|
||||
@@ -48,23 +43,20 @@ 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;
|
||||
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/)
|
||||
if (unitOfWork == null )
|
||||
{
|
||||
var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
|
||||
//提高体验,取消工作单元强制性
|
||||
//throw new AbpException("A DbContext can only be created inside a unit of work!");
|
||||
//var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
|
||||
//如果不启用工作单元,创建一个新的db,不开启事务即可
|
||||
return dbContext;
|
||||
//return dbContext;
|
||||
|
||||
//2024-11-30,改回强制性使用工作单元,否则容易造成歧义
|
||||
throw new AbpException("DbContext 只能在工作单元内工作,当前DbContext没有工作单元,如需创建新线程并发操作,请手动创建工作单元");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//尝试当前工作单元获取db
|
||||
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.Domain;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.Guids;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
using Yi.Framework.SqlSugarCore.Repositories;
|
||||
using Yi.Framework.SqlSugarCore.Uow;
|
||||
@@ -25,9 +23,35 @@ 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);
|
||||
var dbConnOptions = new DbConnOptions();
|
||||
section.Bind(dbConnOptions);
|
||||
|
||||
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
|
||||
//很多人遗漏了这一点,不同的数据库,对于主键的使用规约不一样,需要根据数据库进行判断
|
||||
SequentialGuidType guidType;
|
||||
switch (dbConnOptions.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
case DbType.PostgreSQL:
|
||||
guidType= SequentialGuidType.SequentialAsString;
|
||||
break;
|
||||
case DbType.SqlServer:
|
||||
guidType = SequentialGuidType.SequentialAtEnd;
|
||||
break;
|
||||
case DbType.Oracle:
|
||||
guidType = SequentialGuidType.SequentialAsBinary;
|
||||
break;
|
||||
default:
|
||||
guidType = SequentialGuidType.SequentialAtEnd;
|
||||
break;
|
||||
}
|
||||
Configure<AbpSequentialGuidGeneratorOptions>(options =>
|
||||
{
|
||||
options.DefaultSequentialGuidType = guidType;
|
||||
});
|
||||
|
||||
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContextFactory>();
|
||||
|
||||
//不开放sqlsugarClient
|
||||
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
|
||||
@@ -40,7 +64,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
|
||||
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
|
||||
//替换Sqlsugar默认序列化器,用来解决.Select()不支持嵌套对象/匿名对象的非公有访问器 值无法绑定,如Id属性
|
||||
context.Services.AddSingleton<ISerializeService,SqlSugarNonPublicSerializer> ();
|
||||
context.Services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
|
||||
|
||||
var dbConfig = section.Get<DbConnOptions>();
|
||||
//将默认db传递给abp连接字符串模块
|
||||
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
|
||||
|
||||
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -50,8 +80,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 +95,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
sb.AppendLine("===============================");
|
||||
|
||||
|
||||
_logger.LogInformation(sb.ToString());
|
||||
//Todo:准备支持多租户种子数据及CodeFirst
|
||||
logger.LogInformation(sb.ToString());
|
||||
|
||||
if (options.EnabledCodeFirst)
|
||||
{
|
||||
CodeFirst(service);
|
||||
}
|
||||
|
||||
if (options.EnabledDbSeed)
|
||||
{
|
||||
await DataSeedAsync(service);
|
||||
@@ -80,7 +110,6 @@ namespace Yi.Framework.SqlSugarCore
|
||||
|
||||
private void CodeFirst(IServiceProvider service)
|
||||
{
|
||||
|
||||
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
||||
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
||||
|
||||
@@ -95,11 +124,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 +137,4 @@ namespace Yi.Framework.SqlSugarCore
|
||||
await dataSeeder.SeedAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
|
||||
|
||||
public interface IErrorObjct: IHasErrcode, IHasErrmsg
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
|
||||
|
||||
public interface IHasErrcode
|
||||
{
|
||||
public int errcode { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
|
||||
|
||||
public interface IHasErrmsg
|
||||
{
|
||||
string errmsg { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
|
||||
|
||||
public class AccessTokenResponse
|
||||
{
|
||||
public string access_token { get; set; }
|
||||
|
||||
public int expires_in { get; set; }
|
||||
}
|
||||
|
||||
public class AccessTokenRequest
|
||||
{
|
||||
public string grant_type { get; set; }
|
||||
public string appid { get; set; }
|
||||
public string secret { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Yi.Framework.WeChat.MiniProgram.Abstract;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
|
||||
|
||||
|
||||
public class Code2SessionResponse: IErrorObjct
|
||||
{
|
||||
public string openid { get; set; }
|
||||
public string session_key { get; set; }
|
||||
public string unionid { get; set; }
|
||||
public int errcode { get; set; }
|
||||
public string errmsg { get; set; }
|
||||
}
|
||||
|
||||
public class Code2SessionRequest
|
||||
{
|
||||
public string appid { get; set; }
|
||||
public string secret { get; set; }
|
||||
public string js_code { get; set; }
|
||||
public string grant_type => "authorization_code";
|
||||
}
|
||||
|
||||
public class Code2SessionInput
|
||||
{
|
||||
public Code2SessionInput(string js_code)
|
||||
{
|
||||
|
||||
this.js_code=js_code;
|
||||
}
|
||||
public string js_code { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Yi.Framework.WeChat.MiniProgram.Abstract;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
|
||||
|
||||
public class SubscribeNoticeRequest
|
||||
{
|
||||
/// <summary>
|
||||
///用户openid,可以是小程序的openid,也可以是mp_template_msg.appid对应的公众号的openid
|
||||
/// </summary>
|
||||
public string touser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 小程序模板id
|
||||
/// </summary>
|
||||
public string template_id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转页面
|
||||
/// </summary>
|
||||
public string page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 小程序模板消息的数据
|
||||
/// </summary>
|
||||
public Dictionary<string, keyValueItem> data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认为正式版
|
||||
/// </summary>
|
||||
public string miniprogram_state { get; set; } = "formal";
|
||||
|
||||
/// <summary>
|
||||
/// 默认为中文
|
||||
/// </summary>
|
||||
public string lang { get; set; } = "zh_CN";
|
||||
}
|
||||
|
||||
|
||||
public class SubscribeNoticeInput
|
||||
{
|
||||
/// <summary>
|
||||
///用户openid,可以是小程序的openid,也可以是mp_template_msg.appid对应的公众号的openid
|
||||
/// </summary>
|
||||
public string touser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 小程序模板id
|
||||
/// </summary>
|
||||
public string template_id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转页面
|
||||
/// </summary>
|
||||
public string page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 公众号模板消息的数据
|
||||
/// </summary>
|
||||
public Dictionary<string, keyValueItem> data { get; set; }
|
||||
}
|
||||
|
||||
public class SubscribeNoticeResponse : IErrorObjct
|
||||
{
|
||||
public int errcode { get; set; }
|
||||
public string errmsg { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public class keyValueItem
|
||||
{
|
||||
public keyValueItem(string value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public string value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Yi.Framework.WeChat.MiniProgram.HttpModels;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram;
|
||||
|
||||
public interface IWeChatMiniProgramManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户openid
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<Code2SessionResponse> Code2SessionAsync(Code2SessionInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 向用户发送订阅消息,要openid
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task SendSubscribeNoticeAsync(SubscribeNoticeInput input);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.Caching;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram.Token;
|
||||
|
||||
internal class CacheMiniProgramToken : DefaultMinProgramToken, IMiniProgramToken
|
||||
{
|
||||
private IDistributedCache<string> _cache;
|
||||
private const string CacheKey = "MiniProgramToken";
|
||||
|
||||
public CacheMiniProgramToken(IOptions<WeChatMiniProgramOptions> options, IDistributedCache<string> cache) :
|
||||
base(options)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
return await _cache.GetOrAddAsync("MiniProgramToken", async () => { return await base.GetTokenAsync(); }, () =>
|
||||
{
|
||||
return new DistributedCacheEntryOptions()
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) - TimeSpan.FromMinutes(1)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
using Yi.Framework.WeChat.MiniProgram.HttpModels;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram.Token;
|
||||
|
||||
internal class DefaultMinProgramToken:IMiniProgramToken
|
||||
{
|
||||
private const string Url = "https://api.weixin.qq.com/cgi-bin/token";
|
||||
private WeChatMiniProgramOptions _options;
|
||||
public DefaultMinProgramToken(IOptions<WeChatMiniProgramOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
var token = await this.GetAccessToken();
|
||||
return token.access_token;
|
||||
}
|
||||
public async Task<AccessTokenResponse> GetAccessToken()
|
||||
{
|
||||
var req = new AccessTokenRequest();
|
||||
req.appid = _options.AppID;
|
||||
req.secret = _options.AppSecret;
|
||||
req.grant_type = "client_credential";
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
string queryString = req.ToQueryString();
|
||||
var builder = new UriBuilder(Url);
|
||||
builder.Query = queryString;
|
||||
HttpResponseMessage response = await httpClient.GetAsync(builder.ToString());
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseBody = await response.Content.ReadFromJsonAsync<AccessTokenResponse>();
|
||||
return responseBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram.Token;
|
||||
|
||||
public interface IMiniProgramToken
|
||||
{
|
||||
public Task<string> GetTokenAsync();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram;
|
||||
|
||||
public class WeChatMiniProgramException: Exception
|
||||
{
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
// 加上前缀
|
||||
return "微信Api异常: " + base.Message;
|
||||
}
|
||||
}
|
||||
|
||||
public WeChatMiniProgramException()
|
||||
{
|
||||
}
|
||||
|
||||
public WeChatMiniProgramException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public WeChatMiniProgramException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
using Yi.Framework.WeChat.MiniProgram.Abstract;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram;
|
||||
|
||||
public static class WeChatMiniProgramExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 效验请求是否成功
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
internal static void ValidateSuccess(this IErrorObjct response)
|
||||
{
|
||||
|
||||
if (response.errcode != 0)
|
||||
{
|
||||
throw new WeChatMiniProgramException(response.errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ToQueryString<T>(this T obj)
|
||||
{
|
||||
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
var queryParams = new List<string>();
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var value = prop.GetValue(obj, null);
|
||||
if (value != null)
|
||||
{
|
||||
// 处理集合
|
||||
if (value is IEnumerable<object> enumerable)
|
||||
{
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
queryParams.Add($"{HttpUtility.UrlEncode(prop.Name)}={HttpUtility.UrlEncode(item.ToString())}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryParams.Add($"{HttpUtility.UrlEncode(prop.Name)}={HttpUtility.UrlEncode(value.ToString())}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join("&", queryParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
using Yi.Framework.WeChat.MiniProgram.HttpModels;
|
||||
using Yi.Framework.WeChat.MiniProgram.Token;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram;
|
||||
|
||||
public class WeChatMiniProgramManager : IWeChatMiniProgramManager, ISingletonDependency
|
||||
{
|
||||
private IMiniProgramToken _weChatToken;
|
||||
private WeChatMiniProgramOptions _options;
|
||||
|
||||
public WeChatMiniProgramManager(IMiniProgramToken weChatToken, IOptions<WeChatMiniProgramOptions> options)
|
||||
{
|
||||
_weChatToken = weChatToken;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户openid
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Code2SessionResponse> Code2SessionAsync(Code2SessionInput input)
|
||||
{
|
||||
string url = "https://api.weixin.qq.com/sns/jscode2session";
|
||||
var req = new Code2SessionRequest();
|
||||
req.js_code = input.js_code;
|
||||
req.secret = _options.AppSecret;
|
||||
req.appid = _options.AppID;
|
||||
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
string queryString = req.ToQueryString();
|
||||
var builder = new UriBuilder(url);
|
||||
builder.Query = queryString;
|
||||
HttpResponseMessage response = await httpClient.GetAsync(builder.ToString());
|
||||
var responseBody = await response.Content.ReadFromJsonAsync<Code2SessionResponse>();
|
||||
|
||||
responseBody.ValidateSuccess();
|
||||
|
||||
return responseBody;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 发送模板订阅消息
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
public async Task SendSubscribeNoticeAsync(SubscribeNoticeInput input)
|
||||
{
|
||||
var token = await _weChatToken.GetTokenAsync();
|
||||
string url = $"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={token}";
|
||||
var req = new SubscribeNoticeRequest
|
||||
{
|
||||
touser = input.touser,
|
||||
template_id = input.template_id,
|
||||
page = input.page,
|
||||
data = input.data,
|
||||
miniprogram_state = _options.Notice?.State??"formal"
|
||||
};
|
||||
req.template_id=req.template_id?? _options.Notice?.TemplateId;
|
||||
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
var body =new StringContent(JsonConvert.SerializeObject(req));
|
||||
HttpResponseMessage response = await httpClient.PostAsync(url, body);
|
||||
var responseBody = await response.Content.ReadFromJsonAsync<SubscribeNoticeResponse>();
|
||||
responseBody.ValidateSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace Yi.Framework.WeChat.MiniProgram;
|
||||
|
||||
public class WeChatMiniProgramOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// AppId
|
||||
/// </summary>
|
||||
public string AppID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// App密钥
|
||||
/// </summary>
|
||||
public string AppSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public WeChatMiniProgramNoticeItem Notice { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class WeChatMiniProgramNoticeItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 模板id
|
||||
/// </summary>
|
||||
public string TemplateId { get; set; }
|
||||
|
||||
public string State { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\common.props" />
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Yi.Framework.Core\Yi.Framework.Core.csproj" />
|
||||
<PackageReference Include="Volo.Abp.Caching" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Caching;
|
||||
using Yi.Framework.Core;
|
||||
using Yi.Framework.WeChat.MiniProgram.Token;
|
||||
|
||||
namespace Yi.Framework.WeChat.MiniProgram;
|
||||
|
||||
[DependsOn(typeof(YiFrameworkCoreModule),
|
||||
typeof(AbpCachingModule))]
|
||||
public class YiFrameworkWeChatMiniProgramModule: AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var services = context.Services;
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
Configure<WeChatMiniProgramOptions>(configuration.GetSection("WeChatMiniProgram"));
|
||||
services.AddSingleton<IMiniProgramToken, CacheMiniProgramToken>();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ public class AuditingStore : IAuditingStore, ITransientDependency
|
||||
protected IUnitOfWorkManager UnitOfWorkManager { get; }
|
||||
protected AbpAuditingOptions Options { get; }
|
||||
protected IAuditLogInfoToAuditLogConverter Converter { get; }
|
||||
|
||||
public AuditingStore(
|
||||
IAuditLogRepository auditLogRepository,
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
@@ -52,10 +53,10 @@ public class AuditingStore : IAuditingStore, ITransientDependency
|
||||
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
|
||||
{
|
||||
Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss"));
|
||||
using (var uow = UnitOfWorkManager.Begin(true))
|
||||
using (var uow = UnitOfWorkManager.Begin())
|
||||
{
|
||||
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; }
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
|
||||
|
||||
public class MoneyTopUserDto
|
||||
/// <summary>
|
||||
/// 用户排行榜
|
||||
/// </summary>
|
||||
public class BaseAnalyseTopUserDto
|
||||
{
|
||||
public Guid UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string? Nick { get; set; }
|
||||
public decimal Money { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public int Level { get; set; }
|
||||
@@ -18,5 +21,4 @@ public class MoneyTopUserDto
|
||||
/// 用户限制
|
||||
/// </summary>
|
||||
public UserLimitEnum UserLimit { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
|
||||
|
||||
public class MoneyTopUserDto:BaseAnalyseTopUserDto
|
||||
{
|
||||
public decimal Money { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
|
||||
|
||||
public class PointsTopUserDto:BaseAnalyseTopUserDto
|
||||
{
|
||||
public int Points { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
|
||||
|
||||
public class ValueTopUserDto:BaseAnalyseTopUserDto
|
||||
{
|
||||
public decimal Value { get; set; }
|
||||
}
|
||||
@@ -4,8 +4,6 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
||||
{
|
||||
public class ArticleGetListOutputDto : EntityDto<Guid>
|
||||
{
|
||||
//批量查询,不给内容,性能考虑
|
||||
//public string Content { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Guid DiscussId { get; set; }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Consts;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
||||
{
|
||||
@@ -10,5 +11,23 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
||||
public Guid ParentId { get; set; }
|
||||
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
public bool HasPermission { get;internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置权限
|
||||
/// </summary>
|
||||
public void SetPassPermission()
|
||||
{
|
||||
HasPermission = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置无权限
|
||||
/// </summary>
|
||||
public void SetNoPermission()
|
||||
{
|
||||
HasPermission = false;
|
||||
Content=DiscussConst.Privacy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,15 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
/// 是否禁止评论创建功能
|
||||
/// </summary>
|
||||
public bool IsDisableCreateComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标签
|
||||
/// </summary>
|
||||
public List<Guid>? DiscussLableIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
public class DiscussGetListInputVo : PagedAndSortedResultRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建者的用户名
|
||||
/// 创建者的用户名
|
||||
/// </summary>
|
||||
public string? UserName { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
@@ -15,11 +15,11 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
|
||||
public Guid? PlateId { get; set; }
|
||||
|
||||
//默认查询非置顶
|
||||
//默认查询非置顶
|
||||
public bool? IsTop { get; set; }
|
||||
|
||||
|
||||
//查询方式
|
||||
//查询方式
|
||||
public QueryDiscussTypeEnum Type { get; set; } = QueryDiscussTypeEnum.New;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Consts;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
|
||||
@@ -9,89 +10,47 @@ 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; }
|
||||
public string Types { get; set; }
|
||||
public string? Introduction { get; set; }
|
||||
|
||||
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权限
|
||||
public string? PrivateCode { get; set; }
|
||||
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
public List<Guid>? PermissionUserIds { get; set; }
|
||||
/// <summary>
|
||||
/// 所需角色
|
||||
/// </summary>
|
||||
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
|
||||
|
||||
public BbsUserGetListOutputDto User { get; set; }
|
||||
|
||||
public void SetBan()
|
||||
{
|
||||
Title = DiscussConst.Privacy;
|
||||
Introduction = "";
|
||||
Cover = null;
|
||||
//被禁止
|
||||
IsBan = true;
|
||||
}
|
||||
public List<Guid>? DiscussLableIds { get; set; } = new List<Guid>();
|
||||
public List<DiscussLableGetOutputDto> Lables { get; set; } = new List<DiscussLableGetOutputDto>();
|
||||
}
|
||||
|
||||
|
||||
public static class DiscussGetListOutputDtoExtension
|
||||
{
|
||||
|
||||
public static void ApplyPermissionTypeFilter(this List<DiscussGetListOutputDto> dtos, Guid userId)
|
||||
{
|
||||
dtos?.ForEach(dto =>
|
||||
{
|
||||
switch (dto.PermissionType)
|
||||
{
|
||||
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();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.Plate;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Consts;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
|
||||
|
||||
@@ -9,11 +11,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
public class DiscussGetOutputDto : EntityDto<Guid>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否禁止评论创建功能
|
||||
/// 是否禁止评论创建功能
|
||||
/// </summary>
|
||||
public bool IsDisableCreateComment { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string? Types { get; set; }
|
||||
public string? Introduction { get; set; }
|
||||
public int AgreeNum { get; set; }
|
||||
public int SeeNum { get; set; }
|
||||
@@ -21,24 +22,48 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
public string? Color { get; set; }
|
||||
|
||||
public Guid PlateId { get; set; }
|
||||
//是否置顶,默认false
|
||||
//是否置顶,默认false
|
||||
public bool IsTop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 封面
|
||||
/// 封面
|
||||
/// </summary>
|
||||
public string? Cover { get; set; }
|
||||
//是否私有,默认false
|
||||
//是否私有,默认false
|
||||
public bool IsPrivate { get; set; }
|
||||
|
||||
//私有需要判断code权限
|
||||
//私有需要判断code权限
|
||||
public string? PrivateCode { get; set; }
|
||||
public DateTime CreationTime { get; set; }
|
||||
public DiscussPermissionTypeEnum PermissionType { get; set; }
|
||||
public bool IsAgree { get; set; } = false;
|
||||
public List<Guid>? PermissionUserIds { get; set; }
|
||||
public List<string> PermissionRoleCodes { get; set; } = new List<string>();
|
||||
|
||||
|
||||
|
||||
public BbsUserGetListOutputDto User { get; set; }
|
||||
|
||||
public PlateGetOutputDto Plate { get; set; }
|
||||
|
||||
public List<Guid>? DiscussLableIds { get; set; } = new List<Guid>();
|
||||
public List<DiscussLableGetOutputDto> Lables { get; set; } =new List<DiscussLableGetOutputDto>();
|
||||
|
||||
public bool HasPermission { get;internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置权限
|
||||
/// </summary>
|
||||
public void SetPassPermission()
|
||||
{
|
||||
HasPermission = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置无权限
|
||||
/// </summary>
|
||||
public void SetNoPermission()
|
||||
{
|
||||
HasPermission = false;
|
||||
Content=DiscussConst.Privacy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,25 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
||||
public DiscussPermissionTypeEnum PermissionType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 封面
|
||||
/// 封面
|
||||
/// </summary>
|
||||
public string? Cover { get; set; }
|
||||
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否禁止评论创建功能
|
||||
/// 是否禁止评论创建功能
|
||||
/// </summary>
|
||||
public bool IsDisableCreateComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标签
|
||||
/// </summary>
|
||||
public List<Guid>? DiscussLableIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 需求角色
|
||||
/// </summary>
|
||||
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
|
||||
|
||||
public class DiscussLableGetOutputDto:EntityDto<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public string? BackgroundColor { get; set; }
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
|
||||
/// <summary>
|
||||
/// Label输入创建对象
|
||||
/// </summary>
|
||||
public class MyTypeCreateInputVo
|
||||
public class DiscussLableCreateInputVo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Color { get; set; }
|
||||
@@ -0,0 +1,9 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
|
||||
{
|
||||
public class DiscussLableGetListInputVo : PagedAndSortedResultRequestDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@ using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
|
||||
{
|
||||
public class MyTypeGetListOutputDto : EntityDto<Guid>
|
||||
public class DiscussLableGetListOutputDto : EntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public string? BackgroundColor { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
|
||||
{
|
||||
public class MyTypeOutputDto : EntityDto<Guid>
|
||||
public class DiscussLableOutputDto : EntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Color { get; set; }
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
|
||||
{
|
||||
public class MyTypeUpdateInputVo
|
||||
public class DiscussLableUpdateInputVo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public string? BackgroundColor { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
|
||||
{
|
||||
public class MyTypeGetListInputVo : PagedAndSortedResultRequestDto
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public string? BackgroundColor { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
|
||||
|
||||
public class BbsShopAccountDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 钱钱
|
||||
/// </summary>
|
||||
public decimal Money { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 积分
|
||||
/// </summary>
|
||||
public int Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 价值
|
||||
/// </summary>
|
||||
public decimal Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
|
||||
|
||||
public class BuyShopInputDto
|
||||
{
|
||||
public Guid GoodsId { get; set; }
|
||||
public string ContactInformation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
|
||||
|
||||
public class ShopGetListOutput:EntityDto<Guid>
|
||||
{
|
||||
/// <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; }
|
||||
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已限制
|
||||
/// </summary>
|
||||
public bool IsLimit { get; set; }
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.IServices
|
||||
/// <summary>
|
||||
/// Label服务抽象
|
||||
/// </summary>
|
||||
public interface IMyTypeService : IYiCrudAppService<MyTypeOutputDto, MyTypeGetListOutputDto, Guid, MyTypeGetListInputVo, MyTypeCreateInputVo, MyTypeUpdateInputVo>
|
||||
public interface IDiscussLableService : IYiCrudAppService<DiscussLableOutputDto, DiscussLableGetListOutputDto, Guid, DiscussLableGetListInputVo, DiscussLableCreateInputVo, DiscussLableUpdateInputVo>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -8,6 +8,5 @@ namespace Yi.Framework.Bbs.Application.Contracts.IServices
|
||||
/// </summary>
|
||||
public interface IDiscussService : IYiCrudAppService<DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>
|
||||
{
|
||||
Task VerifyDiscussPermissionAsync(Guid discussId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\digital-collectibles\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\rbac\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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,9 +54,79 @@ 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作者主题,返回当前作者最新的主题
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("analyse/bbs-discuss/author/{userId}")]
|
||||
public async Task<List<DiscussGetListOutputDto>> GetAuthorDiscussAsync(
|
||||
[FromRoute] Guid userId,
|
||||
[FromQuery] PagedResultRequestDto input)
|
||||
{
|
||||
var output = await _forumManager._discussRepository._DbQueryable.Where(discuss=>discuss.CreatorId==userId)
|
||||
.Where(discuss=>discuss.PermissionType== DiscussPermissionTypeEnum.Public)
|
||||
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
|
||||
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
|
||||
.OrderByDescending(discuss => discuss.CreationTime)
|
||||
.Select((discuss, user, info) => new DiscussGetListOutputDto
|
||||
{
|
||||
Id = discuss.Id,
|
||||
User = new BbsUserGetListOutputDto()
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Nick = user.Nick,
|
||||
Icon = user.Icon,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit
|
||||
}
|
||||
|
||||
}, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Yi.Framework.Bbs.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Entities.Integral;
|
||||
using Yi.Framework.Bbs.Domain.Managers;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.DigitalCollectibles.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Authorization;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||
@@ -20,11 +21,16 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
|
||||
{
|
||||
private BbsUserManager _bbsUserManager;
|
||||
private IOnlineService _onlineService;
|
||||
private readonly IPointAnalyseService _pointAnalyseService;
|
||||
private readonly IValueAnalyseService _valueAnalyseService;
|
||||
|
||||
public BbsUserAnalyseService(BbsUserManager bbsUserManager, IOnlineService onlineService)
|
||||
public BbsUserAnalyseService(BbsUserManager bbsUserManager, IOnlineService onlineService,
|
||||
IPointAnalyseService pointAnalyseService, IValueAnalyseService valueAnalyseService)
|
||||
{
|
||||
_bbsUserManager = bbsUserManager;
|
||||
_onlineService = onlineService;
|
||||
_pointAnalyseService = pointAnalyseService;
|
||||
_valueAnalyseService = valueAnalyseService;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +116,9 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
|
||||
)
|
||||
.ToPageListAsync(pageIndex, input.MaxResultCount, total);
|
||||
|
||||
output.ForEach(x => { x.LevelName = _bbsUserManager._levelCacheDic[x.Level].Name; });
|
||||
var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
|
||||
|
||||
output.ForEach(x => { x.LevelName = levelCache[x.Level].Name; });
|
||||
return new PagedResultDto<MoneyTopUserDto>
|
||||
{
|
||||
Items = output,
|
||||
@@ -169,5 +177,118 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 积分排行榜
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("analyse/dc-user/points-top/{userId?}")]
|
||||
public async Task<PagedResultDto<PointsTopUserDto>> GetPointsTopAsync([FromQuery] PagedResultRequestDto input,
|
||||
[FromRoute] Guid? userId)
|
||||
{
|
||||
var result = await _pointAnalyseService.GetValueTopAsync(input, null);
|
||||
|
||||
var userIds = result.Items.Select(x => x.UserId).ToList();
|
||||
|
||||
var baseOutput = await _bbsUserManager._userRepository._DbQueryable
|
||||
.Where(u => userIds.Contains(u.Id))
|
||||
.LeftJoin<BbsUserExtraInfoEntity>((u, info) => u.Id == info.UserId)
|
||||
.Select((u, info) =>
|
||||
new BaseAnalyseTopUserDto
|
||||
{
|
||||
UserName = u.UserName,
|
||||
Nick = u.Nick,
|
||||
Icon = u.Icon,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
UserId = info.UserId
|
||||
}
|
||||
).ToListAsync();
|
||||
|
||||
|
||||
var output = new List<PointsTopUserDto>();
|
||||
var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
|
||||
result.Items.ToList().ForEach(x =>
|
||||
{
|
||||
var currentUserInfo = baseOutput.Where(u => u.UserId == x.UserId).FirstOrDefault();
|
||||
|
||||
if (currentUserInfo is not null)
|
||||
{
|
||||
output.Add(new PointsTopUserDto
|
||||
{
|
||||
UserName = currentUserInfo.UserName,
|
||||
Nick = currentUserInfo.Nick,
|
||||
Order = x.Order,
|
||||
Icon = currentUserInfo.Icon,
|
||||
Level = currentUserInfo.Level,
|
||||
LevelName = levelCache[currentUserInfo.Level].Name,
|
||||
UserLimit = UserLimitEnum.Normal,
|
||||
Points = x.Points
|
||||
});
|
||||
}
|
||||
});
|
||||
return new PagedResultDto<PointsTopUserDto>
|
||||
{
|
||||
Items = output,
|
||||
TotalCount = result.TotalCount
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 价值排行榜
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("analyse/dc-user/value-top/{userId?}")]
|
||||
public async Task<PagedResultDto<ValueTopUserDto>> GetValueTopAsync([FromQuery] PagedResultRequestDto input,
|
||||
[FromRoute] Guid? userId)
|
||||
{
|
||||
var result = await _valueAnalyseService.GetValueTopAsync(input, null);
|
||||
|
||||
var userIds = result.Items.Select(x => x.UserId).ToList();
|
||||
|
||||
var baseOutput = await _bbsUserManager._userRepository._DbQueryable
|
||||
.Where(u => userIds.Contains(u.Id))
|
||||
.LeftJoin<BbsUserExtraInfoEntity>((u, info) => u.Id == info.UserId)
|
||||
.Select((u, info) =>
|
||||
new BaseAnalyseTopUserDto
|
||||
{
|
||||
UserName = u.UserName,
|
||||
Nick = u.Nick,
|
||||
Icon = u.Icon,
|
||||
Level = info.Level,
|
||||
UserLimit = info.UserLimit,
|
||||
UserId = info.UserId
|
||||
}
|
||||
).ToListAsync();
|
||||
|
||||
|
||||
var output = new List<ValueTopUserDto>();
|
||||
var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
|
||||
result.Items.ToList().ForEach(x =>
|
||||
{
|
||||
var currentUserInfo = baseOutput.Where(u => u.UserId == x.UserId).FirstOrDefault();
|
||||
|
||||
if (currentUserInfo is not null)
|
||||
{
|
||||
output.Add(new ValueTopUserDto
|
||||
{
|
||||
UserName = currentUserInfo.UserName,
|
||||
Nick = currentUserInfo.Nick,
|
||||
Order = x.Order,
|
||||
Icon = currentUserInfo.Icon,
|
||||
Level = currentUserInfo.Level,
|
||||
LevelName =levelCache[currentUserInfo.Level].Name,
|
||||
UserLimit = UserLimitEnum.Normal,
|
||||
Value = x.Value
|
||||
});
|
||||
}
|
||||
});
|
||||
return new PagedResultDto<ValueTopUserDto>
|
||||
{
|
||||
Items = output,
|
||||
TotalCount = result.TotalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,21 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
/// <summary>
|
||||
/// Article服务实现
|
||||
/// </summary>
|
||||
|
||||
public class ArticleService : YiCrudAppService<ArticleAggregateRoot, ArticleGetOutputDto, ArticleGetListOutputDto, Guid, ArticleGetListInputVo, ArticleCreateInputVo, ArticleUpdateInputVo>,
|
||||
IArticleService
|
||||
public class ArticleService : YiCrudAppService<ArticleAggregateRoot, ArticleGetOutputDto, ArticleGetListOutputDto,
|
||||
Guid, ArticleGetListInputVo, ArticleCreateInputVo, ArticleUpdateInputVo>,
|
||||
IArticleService
|
||||
{
|
||||
public ArticleService(IArticleRepository articleRepository,
|
||||
ISqlSugarRepository<DiscussAggregateRoot> discussRepository,
|
||||
IDiscussService discussService,
|
||||
ForumManager forumManager) : base(articleRepository)
|
||||
{
|
||||
|
||||
_articleRepository = articleRepository;
|
||||
_discussRepository = discussRepository;
|
||||
_discussService = discussService;
|
||||
_forumManager = forumManager;
|
||||
|
||||
}
|
||||
|
||||
private ForumManager _forumManager;
|
||||
private IArticleRepository _articleRepository;
|
||||
private ISqlSugarRepository<DiscussAggregateRoot> _discussRepository;
|
||||
@@ -55,13 +54,34 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var entities = await _articleRepository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!))
|
||||
//.WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!))
|
||||
.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 _articleRepository._DbQueryable
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!))
|
||||
.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<ArticleGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询文章
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<ArticleGetOutputDto> GetAsync(Guid id)
|
||||
{
|
||||
var entity = await _articleRepository.GetAsync(id);
|
||||
var output = entity.Adapt<ArticleGetOutputDto>();
|
||||
if (!await _forumManager.VerifyDiscussPermissionAsync(entity.DiscussId, CurrentUser.Id, CurrentUser.Roles))
|
||||
{
|
||||
output.SetNoPermission();
|
||||
}
|
||||
else
|
||||
{
|
||||
output.SetPassPermission();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文章全部树级信息
|
||||
@@ -72,17 +92,13 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
[Route("article/all/discuss-id/{discussId}")]
|
||||
public async Task<List<ArticleAllOutputDto>> GetAllAsync([FromRoute] Guid discussId)
|
||||
{
|
||||
await _discussService.VerifyDiscussPermissionAsync(discussId);
|
||||
|
||||
|
||||
var entities = await _articleRepository.GetTreeAsync(x => x.DiscussId == discussId);
|
||||
//var result = entities.Tile();
|
||||
var items = entities.Adapt<List<ArticleAllOutputDto>>();
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询文章
|
||||
/// 查询文章概述
|
||||
/// </summary>
|
||||
/// <param name="discussId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -109,7 +125,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
[Authorize]
|
||||
public async override Task<ArticleGetOutputDto> CreateAsync(ArticleCreateInputVo input)
|
||||
{
|
||||
await VerifyDiscussCreateIdAsync(input.DiscussId);
|
||||
await VerifyPermissionAsync(input.DiscussId);
|
||||
return await base.CreateAsync(input);
|
||||
}
|
||||
|
||||
@@ -122,7 +138,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
public override async Task<ArticleGetOutputDto> UpdateAsync(Guid id, ArticleUpdateInputVo input)
|
||||
{
|
||||
var entity = await _articleRepository.GetByIdAsync(id);
|
||||
await VerifyDiscussCreateIdAsync(entity.DiscussId);
|
||||
await VerifyPermissionAsync(entity.DiscussId);
|
||||
return await base.UpdateAsync(id, input);
|
||||
}
|
||||
|
||||
@@ -135,7 +151,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
public override async Task DeleteAsync(Guid id)
|
||||
{
|
||||
var entity = await _articleRepository.GetByIdAsync(id);
|
||||
await VerifyDiscussCreateIdAsync(entity.DiscussId);
|
||||
await VerifyPermissionAsync(entity.DiscussId);
|
||||
await base.DeleteAsync(id);
|
||||
}
|
||||
|
||||
@@ -144,8 +160,10 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
/// 导入文章
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task PostImportAsync([FromQuery] ArticleImprotDto input, [FromForm][Required] IFormFileCollection file)
|
||||
public async Task PostImportAsync([FromQuery] ArticleImprotDto input,
|
||||
[FromForm] [Required] IFormFileCollection file)
|
||||
{
|
||||
await VerifyPermissionAsync(input.DiscussId);
|
||||
var fileObjs = new List<FileObject>();
|
||||
if (file.Count > 0)
|
||||
{
|
||||
@@ -172,45 +190,18 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
{
|
||||
throw new UserFriendlyException("未选择文件");
|
||||
}
|
||||
|
||||
//使用简单工厂根据传入的类型进行判断
|
||||
await _forumManager.PostImportAsync(input.DiscussId, input.ArticleParentId, fileObjs, input.ImportType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 校验创建权限,userId为主题创建者
|
||||
/// </summary>
|
||||
/// <param name="disucssId"></param>
|
||||
/// <returns></returns>
|
||||
private async Task VerifyDiscussCreateIdAsync(Guid disucssId)
|
||||
private async Task VerifyPermissionAsync(Guid discussId)
|
||||
{
|
||||
var discuss = await _discussRepository.GetFirstAsync(x => x.Id == disucssId);
|
||||
if (discuss is null)
|
||||
if (!await _forumManager.VerifyDiscussPermissionAsync(discussId, CurrentUser.Id, isVerifyLook: false))
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.No_Exist);
|
||||
}
|
||||
|
||||
//这块有点绕,这个版本的写法比较清晰
|
||||
bool result = false;
|
||||
|
||||
if (CurrentUser.GetPermissions().Contains(UserConst.AdminPermissionCode))
|
||||
{
|
||||
//如果是超管,直接跳过
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果不是超管,必须满足作者是自己,同时还有发布的权限
|
||||
if (discuss.CreatorId == CurrentUser.Id)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
throw new UserFriendlyException("权限不足,请联系主题作者或管理员申请开通");
|
||||
throw new UserFriendlyException("您无权限进行操作", "403");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,13 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
private IDiscussService _discussService { get; set; }
|
||||
/// <summary>
|
||||
/// 获取改主题下的评论,结构为二维列表,该查询无分页
|
||||
/// Todo: 可放入领域层
|
||||
/// </summary>
|
||||
/// <param name="discussId"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<PagedResultDto<CommentGetListOutputDto>> GetDiscussIdAsync([FromRoute] Guid discussId, [FromQuery] CommentGetListInputVo input)
|
||||
{
|
||||
await _discussService.VerifyDiscussPermissionAsync(discussId);
|
||||
await _forumManager.VerifyDiscussPermissionAsync(discussId,CurrentUser.Id);
|
||||
|
||||
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Content), x => x.Content.Contains(input.Content))
|
||||
.Where(x => x.DiscussId == discussId)
|
||||
@@ -64,15 +63,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
List<Guid> userIds = entities.Where(x => x.CreatorId != null).Select(x => x.CreatorId ?? Guid.Empty).ToList();
|
||||
var bbsUserInfoDic = (await _bbsUserManager.GetBbsUserInfoAsync(userIds)).ToDictionary(x => x.Id);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------数据查询完成------
|
||||
|
||||
|
||||
|
||||
|
||||
//------数据查询完成------,以下只是dto的简单组装
|
||||
|
||||
//从根目录开始组装
|
||||
//结果初始值,第一层等于全部根节点
|
||||
@@ -117,11 +108,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
rootOutoutDto?.ForEach(x =>
|
||||
{
|
||||
x.Children = x.Children.OrderByDescending(x => x.CreationTime).ToList();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
return new PagedResultDto<CommentGetListOutputDto>(entities.Count(), rootOutoutDto);
|
||||
}
|
||||
|
||||
@@ -136,6 +123,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
[Authorize]
|
||||
public override async Task<CommentGetOutputDto> CreateAsync(CommentCreateInputVo input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.Content)|| input.Content=="<p><br></p>")
|
||||
{
|
||||
throw new UserFriendlyException("评论不能为空");
|
||||
}
|
||||
|
||||
var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);
|
||||
if (discuess is null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Data;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.MyType;
|
||||
using Yi.Framework.Bbs.Application.Contracts.IServices;
|
||||
using Yi.Framework.Bbs.Domain.Entities.Forum;
|
||||
using Yi.Framework.Ddd.Application;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
{
|
||||
/// <summary>
|
||||
/// DiscussLable服务实现
|
||||
/// </summary>
|
||||
public class DiscussLableService : YiCrudAppService<DiscussLableAggregateRoot, DiscussLableOutputDto,
|
||||
DiscussLableGetListOutputDto, Guid, DiscussLableGetListInputVo, DiscussLableCreateInputVo,
|
||||
DiscussLableUpdateInputVo>,
|
||||
IDiscussLableService
|
||||
{
|
||||
private ISqlSugarRepository<DiscussLableAggregateRoot, Guid> _repository;
|
||||
|
||||
public DiscussLableService(ISqlSugarRepository<DiscussLableAggregateRoot, Guid> repository) : base(repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
[HttpGet("discuss-lable/all")]
|
||||
public async Task<ListResultDto<DiscussLableGetListOutputDto>> GetAllListAsync(DiscussLableGetListInputVo input)
|
||||
{
|
||||
var order = input.Sorting ?? nameof(DiscussLableAggregateRoot.Name);
|
||||
var output = await _repository._DbQueryable
|
||||
.WhereIF(input.Name is not null, x => x.Name.Contains(input.Name))
|
||||
.OrderBy(order)
|
||||
.Select(x => new DiscussLableGetListOutputDto(), true)
|
||||
.ToListAsync();
|
||||
return new ListResultDto<DiscussLableGetListOutputDto>(output);
|
||||
}
|
||||
|
||||
public override async Task<PagedResultDto<DiscussLableGetListOutputDto>> GetListAsync(
|
||||
DiscussLableGetListInputVo input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var order = input.Sorting ?? nameof(DiscussLableAggregateRoot.Name);
|
||||
var output = await _repository._DbQueryable
|
||||
.WhereIF(input.Name is not null, x => x.Name.Contains(input.Name))
|
||||
.OrderBy(order)
|
||||
.Select(x => new DiscussLableGetListOutputDto(), true)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<DiscussLableGetListOutputDto>(total, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SqlSugar;
|
||||
@@ -9,10 +10,12 @@ using Volo.Abp.EventBus.Local;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss;
|
||||
using Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
|
||||
using Yi.Framework.Bbs.Application.Contracts.IServices;
|
||||
using Yi.Framework.Bbs.Domain.Entities;
|
||||
using Yi.Framework.Bbs.Domain.Entities.Forum;
|
||||
using Yi.Framework.Bbs.Domain.Managers;
|
||||
using Yi.Framework.Bbs.Domain.Repositories;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Consts;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||
using Yi.Framework.Bbs.Domain.Shared.Etos;
|
||||
@@ -29,28 +32,34 @@ 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)
|
||||
private IDiscussLableRepository _discussLableRepository;
|
||||
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager,
|
||||
ISqlSugarRepository<DiscussTopEntity> discussTopRepository,
|
||||
ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus,
|
||||
ISqlSugarRepository<AgreeEntity> agreeRepository, IDiscussLableRepository discussLableRepository) : base(forumManager._discussRepository)
|
||||
{
|
||||
_forumManager = forumManager;
|
||||
_plateEntityRepository = plateEntityRepository;
|
||||
_localEventBus = localEventBus;
|
||||
_discussTopEntityRepository = discussTopEntityRepository;
|
||||
_bbsUserManager=bbsUserManager;
|
||||
_agreeRepository = agreeRepository;
|
||||
_discussLableRepository = discussLableRepository;
|
||||
_discussTopRepository = discussTopRepository;
|
||||
_bbsUserManager = bbsUserManager;
|
||||
}
|
||||
|
||||
private readonly ILocalEventBus _localEventBus;
|
||||
private ForumManager _forumManager { get; set; }
|
||||
|
||||
|
||||
private ISqlSugarRepository<PlateAggregateRoot> _plateEntityRepository { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 单查
|
||||
/// </summary>
|
||||
@@ -58,45 +67,74 @@ 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 output = 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
|
||||
.Select((discuss, user, info, plate) => new DiscussGetOutputDto
|
||||
{
|
||||
Id = discuss.Id,
|
||||
IsAgree = false,
|
||||
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)
|
||||
.FirstAsync(discuss => discuss.Id == id);
|
||||
|
||||
}
|
||||
}, true)
|
||||
.SingleAsync(discuss => discuss.Id == id);
|
||||
|
||||
if (item is not null)
|
||||
if (output is null)
|
||||
{
|
||||
await VerifyDiscussPermissionAsync(item.Id);
|
||||
await _localEventBus.PublishAsync(new SeeDiscussEventArgs { DiscussId = item.Id, OldSeeNum = item.SeeNum });
|
||||
throw new UserFriendlyException("该主题不存在", "404");
|
||||
}
|
||||
|
||||
//组装点赞
|
||||
var agreeCreatorList =
|
||||
(await _agreeRepository._DbQueryable.Where(x => x.DiscussId == output.Id).Select(x=>x.CreatorId).ToListAsync());
|
||||
//已登录
|
||||
if (CurrentUser.Id is not null)
|
||||
{
|
||||
output.IsAgree = agreeCreatorList.Contains(CurrentUser.Id);
|
||||
}
|
||||
|
||||
//组装标签
|
||||
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
|
||||
foreach (var lableId in output.DiscussLableIds)
|
||||
{
|
||||
if (lableDic.TryGetValue(lableId,out var item))
|
||||
{
|
||||
output.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
//如果没有权限
|
||||
if (!await _forumManager.VerifyDiscussPermissionAsync(output.Id,CurrentUser.Id, CurrentUser.Roles))
|
||||
{
|
||||
output.SetNoPermission();
|
||||
}
|
||||
else
|
||||
{
|
||||
output.SetPassPermission();
|
||||
}
|
||||
|
||||
await _localEventBus.PublishAsync(new SeeDiscussEventArgs
|
||||
{ DiscussId = output.Id, OldSeeNum = output.SeeNum });
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,49 +143,74 @@ 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);
|
||||
|
||||
//查询完主题之后,要过滤一下私有的主题信息
|
||||
items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty);
|
||||
|
||||
items?.ForEach(x => x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name);
|
||||
//点赞字典,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());
|
||||
|
||||
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
|
||||
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
|
||||
|
||||
//组装等级、是否点赞赋值、标签
|
||||
items?.ForEach(x =>
|
||||
{
|
||||
x.User.LevelName = 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);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var lableId in x.DiscussLableIds)
|
||||
{
|
||||
if (lableDic.TryGetValue(lableId,out var item))
|
||||
{
|
||||
x.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return new PagedResultDto<DiscussGetListOutputDto>(total, items);
|
||||
}
|
||||
|
||||
@@ -157,14 +220,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,
|
||||
@@ -174,7 +239,6 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
Address = user.Address,
|
||||
Age = user.Age,
|
||||
CreationTime = user.CreationTime,
|
||||
|
||||
Level = info.Level,
|
||||
Introduction = user.Introduction,
|
||||
Icon = user.Icon,
|
||||
@@ -187,7 +251,20 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
}
|
||||
}, true)
|
||||
.ToListAsync();
|
||||
output?.ForEach(x => x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name);
|
||||
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
|
||||
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
|
||||
|
||||
output?.ForEach(x =>
|
||||
{
|
||||
x.User.LevelName = levelCacheDic[x.User.Level].Name;
|
||||
foreach (var lableId in x.DiscussLableIds)
|
||||
{
|
||||
if (lableDic.TryGetValue(lableId,out var item))
|
||||
{
|
||||
x.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
|
||||
}
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -206,6 +283,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)
|
||||
{
|
||||
@@ -220,33 +302,10 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验主题查询权限
|
||||
/// </summary>
|
||||
/// <param name="discussId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="UserFriendlyException"></exception>
|
||||
public async Task VerifyDiscussPermissionAsync(Guid discussId)
|
||||
|
||||
public override Task<DiscussGetOutputDto> UpdateAsync(Guid id, DiscussUpdateInputVo input)
|
||||
{
|
||||
var discuss = await _forumManager._discussRepository.GetFirstAsync(x => x.Id == discussId);
|
||||
if (discuss is null)
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.No_Exist);
|
||||
}
|
||||
if (discuss.PermissionType == DiscussPermissionTypeEnum.Oneself)
|
||||
{
|
||||
if (discuss.CreatorId != CurrentUser.Id)
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.Privacy);
|
||||
}
|
||||
}
|
||||
if (discuss.PermissionType == DiscussPermissionTypeEnum.User)
|
||||
{
|
||||
if (discuss.CreatorId != CurrentUser.Id && !discuss.PermissionUserIds.Contains(CurrentUser.Id ?? Guid.Empty))
|
||||
{
|
||||
throw new UserFriendlyException(DiscussConst.Privacy);
|
||||
}
|
||||
}
|
||||
return base.UpdateAsync(id, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user