mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-03 16:20:52 +08:00
Compare commits
393 Commits
AbpUnitOfW
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8c30ca2e1 | ||
|
|
d8d1e25972 | ||
|
|
fc5779225e | ||
|
|
287b30e2c4 | ||
|
|
d00cdcf122 | ||
|
|
92064cc4f5 | ||
|
|
b1f9aba4f6 | ||
|
|
e07714ee54 | ||
|
|
f5da80de9e | ||
|
|
1fd75a198d | ||
|
|
16b2238f5b | ||
|
|
9afeb59766 | ||
|
|
960d8a309c | ||
|
|
adf09f4753 | ||
|
|
dc0c83a620 | ||
|
|
a1210c1efd | ||
|
|
18f253371b | ||
|
|
bd410087af | ||
|
|
d4678f2fe3 | ||
|
|
6f64ebfba3 | ||
|
|
689b136724 | ||
|
|
d9d3118879 | ||
|
|
1d78fec144 | ||
|
|
d12769dd29 | ||
|
|
bd51228293 | ||
|
|
eaf5c01a33 | ||
|
|
369d5ef54e | ||
|
|
8975b76e79 | ||
|
|
7512a373a6 | ||
|
|
7b3b7292ea | ||
|
|
816f680672 | ||
|
|
50e86f99a4 | ||
|
|
6701a09ad1 | ||
|
|
ecba8d5c66 | ||
|
|
69618eef33 | ||
|
|
44394dffac | ||
|
|
66c2d641a8 | ||
|
|
7e90624467 | ||
|
|
19e26f4383 | ||
|
|
699fc54885 | ||
|
|
05b81b710f | ||
|
|
3c9c882c42 | ||
|
|
176af22572 | ||
|
|
51ee3fb460 | ||
|
|
2c0689fe02 | ||
|
|
1c8899ed4f | ||
|
|
30c45eeb59 | ||
|
|
cbd76d2952 | ||
|
|
9a121af7bd | ||
|
|
96503a2f15 | ||
|
|
29f61e1dc9 | ||
|
|
80d8ac2bc8 | ||
|
|
f77c775229 | ||
|
|
38463fc477 | ||
|
|
5a212d7949 | ||
|
|
fe7e2eb660 | ||
|
|
225d8d4726 | ||
|
|
b69c6d86c1 | ||
|
|
fe7c1763ba | ||
|
|
a50c45f7a3 | ||
|
|
2bc8837155 | ||
|
|
8a6e5abf48 | ||
|
|
8b191330b8 | ||
|
|
0d2f2cb826 | ||
|
|
571df74c43 | ||
|
|
cefde6848d | ||
|
|
551597765c | ||
|
|
5eaffe2ec2 | ||
|
|
2ec7b5f4fd | ||
|
|
4521212a90 | ||
|
|
94834f45c3 | ||
|
|
22ac150acd | ||
|
|
1cc5f2a14f | ||
|
|
d9997eeb28 | ||
|
|
06e77aa8fd | ||
|
|
e9e2228f6e | ||
|
|
d516a381d0 | ||
|
|
4e792ba976 | ||
|
|
f90d3871fa | ||
|
|
6005b9329d | ||
|
|
9d4b3e7d0c | ||
|
|
72795382a1 | ||
|
|
35cdff2afa | ||
|
|
dcf547f513 | ||
|
|
8660d45f36 | ||
|
|
63dd55e7a4 | ||
|
|
40cd89f90c | ||
|
|
901ccc7314 | ||
|
|
629add1e8a | ||
|
|
8b92cd6bed | ||
|
|
e63fb71ef6 | ||
|
|
aa122d2d82 | ||
|
|
5d6bfe36d0 | ||
|
|
26dadd7dae | ||
|
|
520dca6953 | ||
|
|
699f7febe4 | ||
|
|
87a14ebac1 | ||
|
|
29573342b5 | ||
|
|
91b216c06e | ||
|
|
83a6ec1b98 | ||
|
|
c5d636d697 | ||
|
|
3d704220f3 | ||
|
|
b830317608 | ||
|
|
21ff599a4e | ||
|
|
224c2b96e4 | ||
|
|
cbb3510d94 | ||
|
|
0b111852ec | ||
|
|
ff8038a616 | ||
|
|
710ad95eda | ||
|
|
f7d9effa07 | ||
|
|
cec28faaf7 | ||
|
|
bcdcca82eb | ||
|
|
4e0cc9a24a | ||
|
|
53a402a656 | ||
|
|
85ed4df1e4 | ||
|
|
8ef91ebd03 | ||
|
|
ccaebb8ec2 | ||
|
|
4afc1cc492 | ||
|
|
ddba0f9aa1 | ||
|
|
c782246a1d | ||
|
|
afa5fad8c6 | ||
|
|
d605809932 | ||
|
|
30250db0fb | ||
|
|
b48f584db8 | ||
|
|
56d850d74b | ||
|
|
3ef1323f05 | ||
|
|
d9ed547a20 | ||
|
|
82865631fc | ||
|
|
337088c908 | ||
|
|
c092ee46e9 | ||
|
|
287634cf99 | ||
|
|
c1535fd116 | ||
|
|
d7d4fd8a48 | ||
|
|
1552b00516 | ||
|
|
a40d9b79b4 | ||
|
|
0bc5b1a940 | ||
|
|
4dce50438c | ||
|
|
de7f9264fd | ||
|
|
f00a90f1ef | ||
|
|
2c578cc9e4 | ||
|
|
a04974d905 | ||
|
|
23cedbec83 | ||
|
|
3e07ca822a | ||
|
|
f9341fd2ac | ||
|
|
f6b19ec2a5 | ||
|
|
f8a4c737ee | ||
|
|
c395900861 | ||
|
|
b2ad667894 | ||
|
|
45736dfce9 | ||
|
|
753b5b0a26 | ||
|
|
3aaed801f6 | ||
|
|
1fd4f2754a | ||
|
|
bedee3391e | ||
|
|
176a672e86 | ||
|
|
9da6bcde41 | ||
|
|
3f8daa1d17 | ||
|
|
ed873da3b6 | ||
|
|
c0dfa83828 | ||
|
|
db08688968 | ||
|
|
400a146a48 | ||
|
|
a645264da7 | ||
|
|
09d19d876f | ||
|
|
373877cfcf | ||
|
|
9e143c0a75 | ||
|
|
37b16e8395 | ||
|
|
9c94953e0e | ||
|
|
2c48b8f881 | ||
|
|
4c4b78dda7 | ||
|
|
4ba9a7917f | ||
|
|
5d286ebc9e | ||
|
|
e69bbb46b3 | ||
|
|
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 | ||
|
|
c4e79e46cf | ||
|
|
4fadba27dc | ||
|
|
7eab4dd5b1 | ||
|
|
0e6d380b7e | ||
|
|
1a73f7bef3 | ||
|
|
a518e7b210 | ||
|
|
e2dae1c4ab | ||
|
|
0d00f91b31 | ||
|
|
d3b87e8984 | ||
|
|
fcaad5c6cc | ||
|
|
8ca741792a | ||
|
|
0f687a7e34 | ||
|
|
736995c35b | ||
|
|
4ae548cc5b | ||
|
|
942868f17f | ||
|
|
8a57bf52f9 | ||
|
|
260d87c97e | ||
|
|
cb1bac25a3 | ||
|
|
ef20ef7014 | ||
|
|
b88cad7b80 | ||
|
|
3383e86064 | ||
|
|
a0ef3af155 | ||
|
|
e8fcab4c6b | ||
|
|
1c6a795061 | ||
|
|
36246c2945 | ||
|
|
7af54f600f |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -154,6 +154,10 @@ PublishScripts/
|
|||||||
*.nupkg
|
*.nupkg
|
||||||
# The packages folder can be ignored because of Package Restore
|
# The packages folder can be ignored because of Package Restore
|
||||||
**/packages/*
|
**/packages/*
|
||||||
|
|
||||||
|
# 把 Yi.Vben5.Vue3 下的 packages 目录重新放出来
|
||||||
|
!**/Yi.Vben5.Vue3/packages/
|
||||||
|
!**/Yi.Vben5.Vue3/packages/**
|
||||||
# except build/, which is used as an MSBuild target.
|
# except build/, which is used as an MSBuild target.
|
||||||
!**/packages/build/
|
!**/packages/build/
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
@@ -263,14 +267,19 @@ src/Acme.BookStore.Blazor.Server.Tiered/Logs/*
|
|||||||
|
|
||||||
# Use abp install-libs to restore.
|
# Use abp install-libs to restore.
|
||||||
**/wwwroot/libs/*
|
**/wwwroot/libs/*
|
||||||
|
public
|
||||||
dist
|
dist
|
||||||
|
# 把 Yi.Vben5.Vue3 下的 public 目录重新放出来
|
||||||
|
!**/Yi.Vben5.Vue3/apps/web-antd/public/
|
||||||
.vscode
|
.vscode
|
||||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
|
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
|
||||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
|
/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.Development.json
|
||||||
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json
|
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json
|
||||||
|
/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/appsettings.Development.json
|
||||||
database_backup
|
database_backup
|
||||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Staging.json
|
/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/logs/
|
||||||
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
|
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
|||||||
37
README-Docker.md
Normal file
37
README-Docker.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# 🍉Docker 构建说明
|
||||||
|
|
||||||
|
## 🍊后端
|
||||||
|
执行目录:Yi\Yi.Abp.Net8
|
||||||
|
|
||||||
|
#### 🍊启动
|
||||||
|
D:/code/csharp/source/Yi/Yi.Bbs.Vue3/yi-bbs.conf 为我的配置文件,内部带了默认的配置文件,根据自己配置进行更改
|
||||||
|
|
||||||
|
//不带配置文件
|
||||||
|
docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:1.0.0
|
||||||
|
|
||||||
|
//带配置文件
|
||||||
|
docker run -d --name yi.admin -p 19001:19001 -v D:/code/csharp/source/Yi/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json:/app/appsettings.json jiftcc/yi.admin:1.0.0
|
||||||
|
|
||||||
|
|
||||||
|
#### 🍊完整代码编译
|
||||||
|
docker build -t jiftcc/yi.admin:1.0.0 -f Dockerfile .
|
||||||
|
|
||||||
|
#### 🍊快速产物编译
|
||||||
|
docker build -t jiftcc/yi.admin:1.0.0 -f DockerfileFast .
|
||||||
|
|
||||||
|
****
|
||||||
|
|
||||||
|
## 🍇前端
|
||||||
|
执行目录:Yi\Yi.Bbs.Vue3
|
||||||
|
|
||||||
|
#### 🍇启动
|
||||||
|
D:/code/csharp/source/Yi/Yi.Bbs.Vue3/yi-bbs.conf 为我的conf配置目录,默认反向代理到ccnetcore.com,根据自己后端地址进行修改配置
|
||||||
|
|
||||||
|
docker run -d --name yi.bbs -p 18001:18001 -v D:/code/csharp/source/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:1.0.0
|
||||||
|
|
||||||
|
#### 🍇完整代码编译
|
||||||
|
docker build -t jiftcc/yi.bbs:1.0.0 -f Dockerfile .
|
||||||
|
|
||||||
|
#### 🍇快速产物编译
|
||||||
|
docker build -t jiftcc/yi.bbs:1.0.0 -f DockerfileFast .
|
||||||
|
|
||||||
12
README-en.md
12
README-en.md
@@ -35,6 +35,18 @@ A Comprehensive Solution, Ultimately Just Another Wheel.
|
|||||||
- Yi.RuoYi.Vue3:RuoYi JS Backend Frontend
|
- Yi.RuoYi.Vue3:RuoYi JS Backend Frontend
|
||||||
|
|
||||||
****
|
****
|
||||||
|
## 🍉 docker
|
||||||
|
|
||||||
|
Full content:README-Docker.md
|
||||||
|
|
||||||
|
backend:`docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:last`
|
||||||
|
|
||||||
|
bbs frontend:`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:last`
|
||||||
|
|
||||||
|
> In addition, we provide Docker build operation, and we hope that you can build your own image through this method
|
||||||
|
|
||||||
|
****
|
||||||
|
|
||||||
|
|
||||||
## 🍊 Official website and demo link:
|
## 🍊 Official website and demo link:
|
||||||
|
|
||||||
|
|||||||
97
README.md
97
README.md
@@ -1,12 +1,18 @@
|
|||||||
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi框架</h1>
|
<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>
|
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
|
||||||
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端接入Ruoyi/Pure Vue</h5>
|
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端接入Vben/Ruoyi/Pure Vue</h5>
|
||||||
<h2 align="center">集大成者,终究轮子</h2>
|
<h2 align="center">集大成者,终究轮子</h2>
|
||||||
|
|
||||||
[](https://gitee.com/ccnetcore/Yi)
|
[](https://gitee.com/ccnetcore/Yi)
|
||||||
[](https://gitee.com/ccnetcore/Yi)
|
[](https://gitee.com/ccnetcore/Yi)
|
||||||
[](https://gitee.com/ccnetcore/Yi)
|
[](https://gitee.com/ccnetcore/Yi)
|
||||||
|
|
||||||
|
本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助
|
||||||
|
|
||||||
|
[亚洲最佳CDN、边缘和安全解决方案 - Tencent EdgeOne](https://edgeone.ai/zh?from=github)
|
||||||
|
|
||||||
|
<img src="readme/edgeone.png"/>
|
||||||
|
|
||||||
[English](README-en.md) | 简体中文
|
[English](README-en.md) | 简体中文
|
||||||
****
|
****
|
||||||
## 🍍 简介:
|
## 🍍 简介:
|
||||||
@@ -22,7 +28,7 @@ YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端
|
|||||||
|
|
||||||
Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
|
Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
|
||||||
与Sqlsugar理念一致,以用户体验出发。
|
与Sqlsugar理念一致,以用户体验出发。
|
||||||
适合.Net8学习、Sqlsugar学习 、项目二次开发。
|
全生态拥抱AI,接入AI,100%代码经过AI洗礼
|
||||||
集大成者,终究轮子
|
集大成者,终究轮子
|
||||||
|
|
||||||
(更新频繁,可watching持续关注。)
|
(更新频繁,可watching持续关注。)
|
||||||
@@ -33,25 +39,52 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
|
|||||||
|
|
||||||
**分支目录:**
|
**分支目录:**
|
||||||
|
|
||||||
- 分支**Abp**: 基于Abp.vNext分支,DDD领域驱动设计,回归开发本质,极度简单,一个后台支持以下多个前端
|
- 分支**main**: 基于Abp.vNext分支,默认分支,只使用Rbac权限管理后台
|
||||||
|
- 分支**abp**: 基于Abp.vNext分支,完整分支,具备超多内置模块
|
||||||
|
|
||||||
|
|
||||||
- Yi.Abp.Net8:后端
|
- Yi.Abp.Net8:后端
|
||||||
- Yi.Bbs.Vue3:Bbs社区 前端
|
|
||||||
- Yi.Doc.Md: 开源文档教程
|
- Yi.Doc.Md: 开源文档教程
|
||||||
|
- Yi.Vben5.Vue3:Vben ts后台前端
|
||||||
- Yi.Pure.Vue3:Pure ts后台前端
|
- Yi.Pure.Vue3:Pure ts后台前端
|
||||||
- Yi.RuoYi.Vue3:RuoYi js后台前端
|
- Yi.RuoYi.Vue3:RuoYi js后台前端
|
||||||
|
|
||||||
|
|
||||||
|
****
|
||||||
|
## 🍉 docker 一键启动
|
||||||
|
|
||||||
|
完整内容在:README-Docker.md
|
||||||
|
|
||||||
|
后端:`docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:last`
|
||||||
|
|
||||||
|
bbs前端:`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:last`
|
||||||
|
|
||||||
|
> 另外我们提供docker的build操作,我们更希望你能通过此种方式二开构建属于自己的镜像,因为上面镜像更新不及时
|
||||||
|
|
||||||
****
|
****
|
||||||
|
|
||||||
## 🍊 官网及演示地址:
|
## 🍊 官网及演示地址:
|
||||||
|
|
||||||
废话少说直接上地址
|
废话少说直接上地址
|
||||||
|
|
||||||
Yi社区官网网址(Bbs社区正式):[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
|
- Yi社区官网网址(Bbs社区正式):[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
|
||||||
|
|
||||||
Rbac后台演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
DotNet后端主要由:SharpDance 意框架官方团队 及 数百名 开源用户 共同维护
|
||||||
|
|
||||||
Pure后台演示地址:https://ccnetcore.com:1001 (用户cc、密码123456)
|
|
||||||
|
- Vben后台演示地址:https://data.ccnetcore.com:2000 (用户cc、密码123456)
|
||||||
|
|
||||||
|
Vben前端主要由:【https://gitee.com/vichen2021】贡献维护
|
||||||
|
|
||||||
|
|
||||||
|
- Ruoyi后台演示地址:https://data.ccnetcore.com:1000 (用户cc、密码123456)
|
||||||
|
|
||||||
|
Ruoyi前端主要由:30多名开源用户共同贡献维护
|
||||||
|
|
||||||
|
|
||||||
|
- Pure后台演示地址:https://data.ccnetcore.com:1001 (用户cc、密码123456)
|
||||||
|
|
||||||
|
Pure前端主要由:3名开源用户共同贡献维护
|
||||||
|
|
||||||
## 🍏 支持:
|
## 🍏 支持:
|
||||||
|
|
||||||
@@ -60,8 +93,11 @@ Pure后台演示地址:https://ccnetcore.com:1001 (用户cc、密码123456
|
|||||||
- [x] 完全支持微服务架构
|
- [x] 完全支持微服务架构
|
||||||
|
|
||||||
****
|
****
|
||||||
|
|
||||||
## 🍇 详细到爆炸的Yi框架教程导航:
|
## 🍇 详细到爆炸的Yi框架教程导航:
|
||||||
|
|
||||||
|
0. [社区导航大全](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742/fb8c871b-41fc-21bc-474f-3a154498f42b)
|
||||||
|
|
||||||
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)
|
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)
|
||||||
2. [框架功能模块教程](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(已完成)
|
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)(已完成)
|
||||||
@@ -98,8 +134,13 @@ Pure后台演示地址:https://ccnetcore.com:1001 (用户cc、密码123456
|
|||||||
- [SqlSugar官网](https://www.donet5.com/home/doc)
|
- [SqlSugar官网](https://www.donet5.com/home/doc)
|
||||||
|
|
||||||
## 🍅 内置模块简介
|
## 🍅 内置模块简介
|
||||||
- Rbac权限管理系统(已上线)(支持pure、ruoyi前端)
|
|
||||||
- Bbs论坛社区系统(已上线)
|
- rbac 权限管理系统(已上线)(支持vben、pure、ruoyi前端)
|
||||||
|
- bbs 论坛社区系统(已上线)
|
||||||
|
- ai-stock Ai模拟炒股系统(已上线)
|
||||||
|
- chat-hub Ai在线聊天室系统(已上线)
|
||||||
|
- code-gen 代码生成器工具系统(已上线)
|
||||||
|
- digital-collectibles 数字藏品小程序系统(已上线)
|
||||||
|
|
||||||
> 重复的东西,无需再写一遍,这也是优雅的体现之一
|
> 重复的东西,无需再写一遍,这也是优雅的体现之一
|
||||||
|
|
||||||
@@ -120,14 +161,6 @@ C# Asp.NetCore 8.0
|
|||||||
- [x] 分布式缓存:Abp.vNext
|
- [x] 分布式缓存:Abp.vNext
|
||||||
- [x] 事件总线:Abp.vNext
|
- [x] 事件总线:Abp.vNext
|
||||||
|
|
||||||
#### 前端
|
|
||||||
js Vue3
|
|
||||||
- [x] 异步请求:axios
|
|
||||||
- [x] 图表:echarts
|
|
||||||
- [x] ui:element-plus
|
|
||||||
- [x] 存储:pinia
|
|
||||||
- [x] 路由:vue-router
|
|
||||||
- [x] 打包:vite
|
|
||||||
|
|
||||||
#### 运维
|
#### 运维
|
||||||
- [x] 部署:nginx
|
- [x] 部署:nginx
|
||||||
@@ -154,24 +187,16 @@ js Vue3
|
|||||||
- 缓存列表
|
- 缓存列表
|
||||||
- 服务监控
|
- 服务监控
|
||||||
|
|
||||||
#### 🍐 BBS社区论坛系统(持续更新)
|
|
||||||
(采用vue3前端)
|
|
||||||
- 文章功能
|
|
||||||
- 板块功能
|
|
||||||
- 主题功能
|
|
||||||
- 个人中心
|
|
||||||
- 授权中心
|
|
||||||
- 权限管理
|
|
||||||
|
|
||||||
#### 🍉 演示截图:
|
#### 🍉 演示截图:
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="readme/101.png"/></td>
|
<td><img src="readme/303.png"/></td>
|
||||||
<td><img src="readme/102.png"/></td>
|
<td><img src="readme/304.png"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="readme/103.png"/></td>
|
<td><img src="readme/301.png"/></td>
|
||||||
<td><img src="readme/104.png"/></td>
|
<td><img src="readme/302.png"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -242,14 +267,16 @@ js Vue3
|
|||||||
|
|
||||||
[Furion百小僧]https://furion.baiqian.ltd/
|
[Furion百小僧]https://furion.baiqian.ltd/
|
||||||
|
|
||||||
|
[du白]https://gitee.com/vichen2021
|
||||||
|
|
||||||
****
|
****
|
||||||
## 🌽 联系我们:
|
## 🌽 联系我们:
|
||||||
|
|
||||||
作者QQ:`454313500`,2029年之前作者24小时在线,时刻保持活跃更新。
|
作者QQ:`454313500`,2029年之前作者24小时在线,时刻保持活跃更新。
|
||||||
|
|
||||||
QQ交流群:官方一群(已满)、官方二群(已满)、官方三群:`786308927`(已满)、官方四群:`498310311`(基本已满)、官方五群:`981136525`(新群)
|
QQ交流群:官方一群(已满)、官方二群(已满)、官方三群:`786308927`(已满)、官方四群:`498310311`(已满)、官方五群:`981136525`
|
||||||
|
|
||||||
微信交流群:官方微信一群(已满)、官方微信二群
|
微信交流群:官方微信一群(已满)、官方微信二群(已满)、官方微信三群(已满)、官方微信四群(即满)
|
||||||
|
|
||||||
微信交流群:加作者微信 chengzilaoge520 (橙子老哥520),备注拉群
|
微信交流群:加作者微信 chengzilaoge520 (橙子老哥520),备注拉群
|
||||||
|
|
||||||
@@ -260,6 +287,12 @@ QQ交流群:官方一群(已满)、官方二群(已满)、官方三群
|
|||||||
****
|
****
|
||||||
## 🍄 FQA:
|
## 🍄 FQA:
|
||||||
|
|
||||||
前往官网查看留言区
|
如何修改仓库代码:请直接pr即可,官方团队会第一时间处理,目前前端都是pr来的
|
||||||
|
|
||||||
|
gitee仓库官方地址:https://gitee.com/ccnetcore/Yi
|
||||||
|
|
||||||
|
github仓库官方地址:https://github.com/ccnetcore/Yi.Abp.Admin
|
||||||
|
|
||||||
|
前往官网查看留言区:
|
||||||
|
|
||||||
[留言区](https://ccnetcore.com/discuss/1641030787056930818)
|
[留言区](https://ccnetcore.com/discuss/1641030787056930818)
|
||||||
@@ -28,3 +28,6 @@ README.md
|
|||||||
!.git/config
|
!.git/config
|
||||||
!.git/packed-refs
|
!.git/packed-refs
|
||||||
!.git/refs/heads/**
|
!.git/refs/heads/**
|
||||||
|
appsettings.Development.json
|
||||||
|
appsettings.Production.json
|
||||||
|
appsettings.Staging.json
|
||||||
22
Yi.Abp.Net8/Dockerfile
Normal file
22
Yi.Abp.Net8/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER root
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||||
|
RUN echo "Asia/Shanghai" > /etc/timezone
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 19001
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /main
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/main/src/Yi.Abp.Web"
|
||||||
|
RUN dotnet restore "Yi.Abp.Web.csproj"
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
WORKDIR "/main/src/Yi.Abp.Web"
|
||||||
|
RUN dotnet publish "Yi.Abp.Web.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "Yi.Abp.Web.dll"]
|
||||||
11
Yi.Abp.Net8/DockerfileFast
Normal file
11
Yi.Abp.Net8/DockerfileFast
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER root
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||||
|
RUN echo "Asia/Shanghai" > /etc/timezone
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 19001
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ["./publish","."]
|
||||||
|
ENTRYPOINT ["dotnet", "Yi.Abp.Web.dll"]
|
||||||
@@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
version.props = version.props
|
version.props = version.props
|
||||||
publish.bat = publish.bat
|
publish.bat = publish.bat
|
||||||
publish_Demo.bat = publish_Demo.bat
|
publish_Demo.bat = publish_Demo.bat
|
||||||
|
Dockerfile = Dockerfile
|
||||||
|
DockerfileFast = DockerfileFast
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
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}"
|
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}"
|
||||||
@@ -62,38 +64,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Domain.Sh
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.SqlSugarCore", "module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj", "{4503A2F9-139D-4CBC-AF11-689C34F0D77B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.SqlSugarCore", "module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj", "{4503A2F9-139D-4CBC-AF11-689C34F0D77B}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bbs", "bbs", "{E902A945-4F41-4E96-A0DA-9F66CDA22261}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Domain.Shared", "module\bbs\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj", "{EB9349E2-256D-41EB-A345-21635A1361B3}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Domain", "module\bbs\Yi.Framework.Bbs.Domain\Yi.Framework.Bbs.Domain.csproj", "{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Application.Contracts", "module\bbs\Yi.Framework.Bbs.Application.Contracts\Yi.Framework.Bbs.Application.Contracts.csproj", "{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Application", "module\bbs\Yi.Framework.Bbs.Application\Yi.Framework.Bbs.Application.csproj", "{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.SqlSugarCore", "module\bbs\Yi.Framework.Bbs.SqlSugarCore\Yi.Framework.Bbs.SqlSugarCore.csproj", "{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{73CCF2C4-B9FD-44AB-8D4B-0A421805B094}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{73CCF2C4-B9FD-44AB-8D4B-0A421805B094}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.SqlSugarCore", "module\audit-logging\Yi.Framework.AuditLogging.SqlSugarCore\Yi.Framework.AuditLogging.SqlSugarCore.csproj", "{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.SqlSugarCore", "module\audit-logging\Yi.Framework.AuditLogging.SqlSugarCore\Yi.Framework.AuditLogging.SqlSugarCore.csproj", "{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}"
|
||||||
EndProject
|
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}"
|
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
|
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}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}"
|
||||||
@@ -110,20 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManageme
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.Application.Contracts", "module\tenant-management\Yi.Framework.TenantManagement.Application.Contracts\Yi.Framework.TenantManagement.Application.Contracts.csproj", "{FA735055-CBDD-4EFD-B84B-85810DA1425E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.Application.Contracts", "module\tenant-management\Yi.Framework.TenantManagement.Application.Contracts\Yi.Framework.TenantManagement.Application.Contracts.csproj", "{FA735055-CBDD-4EFD-B84B-85810DA1425E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "code-gen", "code-gen", "{4FFE7212-21F2-476D-B628-3C65E6C5075E}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Application", "module\code-gen\Yi.Framework.CodeGen.Application\Yi.Framework.CodeGen.Application.csproj", "{97EC40D7-DBFA-467A-98CB-221AF27B14F2}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Application.Contracts", "module\code-gen\Yi.Framework.CodeGen.Application.Contracts\Yi.Framework.CodeGen.Application.Contracts.csproj", "{882BC563-2F75-4B95-AC96-F4BF23F5E69D}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Domain", "module\code-gen\Yi.Framework.CodeGen.Domain\Yi.Framework.CodeGen.Domain.csproj", "{85CB8517-2B80-42D8-B954-081079AC9BA0}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Domain.Shared", "module\code-gen\Yi.Framework.CodeGen.Domain.Shared\Yi.Framework.CodeGen.Domain.Shared.csproj", "{EEFF0F05-2709-4151-A8CE-667935CEAE0B}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Caching.FreeRedis", "framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj", "{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Caching.FreeRedis", "framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj", "{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.SqlSugarCore", "module\code-gen\Yi.Framework.CodeGen.SqlSugarCore\Yi.Framework.CodeGen.SqlSugarCore.csproj", "{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{8B27846A-043D-4F2F-8140-5CEC9D1863B5}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{8B27846A-043D-4F2F-8140-5CEC9D1863B5}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.HttpApi.Client", "client\Yi.Abp.HttpApi.Client\Yi.Abp.HttpApi.Client.csproj", "{6B554DCC-3A81-4624-9141-4E39365ADA35}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.HttpApi.Client", "client\Yi.Abp.HttpApi.Client\Yi.Abp.HttpApi.Client.csproj", "{6B554DCC-3A81-4624-9141-4E39365ADA35}"
|
||||||
@@ -138,18 +102,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagem
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagement.SqlSugarCore", "module\setting-management\Yi.Framework.SettingManagement.SqlSugarCore\Yi.Framework.SettingManagement.SqlSugarCore.csproj", "{495C4643-39D4-46E7-BDC8-237589627BE4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagement.SqlSugarCore", "module\setting-management\Yi.Framework.SettingManagement.SqlSugarCore\Yi.Framework.SettingManagement.SqlSugarCore.csproj", "{495C4643-39D4-46E7-BDC8-237589627BE4}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "chat-hub", "chat-hub", "{D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Application.Contracts", "module\chat-hub\Yi.Framework.ChatHub.Application.Contracts\Yi.Framework.ChatHub.Application.Contracts.csproj", "{65D4D033-5504-44B9-B152-0172ACD64CE6}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Domain.Shared", "module\chat-hub\Yi.Framework.ChatHub.Domain.Shared\Yi.Framework.ChatHub.Domain.Shared.csproj", "{DEEC0B15-190C-4464-B469-C45C6563C592}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.SqlSugarCore", "module\chat-hub\Yi.Framework.ChatHub.SqlSugarCore\Yi.Framework.ChatHub.SqlSugarCore.csproj", "{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Domain", "module\chat-hub\Yi.Framework.ChatHub.Domain\Yi.Framework.ChatHub.Domain.csproj", "{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Application", "module\chat-hub\Yi.Framework.ChatHub.Application\Yi.Framework.ChatHub.Application.csproj", "{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Test", "test\Yi.Framework.Rbac.Test\Yi.Framework.Rbac.Test.csproj", "{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Test", "test\Yi.Framework.Rbac.Test\Yi.Framework.Rbac.Test.csproj", "{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{084CBEEC-5D37-4716-B9C7-D80D6960DFF4}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{084CBEEC-5D37-4716-B9C7-D80D6960DFF4}"
|
||||||
@@ -170,6 +122,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Tool.HttpApi.Client"
|
|||||||
EndProject
|
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}"
|
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
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -248,26 +204,6 @@ Global
|
|||||||
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{EB9349E2-256D-41EB-A345-21635A1361B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{EB9349E2-256D-41EB-A345-21635A1361B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{EB9349E2-256D-41EB-A345-21635A1361B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{EB9349E2-256D-41EB-A345-21635A1361B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -276,30 +212,6 @@ Global
|
|||||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.Build.0 = 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
|
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -328,30 +240,10 @@ Global
|
|||||||
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.Build.0 = Release|Any CPU
|
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -372,26 +264,6 @@ Global
|
|||||||
{495C4643-39D4-46E7-BDC8-237589627BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{495C4643-39D4-46E7-BDC8-237589627BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{DEEC0B15-190C-4464-B469-C45C6563C592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DEEC0B15-190C-4464-B469-C45C6563C592}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DEEC0B15-190C-4464-B469-C45C6563C592}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DEEC0B15-190C-4464-B469-C45C6563C592}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -428,6 +300,14 @@ Global
|
|||||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.Build.0 = Release|Any CPU
|
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -452,21 +332,9 @@ Global
|
|||||||
{C04D3F71-1557-46D0-B810-97B1FBB6AB73} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
|
{C04D3F71-1557-46D0-B810-97B1FBB6AB73} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
|
||||||
{A2BB899D-4F9A-4184-81BD-94B938E2AB03} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
|
{A2BB899D-4F9A-4184-81BD-94B938E2AB03} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
|
||||||
{4503A2F9-139D-4CBC-AF11-689C34F0D77B} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
|
{4503A2F9-139D-4CBC-AF11-689C34F0D77B} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
|
||||||
{E902A945-4F41-4E96-A0DA-9F66CDA22261} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
|
||||||
{EB9349E2-256D-41EB-A345-21635A1361B3} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
|
|
||||||
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
|
|
||||||
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
|
|
||||||
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
|
|
||||||
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
|
|
||||||
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||||
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
|
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
|
||||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
{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}
|
{68627BC2-F049-4C69-AD17-81DF9478E8CE} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
|
||||||
{499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
{499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||||
{FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
{FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
||||||
@@ -475,25 +343,13 @@ Global
|
|||||||
{9C8C3C53-3DCE-4516-867E-228858E61B26} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
|
{9C8C3C53-3DCE-4516-867E-228858E61B26} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
|
||||||
{17816837-E53B-486B-B796-53C601FE6CD9} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
{17816837-E53B-486B-B796-53C601FE6CD9} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
||||||
{FA735055-CBDD-4EFD-B84B-85810DA1425E} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
{FA735055-CBDD-4EFD-B84B-85810DA1425E} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
||||||
{4FFE7212-21F2-476D-B628-3C65E6C5075E} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
|
||||||
{97EC40D7-DBFA-467A-98CB-221AF27B14F2} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
|
|
||||||
{882BC563-2F75-4B95-AC96-F4BF23F5E69D} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
|
|
||||||
{85CB8517-2B80-42D8-B954-081079AC9BA0} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
|
|
||||||
{EEFF0F05-2709-4151-A8CE-667935CEAE0B} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
|
|
||||||
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||||
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
|
|
||||||
{6B554DCC-3A81-4624-9141-4E39365ADA35} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
|
{6B554DCC-3A81-4624-9141-4E39365ADA35} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
|
||||||
{2D23B44A-DFA3-4C36-8516-4F5AE442403C} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
|
{2D23B44A-DFA3-4C36-8516-4F5AE442403C} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
|
||||||
{00E49781-C6A0-491C-86A1-46F685C90915} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
|
{00E49781-C6A0-491C-86A1-46F685C90915} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
|
||||||
{8C68059E-F3B1-4D28-A1C9-A5830F53E5D3} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
{8C68059E-F3B1-4D28-A1C9-A5830F53E5D3} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||||
{6FEE0EB3-EAD2-47F8-B6FC-3D0FD3CCABFF} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
{6FEE0EB3-EAD2-47F8-B6FC-3D0FD3CCABFF} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
||||||
{495C4643-39D4-46E7-BDC8-237589627BE4} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
{495C4643-39D4-46E7-BDC8-237589627BE4} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
||||||
{D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
|
||||||
{65D4D033-5504-44B9-B152-0172ACD64CE6} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
|
|
||||||
{DEEC0B15-190C-4464-B469-C45C6563C592} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
|
|
||||||
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
|
|
||||||
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
|
|
||||||
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
|
|
||||||
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
|
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
|
||||||
{4FEBBDD9-E4F4-4BAF-8599-E2D57C08A74F} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
{4FEBBDD9-E4F4-4BAF-8599-E2D57C08A74F} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||||
{2CE51D4C-1EF9-462B-BA14-7EA01A7E4AF1} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
{2CE51D4C-1EF9-462B-BA14-7EA01A7E4AF1} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||||
@@ -503,6 +359,8 @@ Global
|
|||||||
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||||
{C8F97775-D903-4365-A4FF-3DA97E318CD2} = {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}
|
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
||||||
|
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||||
|
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}
|
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<SatelliteResourceLanguages>en;zh-CN</SatelliteResourceLanguages>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<NoWarn>$(NoWarn);CS1591;CS8618;CS1998;CS8604;CS8620;CS8600;CS8602</NoWarn>
|
<NoWarn>$(NoWarn);CS1591;CS8618;CS1998;CS8604;CS8620;CS8600;CS8602</NoWarn>
|
||||||
|
|||||||
@@ -4,13 +4,23 @@ using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares;
|
|||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
|
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 提供API信息处理的应用程序构建器扩展方法
|
||||||
|
/// </summary>
|
||||||
public static class ApiInfoBuilderExtensions
|
public static class ApiInfoBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseYiApiHandlinge([NotNull] this IApplicationBuilder app)
|
/// <summary>
|
||||||
|
/// 使用Yi框架的API信息处理中间件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">应用程序构建器实例</param>
|
||||||
|
/// <returns>配置后的应用程序构建器实例</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">当builder参数为null时抛出</exception>
|
||||||
|
public static IApplicationBuilder UseApiInfoHandling([NotNull] this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
app.UseMiddleware<ApiInfoMiddleware>();
|
// 添加API信息处理中间件到请求管道
|
||||||
return app;
|
builder.UseMiddleware<ApiInfoMiddleware>();
|
||||||
|
|
||||||
|
return builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,49 +5,101 @@ using Volo.Abp.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
|
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public static class SwaggerBuilderExtensons
|
/// <summary>
|
||||||
|
/// Swagger构建器扩展类
|
||||||
|
/// </summary>
|
||||||
|
public static class SwaggerBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseYiSwagger(this IApplicationBuilder app, params SwaggerModel[] swaggerModels)
|
/// <summary>
|
||||||
|
/// 配置并使用Yi框架的Swagger中间件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">应用程序构建器</param>
|
||||||
|
/// <param name="swaggerConfigs">Swagger配置模型数组</param>
|
||||||
|
/// <returns>应用程序构建器</returns>
|
||||||
|
public static IApplicationBuilder UseYiSwagger(
|
||||||
|
this IApplicationBuilder app,
|
||||||
|
params SwaggerConfiguration[] swaggerConfigs)
|
||||||
{
|
{
|
||||||
var mvcOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value;
|
if (app == null)
|
||||||
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI(c =>
|
|
||||||
{
|
{
|
||||||
foreach (var setting in mvcOptions.ConventionalControllers.ConventionalControllerSettings)
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
var mvcOptions = app.ApplicationServices
|
||||||
|
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>()
|
||||||
|
.Value;
|
||||||
|
|
||||||
|
// 启用Swagger中间件
|
||||||
|
app.UseSwagger();
|
||||||
|
|
||||||
|
// 配置SwaggerUI
|
||||||
|
app.UseSwaggerUI(options =>
|
||||||
|
{
|
||||||
|
// 添加约定控制器的Swagger终结点
|
||||||
|
var conventionalSettings = mvcOptions.ConventionalControllers.ConventionalControllerSettings;
|
||||||
|
foreach (var setting in conventionalSettings)
|
||||||
{
|
{
|
||||||
c.SwaggerEndpoint($"/swagger/{setting.RemoteServiceName}/swagger.json", setting.RemoteServiceName);
|
options.SwaggerEndpoint(
|
||||||
|
$"/swagger/{setting.RemoteServiceName}/swagger.json",
|
||||||
|
setting.RemoteServiceName);
|
||||||
}
|
}
|
||||||
if (mvcOptions.ConventionalControllers.ConventionalControllerSettings.Count==0&&swaggerModels.Length == 0)
|
|
||||||
|
// 如果没有配置任何终结点,使用默认配置
|
||||||
|
if (!conventionalSettings.Any() && (swaggerConfigs == null || !swaggerConfigs.Any()))
|
||||||
{
|
{
|
||||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework");
|
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// 添加自定义Swagger配置的终结点
|
||||||
|
if (swaggerConfigs != null)
|
||||||
{
|
{
|
||||||
foreach (var k in swaggerModels)
|
foreach (var config in swaggerConfigs)
|
||||||
{
|
{
|
||||||
c.SwaggerEndpoint(k.Url, k.Name);
|
options.SwaggerEndpoint(config.Url, config.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public class SwaggerModel
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swagger配置模型
|
||||||
|
/// </summary>
|
||||||
|
public class SwaggerConfiguration
|
||||||
{
|
{
|
||||||
public SwaggerModel(string name)
|
private const string DefaultSwaggerUrl = "/swagger/v1/swagger.json";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swagger JSON文档的URL
|
||||||
|
/// </summary>
|
||||||
|
public string Url { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swagger文档的显示名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用默认URL创建Swagger配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">文档显示名称</param>
|
||||||
|
public SwaggerConfiguration(string name)
|
||||||
|
: this(DefaultSwaggerUrl, name)
|
||||||
{
|
{
|
||||||
this.Name = name;
|
|
||||||
this.Url = "/swagger/v1/swagger.json";
|
|
||||||
}
|
}
|
||||||
public SwaggerModel(string url, string name)
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建自定义Swagger配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">Swagger JSON文档URL</param>
|
||||||
|
/// <param name="name">文档显示名称</param>
|
||||||
|
public SwaggerConfiguration(string url, string name)
|
||||||
{
|
{
|
||||||
this.Url = url;
|
Url = url ?? throw new ArgumentNullException(nameof(url));
|
||||||
this.Name = name;
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
public string Url { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,61 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Json;
|
|
||||||
using Yi.Framework.Core.Extensions;
|
using Yi.Framework.Core.Extensions;
|
||||||
using static System.Net.WebRequestMethods;
|
|
||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares
|
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API响应信息处理中间件
|
||||||
|
/// 主要用于处理特定文件类型的响应头信息
|
||||||
|
/// </summary>
|
||||||
[DebuggerStepThrough]
|
[DebuggerStepThrough]
|
||||||
public class ApiInfoMiddleware : IMiddleware, ITransientDependency
|
public class ApiInfoMiddleware : IMiddleware, ITransientDependency
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 处理HTTP请求的中间件方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">HTTP上下文</param>
|
||||||
|
/// <param name="next">请求处理委托</param>
|
||||||
|
/// <returns>异步任务</returns>
|
||||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||||
{
|
{
|
||||||
context.Response.OnStarting([DebuggerStepThrough] () =>
|
// // 在响应开始时处理文件下载相关的响应头
|
||||||
{
|
// context.Response.OnStarting(() =>
|
||||||
if (context.Response.StatusCode == StatusCodes.Status200OK
|
// {
|
||||||
&& context.Response.Headers["Content-Type"].ToString() == "application/vnd.ms-excel")
|
// HandleFileDownloadResponse(context);
|
||||||
{
|
// return Task.CompletedTask;
|
||||||
context.FileAttachmentHandle($"{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.xlsx");
|
// });
|
||||||
}
|
|
||||||
if (context.Response.StatusCode == StatusCodes.Status200OK &&
|
|
||||||
context.Response.Headers["Content-Type"].ToString() == "application/x-zip-compressed")
|
|
||||||
{
|
|
||||||
context.FileAttachmentHandle($"{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// 继续处理管道中的下一个中间件
|
||||||
await next(context);
|
await next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理文件下载响应的响应头信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">HTTP上下文</param>
|
||||||
|
private static void HandleFileDownloadResponse(HttpContext context)
|
||||||
|
{
|
||||||
|
// 仅处理状态码为200的响应
|
||||||
|
if (context.Response.StatusCode != StatusCodes.Status200OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentType = context.Response.Headers["Content-Type"].ToString();
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
|
||||||
|
// 处理Excel文件下载
|
||||||
|
if (contentType == "application/vnd.ms-excel")
|
||||||
|
{
|
||||||
|
context.FileAttachmentHandle($"{timestamp}.xlsx");
|
||||||
|
}
|
||||||
|
// 处理ZIP文件下载
|
||||||
|
else if (contentType == "application/x-zip-compressed")
|
||||||
|
{
|
||||||
|
context.FileAttachmentHandle($"{timestamp}.zip");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,148 +9,225 @@ using Microsoft.OpenApi.Any;
|
|||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using Volo.Abp.AspNetCore.Mvc;
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
|
using Volo.Abp.AspNetCore.Mvc.Conventions;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.Options;
|
||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Swagger生成器扩展类
|
||||||
|
/// </summary>
|
||||||
public static class SwaggerAddExtensions
|
public static class SwaggerAddExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services, Action<SwaggerGenOptions>? action=null)
|
/// <summary>
|
||||||
|
/// 添加Yi框架的Swagger生成器服务
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TProgram">程序入口类型</typeparam>
|
||||||
|
/// <param name="services">服务集合</param>
|
||||||
|
/// <param name="setupAction">自定义配置动作</param>
|
||||||
|
/// <returns>服务集合</returns>
|
||||||
|
public static IServiceCollection AddYiSwaggerGen<TProgram>(
|
||||||
|
this IServiceCollection services,
|
||||||
|
Action<SwaggerGenOptions>? setupAction = null)
|
||||||
{
|
{
|
||||||
|
// 获取MVC配置选项
|
||||||
|
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
// 获取并去重远程服务名称
|
||||||
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();
|
var remoteServiceSettings = mvcOptions.ConventionalControllers
|
||||||
|
.ConventionalControllerSettings
|
||||||
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
|
.DistinctBy(x => x.RemoteServiceName);
|
||||||
|
|
||||||
|
|
||||||
services.AddAbpSwaggerGen(
|
services.AddAbpSwaggerGen(
|
||||||
options =>
|
options =>
|
||||||
{
|
|
||||||
if (action is not null)
|
|
||||||
{
|
{
|
||||||
action.Invoke(options);
|
// 应用外部配置
|
||||||
|
setupAction?.Invoke(options);
|
||||||
|
|
||||||
|
// 配置API文档分组
|
||||||
|
ConfigureApiGroups(options, remoteServiceSettings);
|
||||||
|
|
||||||
|
// 配置API文档过滤器
|
||||||
|
ConfigureApiFilter(options, remoteServiceSettings);
|
||||||
|
|
||||||
|
// 配置Schema ID生成规则
|
||||||
|
options.CustomSchemaIds(type => type.FullName);
|
||||||
|
|
||||||
|
// 包含XML注释文档
|
||||||
|
IncludeXmlComments<TProgram>(options);
|
||||||
|
|
||||||
|
// 配置JWT认证
|
||||||
|
ConfigureJwtAuthentication(options);
|
||||||
|
|
||||||
|
// 添加自定义过滤器
|
||||||
|
ConfigureCustomFilters(options);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
|
|
||||||
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
|
|
||||||
{
|
|
||||||
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
|
|
||||||
{
|
|
||||||
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据分组名称过滤 API 文档
|
|
||||||
options.DocInclusionPredicate((docName, apiDesc) =>
|
|
||||||
{
|
|
||||||
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>();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置API分组
|
||||||
|
/// </summary>
|
||||||
|
private static void ConfigureApiGroups(
|
||||||
|
SwaggerGenOptions options,
|
||||||
|
IEnumerable<ConventionalControllerSetting> settings)
|
||||||
|
{
|
||||||
|
foreach (var setting in settings.OrderBy(x => x.RemoteServiceName))
|
||||||
|
{
|
||||||
|
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
|
||||||
|
{
|
||||||
|
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo
|
||||||
|
{
|
||||||
|
Title = setting.RemoteServiceName,
|
||||||
|
Version = "v1"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置API文档过滤器
|
||||||
|
/// </summary>
|
||||||
|
private static void ConfigureApiFilter(
|
||||||
|
SwaggerGenOptions options,
|
||||||
|
IEnumerable<ConventionalControllerSetting> settings)
|
||||||
|
{
|
||||||
|
options.DocInclusionPredicate((docName, apiDesc) =>
|
||||||
|
{
|
||||||
|
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerDesc)
|
||||||
|
{
|
||||||
|
var matchedSetting = settings
|
||||||
|
.FirstOrDefault(x => x.Assembly == controllerDesc.ControllerTypeInfo.Assembly);
|
||||||
|
return matchedSetting?.RemoteServiceName == docName;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包含XML注释文档
|
||||||
|
/// </summary>
|
||||||
|
private static void IncludeXmlComments<TProgram>(SwaggerGenOptions options)
|
||||||
|
{
|
||||||
|
var basePath = Path.GetDirectoryName(typeof(TProgram).Assembly.Location);
|
||||||
|
if (basePath is not null)
|
||||||
|
{
|
||||||
|
foreach (var xmlFile in Directory.GetFiles(basePath, "*.xml"))
|
||||||
|
{
|
||||||
|
options.IncludeXmlComments(xmlFile, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置JWT认证
|
||||||
|
/// </summary>
|
||||||
|
private static void ConfigureJwtAuthentication(SwaggerGenOptions options)
|
||||||
|
{
|
||||||
|
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Description = "请在此输入JWT 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] = Array.Empty<string>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置自定义过滤器
|
||||||
|
/// </summary>
|
||||||
|
private static void ConfigureCustomFilters(SwaggerGenOptions options)
|
||||||
|
{
|
||||||
|
options.OperationFilter<TenantHeaderOperationFilter>();
|
||||||
|
options.SchemaFilter<EnumSchemaFilter>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
|
/// Swagger文档枚举字段显示过滤器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EnumSchemaFilter : ISchemaFilter
|
public class EnumSchemaFilter : ISchemaFilter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实现接口
|
/// 应用枚举架构过滤器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model"></param>
|
/// <param name="schema">OpenAPI架构</param>
|
||||||
/// <param name="context"></param>
|
/// <param name="context">架构过滤器上下文</param>
|
||||||
|
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||||
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
|
||||||
{
|
{
|
||||||
if (context.Type.IsEnum)
|
if (!context.Type.IsEnum) return;
|
||||||
|
|
||||||
|
schema.Enum.Clear();
|
||||||
|
schema.Type = "string";
|
||||||
|
schema.Format = null;
|
||||||
|
|
||||||
|
var enumDescriptions = new StringBuilder();
|
||||||
|
foreach (var enumName in Enum.GetNames(context.Type))
|
||||||
{
|
{
|
||||||
model.Enum.Clear();
|
var enumValue = (Enum)Enum.Parse(context.Type, enumName);
|
||||||
model.Type = "string";
|
var description = GetEnumDescription(enumValue);
|
||||||
model.Format = null;
|
var enumIntValue = Convert.ToInt64(enumValue);
|
||||||
|
|
||||||
|
schema.Enum.Add(new OpenApiString(enumName));
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
enumDescriptions.AppendLine(
|
||||||
Enum.GetNames(context.Type)
|
$"【枚举:{enumName}{(description is null ? string.Empty : $"({description})")}={enumIntValue}】");
|
||||||
.ToList()
|
|
||||||
.ForEach(name =>
|
|
||||||
{
|
|
||||||
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 />");
|
|
||||||
});
|
|
||||||
model.Description= stringBuilder.ToString();
|
|
||||||
}
|
}
|
||||||
|
schema.Description = enumDescriptions.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取枚举描述特性值
|
||||||
|
/// </summary>
|
||||||
private static string? GetEnumDescription(Enum value)
|
private static string? GetEnumDescription(Enum value)
|
||||||
{
|
{
|
||||||
var fieldInfo = value.GetType().GetField(value.ToString());
|
var fieldInfo = value.GetType().GetField(value.ToString());
|
||||||
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||||
return attributes.Length > 0 ? attributes[0].Description : null;
|
return attributes.Length > 0 ? attributes[0].Description : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public class AddRequiredHeaderParameter : IOperationFilter
|
/// 租户头部参数过滤器
|
||||||
|
/// </summary>
|
||||||
|
public class TenantHeaderOperationFilter : IOperationFilter
|
||||||
{
|
{
|
||||||
public static string HeaderKey { get; set; } = "__tenant";
|
/// <summary>
|
||||||
|
/// 租户标识键名
|
||||||
|
/// </summary>
|
||||||
|
private const string TenantHeaderKey = "__tenant";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用租户头部参数过滤器
|
||||||
|
/// </summary>
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
{
|
{
|
||||||
if (operation.Parameters == null)
|
operation.Parameters ??= new List<OpenApiParameter>();
|
||||||
operation.Parameters = new List<OpenApiParameter>();
|
|
||||||
operation.Parameters.Add(new OpenApiParameter
|
operation.Parameters.Add(new OpenApiParameter
|
||||||
{
|
{
|
||||||
Name = HeaderKey,
|
Name = TenantHeaderKey,
|
||||||
In = ParameterLocation.Header,
|
In = ParameterLocation.Header,
|
||||||
Required = false,
|
Required = false,
|
||||||
AllowEmptyValue = true,
|
AllowEmptyValue = true,
|
||||||
Description="租户id或者租户名称(可空为默认租户)"
|
Description = "租户ID或租户名称(留空表示默认租户)"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,65 +9,129 @@ using Volo.Abp.Reflection;
|
|||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.Mvc
|
namespace Yi.Framework.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义路由构建器,用于生成API路由规则
|
||||||
|
/// </summary>
|
||||||
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
|
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
|
||||||
[ExposeServices(typeof(IConventionalRouteBuilder))]
|
[ExposeServices(typeof(IConventionalRouteBuilder))]
|
||||||
public class YiConventionalRouteBuilder : ConventionalRouteBuilder
|
public class YiConventionalRouteBuilder : ConventionalRouteBuilder
|
||||||
{
|
{
|
||||||
public YiConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options) : base(options)
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">ABP约定控制器配置选项</param>
|
||||||
|
public YiConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options)
|
||||||
|
: base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建API路由
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rootPath">根路径</param>
|
||||||
|
/// <param name="controllerName">控制器名称</param>
|
||||||
|
/// <param name="action">Action模型</param>
|
||||||
|
/// <param name="httpMethod">HTTP方法</param>
|
||||||
|
/// <param name="configuration">控制器配置</param>
|
||||||
|
/// <returns>构建的路由URL</returns>
|
||||||
public override string Build(
|
public override string Build(
|
||||||
string rootPath,
|
string rootPath,
|
||||||
string controllerName,
|
string controllerName,
|
||||||
ActionModel action,
|
ActionModel action,
|
||||||
string httpMethod,
|
string httpMethod,
|
||||||
[CanBeNull] ConventionalControllerSetting configuration)
|
[CanBeNull] ConventionalControllerSetting configuration)
|
||||||
{
|
{
|
||||||
|
// 获取API路由前缀
|
||||||
var apiRoutePrefix = GetApiRoutePrefix(action, configuration);
|
var apiRoutePrefix = GetApiRoutePrefix(action, configuration);
|
||||||
var controllerNameInUrl =
|
|
||||||
NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
|
|
||||||
|
|
||||||
var url = $"{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}";
|
// 规范化控制器名称
|
||||||
|
var normalizedControllerName = NormalizeUrlControllerName(
|
||||||
|
rootPath,
|
||||||
|
controllerName,
|
||||||
|
action,
|
||||||
|
httpMethod,
|
||||||
|
configuration);
|
||||||
|
|
||||||
//Add {id} path if needed
|
// 构建基础URL
|
||||||
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
|
var url = $"{rootPath}/{NormalizeControllerNameCase(normalizedControllerName, configuration)}";
|
||||||
if (idParameterModel != null)
|
|
||||||
{
|
|
||||||
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
|
|
||||||
{
|
|
||||||
url += "/{id}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var properties = idParameterModel
|
|
||||||
.ParameterType
|
|
||||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
|
||||||
|
|
||||||
foreach (var property in properties)
|
// 处理ID参数路由
|
||||||
{
|
url = BuildIdParameterRoute(url, action, configuration);
|
||||||
url += "/{" + NormalizeIdPropertyNameCase(property, configuration) + "}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add action name if needed
|
// 处理Action名称路由
|
||||||
var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod, configuration);
|
url = BuildActionNameRoute(url, rootPath, controllerName, action, httpMethod, configuration);
|
||||||
if (!actionNameInUrl.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
url += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}";
|
|
||||||
|
|
||||||
//Add secondary Id
|
|
||||||
var secondaryIds = action.Parameters
|
|
||||||
.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList();
|
|
||||||
if (secondaryIds.Count == 1)
|
|
||||||
{
|
|
||||||
url += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建ID参数路由部分
|
||||||
|
/// </summary>
|
||||||
|
private string BuildIdParameterRoute(
|
||||||
|
string baseUrl,
|
||||||
|
ActionModel action,
|
||||||
|
ConventionalControllerSetting configuration)
|
||||||
|
{
|
||||||
|
var idParameter = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
|
||||||
|
if (idParameter == null)
|
||||||
|
{
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理原始类型ID
|
||||||
|
if (TypeHelper.IsPrimitiveExtended(idParameter.ParameterType, includeEnums: true))
|
||||||
|
{
|
||||||
|
return $"{baseUrl}/{{id}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理复杂类型ID
|
||||||
|
var properties = idParameter.ParameterType
|
||||||
|
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
baseUrl += $"/{{{NormalizeIdPropertyNameCase(property, configuration)}}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建Action名称路由部分
|
||||||
|
/// </summary>
|
||||||
|
private string BuildActionNameRoute(
|
||||||
|
string baseUrl,
|
||||||
|
string rootPath,
|
||||||
|
string controllerName,
|
||||||
|
ActionModel action,
|
||||||
|
string httpMethod,
|
||||||
|
ConventionalControllerSetting configuration)
|
||||||
|
{
|
||||||
|
var actionNameInUrl = NormalizeUrlActionName(
|
||||||
|
rootPath,
|
||||||
|
controllerName,
|
||||||
|
action,
|
||||||
|
httpMethod,
|
||||||
|
configuration);
|
||||||
|
|
||||||
|
if (actionNameInUrl.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUrl += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}";
|
||||||
|
|
||||||
|
// 处理次要ID参数
|
||||||
|
var secondaryIds = action.Parameters
|
||||||
|
.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (secondaryIds.Count == 1)
|
||||||
|
{
|
||||||
|
baseUrl += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,46 @@ using Volo.Abp.Reflection;
|
|||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.Mvc
|
namespace Yi.Framework.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义服务约定实现,用于处理API路由和HTTP方法约束
|
||||||
|
/// </summary>
|
||||||
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
|
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
|
||||||
[ExposeServices(typeof(IAbpServiceConvention))]
|
[ExposeServices(typeof(IAbpServiceConvention))]
|
||||||
public class YiServiceConvention : AbpServiceConvention
|
public class YiServiceConvention : AbpServiceConvention
|
||||||
{
|
{
|
||||||
public YiServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options, IConventionalRouteBuilder conventionalRouteBuilder) : base(options, conventionalRouteBuilder)
|
/// <summary>
|
||||||
|
/// 初始化服务约定的新实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">ABP AspNetCore MVC 配置选项</param>
|
||||||
|
/// <param name="conventionalRouteBuilder">约定路由构建器</param>
|
||||||
|
public YiServiceConvention(
|
||||||
|
IOptions<AbpAspNetCoreMvcOptions> options,
|
||||||
|
IConventionalRouteBuilder conventionalRouteBuilder)
|
||||||
|
: base(options, conventionalRouteBuilder)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ConfigureSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
|
/// <summary>
|
||||||
|
/// 配置选择器,处理路由和HTTP方法约束
|
||||||
|
/// </summary>
|
||||||
|
protected override void ConfigureSelector(
|
||||||
|
string rootPath,
|
||||||
|
string controllerName,
|
||||||
|
ActionModel action,
|
||||||
|
ConventionalControllerSetting? configuration)
|
||||||
{
|
{
|
||||||
|
// 移除空选择器
|
||||||
RemoveEmptySelectors(action.Selectors);
|
RemoveEmptySelectors(action.Selectors);
|
||||||
|
|
||||||
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
|
// 检查远程服务特性
|
||||||
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(action.ActionMethod))
|
var remoteServiceAttr = ReflectionHelper
|
||||||
|
.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
|
||||||
|
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(action.ActionMethod))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据选择器是否存在执行不同的配置
|
||||||
if (!action.Selectors.Any())
|
if (!action.Selectors.Any())
|
||||||
{
|
{
|
||||||
AddAbpServiceSelector(rootPath, controllerName, action, configuration);
|
AddAbpServiceSelector(rootPath, controllerName, action, configuration);
|
||||||
@@ -41,56 +63,92 @@ namespace Yi.Framework.AspNetCore.Mvc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
protected override void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
|
/// 规范化选择器路由
|
||||||
{
|
/// </summary>
|
||||||
base.AddAbpServiceSelector(rootPath, controllerName, action, configuration);
|
protected override void NormalizeSelectorRoutes(
|
||||||
}
|
string rootPath,
|
||||||
|
string controllerName,
|
||||||
protected override void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
|
ActionModel action,
|
||||||
|
ConventionalControllerSetting? configuration)
|
||||||
{
|
{
|
||||||
foreach (var selector in action.Selectors)
|
foreach (var selector in action.Selectors)
|
||||||
{
|
{
|
||||||
var httpMethod = selector.ActionConstraints
|
// 获取HTTP方法约束
|
||||||
.OfType<HttpMethodActionConstraint>()
|
var httpMethod = GetOrCreateHttpMethod(selector, action, configuration);
|
||||||
.FirstOrDefault()?
|
|
||||||
.HttpMethods?
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (httpMethod == null)
|
|
||||||
{
|
|
||||||
httpMethod = SelectHttpMethod(action, configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selector.AttributeRouteModel == null)
|
|
||||||
{
|
|
||||||
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
var template = selector.AttributeRouteModel.Template;
|
|
||||||
if (!template.StartsWith("/"))
|
|
||||||
{
|
|
||||||
var route = $"{rootPath}/{template}";
|
|
||||||
selector.AttributeRouteModel.Template = route;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
|
|
||||||
{
|
|
||||||
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod }));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 处理路由模板
|
||||||
|
ConfigureRouteTemplate(selector, rootPath, controllerName, action, httpMethod, configuration);
|
||||||
|
|
||||||
|
// 确保HTTP方法约束存在
|
||||||
|
EnsureHttpMethodConstraint(selector, httpMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或创建HTTP方法
|
||||||
|
/// </summary>
|
||||||
|
private string GetOrCreateHttpMethod(
|
||||||
|
SelectorModel selector,
|
||||||
|
ActionModel action,
|
||||||
|
ConventionalControllerSetting? configuration)
|
||||||
|
{
|
||||||
|
return selector.ActionConstraints
|
||||||
|
.OfType<HttpMethodActionConstraint>()
|
||||||
|
.FirstOrDefault()?
|
||||||
|
.HttpMethods?
|
||||||
|
.FirstOrDefault()
|
||||||
|
?? SelectHttpMethod(action, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置路由模板
|
||||||
|
/// </summary>
|
||||||
|
private void ConfigureRouteTemplate(
|
||||||
|
SelectorModel selector,
|
||||||
|
string rootPath,
|
||||||
|
string controllerName,
|
||||||
|
ActionModel action,
|
||||||
|
string httpMethod,
|
||||||
|
ConventionalControllerSetting? configuration)
|
||||||
|
{
|
||||||
|
if (selector.AttributeRouteModel == null)
|
||||||
|
{
|
||||||
|
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(
|
||||||
|
rootPath,
|
||||||
|
controllerName,
|
||||||
|
action,
|
||||||
|
httpMethod,
|
||||||
|
configuration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NormalizeAttributeRouteTemplate(selector, rootPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 规范化特性路由模板
|
||||||
|
/// </summary>
|
||||||
|
private void NormalizeAttributeRouteTemplate(SelectorModel selector, string rootPath)
|
||||||
|
{
|
||||||
|
var template = selector.AttributeRouteModel.Template;
|
||||||
|
if (!template.StartsWith("/"))
|
||||||
|
{
|
||||||
|
selector.AttributeRouteModel.Template = $"{rootPath}/{template}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确保HTTP方法约束存在
|
||||||
|
/// </summary>
|
||||||
|
private void EnsureHttpMethodConstraint(SelectorModel selector, string httpMethod)
|
||||||
|
{
|
||||||
|
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
|
||||||
|
{
|
||||||
|
selector.ActionConstraints.Add(
|
||||||
|
new HttpMethodActionConstraint(new[] { httpMethod }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,32 +5,53 @@ using Volo.Abp.AspNetCore.WebClientInfo;
|
|||||||
|
|
||||||
namespace Yi.Framework.AspNetCore;
|
namespace Yi.Framework.AspNetCore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 真实IP地址提供程序,支持代理服务器场景
|
||||||
|
/// </summary>
|
||||||
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
|
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
|
||||||
{
|
{
|
||||||
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
|
private const string XForwardedForHeader = "X-Forwarded-For";
|
||||||
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化真实IP地址提供程序的新实例
|
||||||
|
/// </summary>
|
||||||
|
public RealIpHttpContextWebClientInfoProvider(
|
||||||
|
ILogger<HttpContextWebClientInfoProvider> logger,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
|
: base(logger, httpContextAccessor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取客户端IP地址,优先从X-Forwarded-For头部获取
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>客户端IP地址</returns>
|
||||||
protected override string? GetClientIpAddress()
|
protected override string? GetClientIpAddress()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var httpContext = HttpContextAccessor.HttpContext;
|
var httpContext = HttpContextAccessor.HttpContext;
|
||||||
|
if (httpContext == null)
|
||||||
var headers = httpContext?.Request?.Headers;
|
|
||||||
|
|
||||||
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
|
|
||||||
{
|
{
|
||||||
httpContext.Connection.RemoteIpAddress =
|
return null;
|
||||||
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpContext?.Connection?.RemoteIpAddress?.ToString();
|
var headers = httpContext.Request?.Headers;
|
||||||
|
if (headers != null && headers.ContainsKey(XForwardedForHeader))
|
||||||
|
{
|
||||||
|
// 从X-Forwarded-For获取真实客户端IP
|
||||||
|
var forwardedIp = headers[XForwardedForHeader].FirstOrDefault();
|
||||||
|
if (!string.IsNullOrEmpty(forwardedIp))
|
||||||
|
{
|
||||||
|
httpContext.Connection.RemoteIpAddress = IPAddress.Parse(forwardedIp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpContext.Connection?.RemoteIpAddress?.ToString();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogException(ex, LogLevel.Warning);
|
Logger.LogWarning(ex, "获取客户端IP地址时发生异常");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,55 @@
|
|||||||
namespace Yi.Framework.AspNetCore
|
namespace Yi.Framework.AspNetCore
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 远程服务成功响应信息
|
||||||
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class RemoteServiceSuccessInfo
|
public class RemoteServiceSuccessInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>.
|
/// 获取或设置响应代码
|
||||||
|
/// </summary>
|
||||||
|
public string? Code { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置响应消息
|
||||||
|
/// </summary>
|
||||||
|
public string? Message { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置详细信息
|
||||||
|
/// </summary>
|
||||||
|
public string? Details { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置响应数据
|
||||||
|
/// </summary>
|
||||||
|
public object? Data { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化远程服务成功响应信息的新实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RemoteServiceSuccessInfo()
|
public RemoteServiceSuccessInfo()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>.
|
/// 使用指定参数初始化远程服务成功响应信息的新实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="code">Error code</param>
|
/// <param name="message">响应消息</param>
|
||||||
/// <param name="details">Error details</param>
|
/// <param name="details">详细信息</param>
|
||||||
/// <param name="message">Error message</param>
|
/// <param name="code">响应代码</param>
|
||||||
/// <param name="data">Error data</param>
|
/// <param name="data">响应数据</param>
|
||||||
public RemoteServiceSuccessInfo(string message, string? details = null, string? code = null, object? data = null)
|
public RemoteServiceSuccessInfo(
|
||||||
|
string message,
|
||||||
|
string? details = null,
|
||||||
|
string? code = null,
|
||||||
|
object? data = null)
|
||||||
{
|
{
|
||||||
Message = message;
|
Message = message;
|
||||||
Details = details;
|
Details = details;
|
||||||
Code = code;
|
Code = code;
|
||||||
Data = data;
|
Data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// code.
|
|
||||||
/// </summary>
|
|
||||||
public string? Code { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// message.
|
|
||||||
/// </summary>
|
|
||||||
public string? Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// details.
|
|
||||||
/// </summary>
|
|
||||||
public string? Details { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// data.
|
|
||||||
/// </summary>
|
|
||||||
public object? Data { get; set; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// MIT 许可证
|
// MIT 许可证
|
||||||
//
|
//
|
||||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||||
//
|
//
|
||||||
@@ -17,25 +17,25 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Volo.Abp.AspNetCore.Mvc;
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.Validation;
|
||||||
using Yi.Framework.Core.Extensions;
|
using Yi.Framework.Core.Extensions;
|
||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 友好异常拦截器
|
/// 友好异常拦截器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异常拦截
|
/// 异常拦截
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task OnExceptionAsync(ExceptionContext context)
|
public async Task OnExceptionAsync(ExceptionContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
// 排除 WebSocket 请求处理
|
// 排除 WebSocket 请求处理
|
||||||
if (context.HttpContext.IsWebSocketRequest()) return;
|
if (context.HttpContext.IsWebSocketRequest()) return;
|
||||||
|
|
||||||
@@ -44,20 +44,23 @@ public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
|||||||
|
|
||||||
// 解析异常信息
|
// 解析异常信息
|
||||||
var exceptionMetadata = GetExceptionMetadata(context);
|
var exceptionMetadata = GetExceptionMetadata(context);
|
||||||
|
var unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
|
||||||
// 执行规范化异常处理
|
// 执行规范化异常处理
|
||||||
context.Result = unifyResult.OnException(context, exceptionMetadata);
|
context.Result = unifyResult.OnException(context, exceptionMetadata);
|
||||||
|
|
||||||
// 创建日志记录器
|
// 创建日志记录器
|
||||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
|
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
|
||||||
|
|
||||||
|
var errorMsg = "";
|
||||||
|
if (exceptionMetadata.Errors != null) errorMsg = "\n" + JsonConvert.SerializeObject(exceptionMetadata.Errors);
|
||||||
|
|
||||||
|
|
||||||
// 记录拦截日常
|
// 记录拦截日常
|
||||||
logger.LogError(context.Exception, context.Exception.Message);
|
logger.LogError(context.Exception, context.Exception.Message + errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取异常元数据
|
/// 获取异常元数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
@@ -74,22 +77,25 @@ public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
|||||||
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
|
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
|
||||||
var exception = context is ExceptionContext exContext
|
var exception = context is ExceptionContext exContext
|
||||||
? exContext.Exception
|
? exContext.Exception
|
||||||
: (
|
: context is ActionExecutedContext edContext
|
||||||
context is ActionExecutedContext edContext
|
? edContext.Exception
|
||||||
? edContext.Exception
|
: default;
|
||||||
: default
|
|
||||||
);
|
if (exception is AbpValidationException validationException)
|
||||||
|
{
|
||||||
|
errors = validationException.ValidationErrors;
|
||||||
|
isValidationException = true;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是否是友好异常
|
// 判断是否是友好异常
|
||||||
if (exception is UserFriendlyException friendlyException)
|
if (exception is UserFriendlyException friendlyException)
|
||||||
{
|
{
|
||||||
int statusCode2 = 500;
|
var statusCode2 = 500;
|
||||||
int.TryParse(friendlyException.Code, out statusCode2);
|
int.TryParse(friendlyException.Code, out statusCode2);
|
||||||
isFriendlyException = true;
|
isFriendlyException = true;
|
||||||
errorCode = friendlyException.Code;
|
errorCode = friendlyException.Code;
|
||||||
originErrorCode = friendlyException.Code;
|
originErrorCode = friendlyException.Code;
|
||||||
statusCode = statusCode2==0?403:statusCode2;
|
statusCode = statusCode2 == 0 ? 403 : statusCode2;
|
||||||
isValidationException = false;
|
|
||||||
errors = friendlyException.Message;
|
errors = friendlyException.Message;
|
||||||
data = friendlyException.Data;
|
data = friendlyException.Data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
|
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
|
||||||
|
using Volo.Abp.AspNetCore.Mvc.Response;
|
||||||
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||||
|
|
||||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||||
@@ -20,9 +21,10 @@ public static class UnifyResultExtensions
|
|||||||
services.AddTransient<FriendlyExceptionFilter>();
|
services.AddTransient<FriendlyExceptionFilter>();
|
||||||
services.AddMvc(options =>
|
services.AddMvc(options =>
|
||||||
{
|
{
|
||||||
|
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
|
||||||
|
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpNoContentActionFilter));
|
||||||
options.Filters.AddService<SucceededUnifyResultFilter>(99);
|
options.Filters.AddService<SucceededUnifyResultFilter>(99);
|
||||||
options.Filters.AddService<FriendlyExceptionFilter>(100);
|
options.Filters.AddService<FriendlyExceptionFilter>(100);
|
||||||
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
|
|
||||||
});
|
});
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,24 @@ using Yi.Framework.Core;
|
|||||||
|
|
||||||
namespace Yi.Framework.AspNetCore
|
namespace Yi.Framework.AspNetCore
|
||||||
{
|
{
|
||||||
[DependsOn(typeof(YiFrameworkCoreModule)
|
/// <summary>
|
||||||
)]
|
/// Yi框架ASP.NET Core模块
|
||||||
|
/// </summary>
|
||||||
|
[DependsOn(typeof(YiFrameworkCoreModule))]
|
||||||
public class YiFrameworkAspNetCoreModule : AbpModule
|
public class YiFrameworkAspNetCoreModule : AbpModule
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 配置服务后的处理
|
||||||
|
/// </summary>
|
||||||
public override void PostConfigureServices(ServiceConfigurationContext context)
|
public override void PostConfigureServices(ServiceConfigurationContext context)
|
||||||
{
|
{
|
||||||
var services = context.Services;
|
var services = context.Services;
|
||||||
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
|
|
||||||
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient));
|
// 替换默认的WebClientInfoProvider为支持代理的实现
|
||||||
|
services.Replace(new ServiceDescriptor(
|
||||||
|
typeof(IWebClientInfoProvider),
|
||||||
|
typeof(RealIpHttpContextWebClientInfoProvider),
|
||||||
|
ServiceLifetime.Transient));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Hangfire.Server;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.Threading;
|
||||||
|
using Volo.Abp.Uow;
|
||||||
|
|
||||||
|
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hangfire 工作单元过滤器
|
||||||
|
/// 用于管理后台任务的事务处理
|
||||||
|
/// </summary>
|
||||||
|
public sealed class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
|
||||||
|
{
|
||||||
|
private const string UnitOfWorkItemKey = "HangfireUnitOfWork";
|
||||||
|
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化工作单元过滤器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unitOfWorkManager">工作单元管理器</param>
|
||||||
|
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
|
||||||
|
{
|
||||||
|
_unitOfWorkManager = unitOfWorkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务执行前的处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">执行上下文</param>
|
||||||
|
public void OnPerforming(PerformingContext context)
|
||||||
|
{
|
||||||
|
// 开启一个工作单元并存储到上下文中
|
||||||
|
var uow = _unitOfWorkManager.Begin();
|
||||||
|
context.Items.Add(UnitOfWorkItemKey, uow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务执行后的处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">执行上下文</param>
|
||||||
|
public void OnPerformed(PerformedContext context)
|
||||||
|
{
|
||||||
|
AsyncHelper.RunSync(() => OnPerformedAsync(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务执行后的异步处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">执行上下文</param>
|
||||||
|
private async Task OnPerformedAsync(PerformedContext context)
|
||||||
|
{
|
||||||
|
if (!context.Items.TryGetValue(UnitOfWorkItemKey, out var obj) ||
|
||||||
|
obj is not IUnitOfWork uow)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 如果没有异常且工作单元未完成,则提交事务
|
||||||
|
if (context.Exception == null && !uow.IsCompleted)
|
||||||
|
{
|
||||||
|
await uow.CompleteAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 否则回滚事务
|
||||||
|
await uow.RollbackAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 确保工作单元被释放
|
||||||
|
uow.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<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.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
|
||||||
|
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
|
using Hangfire;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Volo.Abp.BackgroundJobs.Hangfire;
|
||||||
|
using Volo.Abp.BackgroundWorkers;
|
||||||
|
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||||
|
using Volo.Abp.DynamicProxy;
|
||||||
|
|
||||||
|
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hangfire 后台任务模块
|
||||||
|
/// </summary>
|
||||||
|
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule),
|
||||||
|
typeof(AbpBackgroundJobsHangfireModule))]
|
||||||
|
public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 配置服务前的预处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">服务配置上下文</param>
|
||||||
|
public override void PreConfigureServices(ServiceConfigurationContext context)
|
||||||
|
{
|
||||||
|
// 添加 Hangfire 后台任务约定注册器
|
||||||
|
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用程序初始化
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">应用程序初始化上下文</param>
|
||||||
|
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||||
|
{
|
||||||
|
// 获取后台任务管理器和所有 Hangfire 后台任务
|
||||||
|
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
|
||||||
|
var workers = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
|
||||||
|
|
||||||
|
// 获取配置
|
||||||
|
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||||
|
|
||||||
|
// 检查是否启用 Redis
|
||||||
|
var isRedisEnabled = configuration.GetValue<bool>("Redis:IsEnabled");
|
||||||
|
|
||||||
|
foreach (var worker in workers)
|
||||||
|
{
|
||||||
|
// 设置时区为本地时区(上海)
|
||||||
|
worker.TimeZone = TimeZoneInfo.Local;
|
||||||
|
|
||||||
|
if (isRedisEnabled)
|
||||||
|
{
|
||||||
|
// Redis 模式:使用 ABP 后台任务管理器
|
||||||
|
await backgroundWorkerManager.AddAsync(worker);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 内存模式:直接使用 Hangfire
|
||||||
|
var unProxyWorker = ProxyHelper.UnProxy(worker);
|
||||||
|
|
||||||
|
// 添加或更新循环任务
|
||||||
|
RecurringJob.AddOrUpdate(
|
||||||
|
worker.RecurringJobId,
|
||||||
|
(Expression<Func<Task>>)(() =>
|
||||||
|
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default)),
|
||||||
|
worker.CronExpression,
|
||||||
|
new RecurringJobOptions
|
||||||
|
{
|
||||||
|
TimeZone = worker.TimeZone
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用程序初始化前的预处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">应用程序初始化上下文</param>
|
||||||
|
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
|
||||||
|
{
|
||||||
|
// 添加工作单元过滤器
|
||||||
|
var services = context.ServiceProvider;
|
||||||
|
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hangfire 后台任务约定注册器
|
||||||
|
/// </summary>
|
||||||
|
public sealed class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 检查类型是否禁用约定注册
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">要检查的类型</param>
|
||||||
|
/// <returns>如果类型不是 IHangfireBackgroundWorker 或已被禁用则返回 true</returns>
|
||||||
|
protected override bool IsConventionalRegistrationDisabled(Type type)
|
||||||
|
{
|
||||||
|
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) ||
|
||||||
|
base.IsConventionalRegistrationDisabled(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取要暴露的服务类型列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">实现类型</param>
|
||||||
|
/// <returns>服务类型列表</returns>
|
||||||
|
protected override List<Type> GetExposedServiceTypes(Type type)
|
||||||
|
{
|
||||||
|
return new List<Type>
|
||||||
|
{
|
||||||
|
typeof(IHangfireBackgroundWorker)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using Hangfire.Dashboard;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.Users;
|
||||||
|
|
||||||
|
namespace Yi.Framework.BackgroundWorkers.Hangfire;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hangfire 仪表盘的令牌认证过滤器
|
||||||
|
/// </summary>
|
||||||
|
public sealed class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
|
||||||
|
{
|
||||||
|
private const string BearerPrefix = "Bearer ";
|
||||||
|
private const string TokenCookieKey = "Token";
|
||||||
|
private const string HtmlContentType = "text/html";
|
||||||
|
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private string _requiredUsername = "cc";
|
||||||
|
private TimeSpan _tokenExpiration = TimeSpan.FromMinutes(10);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化令牌认证过滤器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceProvider">服务提供者</param>
|
||||||
|
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置需要的用户名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">允许访问的用户名</param>
|
||||||
|
/// <returns>当前实例,支持链式调用</returns>
|
||||||
|
public YiTokenAuthorizationFilter SetRequiredUsername(string username)
|
||||||
|
{
|
||||||
|
_requiredUsername = username ?? throw new ArgumentNullException(nameof(username));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置令牌过期时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expiration">过期时间间隔</param>
|
||||||
|
/// <returns>当前实例,支持链式调用</returns>
|
||||||
|
public YiTokenAuthorizationFilter SetTokenExpiration(TimeSpan expiration)
|
||||||
|
{
|
||||||
|
_tokenExpiration = expiration;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 授权验证
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">仪表盘上下文</param>
|
||||||
|
/// <returns>是否通过授权</returns>
|
||||||
|
public bool Authorize(DashboardContext context)
|
||||||
|
{
|
||||||
|
var httpContext = context.GetHttpContext();
|
||||||
|
var currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
|
||||||
|
|
||||||
|
if (!currentUser.IsAuthenticated)
|
||||||
|
{
|
||||||
|
SetChallengeResponse(httpContext);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果验证通过,设置 cookie
|
||||||
|
var authorization = httpContext.Request.Headers.Authorization.ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(authorization) && authorization.StartsWith(BearerPrefix))
|
||||||
|
{
|
||||||
|
var token = authorization[BearerPrefix.Length..];
|
||||||
|
SetTokenCookie(httpContext, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentUser.UserName == _requiredUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置认证挑战响应
|
||||||
|
/// 当用户未认证时,返回一个包含令牌输入表单的HTML页面
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">HTTP 上下文</param>
|
||||||
|
private void SetChallengeResponse(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
httpContext.Response.StatusCode = 401;
|
||||||
|
httpContext.Response.ContentType = HtmlContentType;
|
||||||
|
|
||||||
|
var html = @"
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Hangfire Dashboard Authorization</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||||
|
.container { max-width: 400px; margin: 0 auto; }
|
||||||
|
.form-group { margin-bottom: 15px; }
|
||||||
|
input[type='text'] { width: 100%; padding: 8px; }
|
||||||
|
button { background: #337ab7; color: white; border: none; padding: 10px 15px; cursor: pointer; }
|
||||||
|
button:hover { background: #286090; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class='container'>
|
||||||
|
<h2>Authorization Required</h2>
|
||||||
|
<div class='form-group'>
|
||||||
|
<input type='text' id='token' placeholder='Enter your Bearer token...' />
|
||||||
|
</div>
|
||||||
|
<button onclick='authorize()'>Authorize</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function authorize() {
|
||||||
|
var token = document.getElementById('token').value;
|
||||||
|
if (token) {
|
||||||
|
document.cookie = 'Token=' + token + '; path=/';
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
|
||||||
|
httpContext.Response.WriteAsync(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置令牌 Cookie
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">HTTP 上下文</param>
|
||||||
|
/// <param name="token">令牌值</param>
|
||||||
|
private void SetTokenCookie(HttpContext httpContext, string token)
|
||||||
|
{
|
||||||
|
var cookieOptions = new CookieOptions
|
||||||
|
{
|
||||||
|
Expires = DateTimeOffset.Now.Add(_tokenExpiration),
|
||||||
|
HttpOnly = true,
|
||||||
|
Secure = httpContext.Request.IsHttps,
|
||||||
|
SameSite = SameSiteMode.Lax
|
||||||
|
};
|
||||||
|
|
||||||
|
httpContext.Response.Cookies.Append(TokenCookieKey, token, cookieOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> AuthorizeAsync(DashboardContext context)
|
||||||
|
{
|
||||||
|
return Task.FromResult(Authorize(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,28 +10,43 @@ using Volo.Abp.MultiTenancy;
|
|||||||
|
|
||||||
namespace Yi.Framework.Caching.FreeRedis
|
namespace Yi.Framework.Caching.FreeRedis
|
||||||
{
|
{
|
||||||
[Dependency(ReplaceServices =true)]
|
/// <summary>
|
||||||
|
/// 缓存键标准化处理器
|
||||||
|
/// 用于处理缓存键的格式化和多租户支持
|
||||||
|
/// </summary>
|
||||||
|
[Dependency(ReplaceServices = true)]
|
||||||
public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
|
public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
|
||||||
{
|
{
|
||||||
protected ICurrentTenant CurrentTenant { get; }
|
private readonly ICurrentTenant _currentTenant;
|
||||||
|
private readonly AbpDistributedCacheOptions _distributedCacheOptions;
|
||||||
protected AbpDistributedCacheOptions DistributedCacheOptions { get; }
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentTenant">当前租户服务</param>
|
||||||
|
/// <param name="distributedCacheOptions">分布式缓存配置选项</param>
|
||||||
public YiDistributedCacheKeyNormalizer(
|
public YiDistributedCacheKeyNormalizer(
|
||||||
ICurrentTenant currentTenant,
|
ICurrentTenant currentTenant,
|
||||||
IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
|
IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
|
||||||
{
|
{
|
||||||
CurrentTenant = currentTenant;
|
_currentTenant = currentTenant;
|
||||||
DistributedCacheOptions = distributedCacheOptions.Value;
|
_distributedCacheOptions = distributedCacheOptions.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标准化缓存键
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">缓存键标准化参数</param>
|
||||||
|
/// <returns>标准化后的缓存键</returns>
|
||||||
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
|
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
|
||||||
{
|
{
|
||||||
var normalizedKey = $"{DistributedCacheOptions.KeyPrefix}{args.Key}";
|
// 添加全局缓存前缀
|
||||||
|
var normalizedKey = $"{_distributedCacheOptions.KeyPrefix}{args.Key}";
|
||||||
|
|
||||||
//if (!args.IgnoreMultiTenancy && CurrentTenant.Id.HasValue)
|
//todo 多租户支持已注释,如需启用取消注释即可
|
||||||
|
//if (!args.IgnoreMultiTenancy && _currentTenant.Id.HasValue)
|
||||||
//{
|
//{
|
||||||
// normalizedKey = $"t:{CurrentTenant.Id.Value},{normalizedKey}";
|
// normalizedKey = $"t:{_currentTenant.Id.Value},{normalizedKey}";
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return normalizedKey;
|
return normalizedKey;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using FreeRedis;
|
using FreeRedis;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
@@ -7,26 +8,57 @@ using Volo.Abp.Caching;
|
|||||||
namespace Yi.Framework.Caching.FreeRedis
|
namespace Yi.Framework.Caching.FreeRedis
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 此模块得益于FreeRedis作者支持IDistributedCache,使用湿滑
|
/// FreeRedis缓存模块
|
||||||
|
/// 提供基于FreeRedis的分布式缓存实现
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DependsOn(typeof(AbpCachingModule))]
|
[DependsOn(typeof(AbpCachingModule))]
|
||||||
public class YiFrameworkCachingFreeRedisModule : AbpModule
|
public class YiFrameworkCachingFreeRedisModule : AbpModule
|
||||||
{
|
{
|
||||||
|
private const string RedisEnabledKey = "Redis:IsEnabled";
|
||||||
|
private const string RedisConfigurationKey = "Redis:Configuration";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">服务配置上下文</param>
|
||||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
var configuration = context.Services.GetConfiguration();
|
var configuration = context.Services.GetConfiguration();
|
||||||
|
|
||||||
var redisEnabled = configuration["Redis:IsEnabled"];
|
// 检查Redis是否启用
|
||||||
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
|
if (!IsRedisEnabled(configuration))
|
||||||
{
|
{
|
||||||
var redisConfiguration = configuration["Redis:Configuration"];
|
return;
|
||||||
RedisClient redisClient = new RedisClient(redisConfiguration);
|
|
||||||
|
|
||||||
context.Services.AddSingleton<IRedisClient>(redisClient);
|
|
||||||
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(new
|
|
||||||
DistributedCache(redisClient)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册Redis服务
|
||||||
|
RegisterRedisServices(context, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查Redis是否启用
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configuration">配置</param>
|
||||||
|
/// <returns>是否启用Redis</returns>
|
||||||
|
private static bool IsRedisEnabled(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var redisEnabled = configuration[RedisEnabledKey];
|
||||||
|
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册Redis相关服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">服务配置上下文</param>
|
||||||
|
/// <param name="configuration">配置</param>
|
||||||
|
private static void RegisterRedisServices(ServiceConfigurationContext context, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var redisConfiguration = configuration[RedisConfigurationKey];
|
||||||
|
var redisClient = new RedisClient(redisConfiguration);
|
||||||
|
|
||||||
|
context.Services.AddSingleton<IRedisClient>(redisClient);
|
||||||
|
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(
|
||||||
|
new DistributedCache(redisClient)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,21 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Data
|
namespace Yi.Framework.Core.Data
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 排序接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 实现此接口的实体类将支持排序功能
|
||||||
|
/// 通常用于列表数据的展示顺序控制
|
||||||
|
/// </remarks>
|
||||||
public interface IOrderNum
|
public interface IOrderNum
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 排序号
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 数字越小越靠前,默认为0
|
||||||
|
/// </remarks>
|
||||||
int OrderNum { get; set; }
|
int OrderNum { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,21 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Data
|
namespace Yi.Framework.Core.Data
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 状态接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 实现此接口的实体类将支持启用/禁用状态管理
|
||||||
|
/// 用于控制数据记录的可用状态
|
||||||
|
/// </remarks>
|
||||||
public interface IState
|
public interface IState
|
||||||
{
|
{
|
||||||
public bool State { get; set; }
|
/// <summary>
|
||||||
|
/// 状态标识
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// true表示启用,false表示禁用
|
||||||
|
/// </remarks>
|
||||||
|
bool State { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,37 @@ using System.Threading.Tasks;
|
|||||||
namespace Yi.Framework.Core.Enums
|
namespace Yi.Framework.Core.Enums
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 定义公共文件路径
|
/// 文件类型枚举
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于定义系统支持的文件类型分类
|
||||||
|
/// 主要用于文件上传和存储时的类型区分
|
||||||
|
/// </remarks>
|
||||||
public enum FileTypeEnum
|
public enum FileTypeEnum
|
||||||
{
|
{
|
||||||
File,
|
/// <summary>
|
||||||
Image,
|
/// 普通文件
|
||||||
Thumbnail,
|
/// </summary>
|
||||||
Excel,
|
file = 0,
|
||||||
Temp
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图片文件
|
||||||
|
/// </summary>
|
||||||
|
image = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 缩略图文件
|
||||||
|
/// </summary>
|
||||||
|
thumbnail = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Excel文件
|
||||||
|
/// </summary>
|
||||||
|
excel = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 临时文件
|
||||||
|
/// </summary>
|
||||||
|
temp = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,23 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Enums
|
namespace Yi.Framework.Core.Enums
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 排序方向枚举
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于定义数据查询时的排序方向
|
||||||
|
/// 常用于列表数据排序
|
||||||
|
/// </remarks>
|
||||||
public enum OrderByEnum
|
public enum OrderByEnum
|
||||||
{
|
{
|
||||||
Asc,
|
/// <summary>
|
||||||
Desc
|
/// 升序排列
|
||||||
|
/// </summary>
|
||||||
|
Asc = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 降序排列
|
||||||
|
/// </summary>
|
||||||
|
Desc = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,67 +6,91 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Enums
|
namespace Yi.Framework.Core.Enums
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 查询操作符枚举
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 定义查询条件中支持的操作符类型
|
||||||
|
/// 用于构建动态查询条件
|
||||||
|
/// </remarks>
|
||||||
public enum QueryOperatorEnum
|
public enum QueryOperatorEnum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 相等
|
/// 等于
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Equal,
|
Equal = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 匹配
|
/// 模糊匹配
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Like,
|
Like = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 大于
|
/// 大于
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GreaterThan,
|
GreaterThan = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 大于或等于
|
/// 大于或等于
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GreaterThanOrEqual,
|
GreaterThanOrEqual = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小于
|
/// 小于
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LessThan,
|
LessThan = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小于或等于
|
/// 小于或等于
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LessThanOrEqual,
|
LessThanOrEqual = 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 等于集合
|
/// 在指定集合中
|
||||||
/// </summary>
|
/// </summary>
|
||||||
In,
|
In = 6,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 不等于集合
|
/// 不在指定集合中
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NotIn,
|
NotIn = 7,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 左边匹配
|
/// 左侧模糊匹配
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LikeLeft,
|
LikeLeft = 8,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 右边匹配
|
/// 右侧模糊匹配
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LikeRight,
|
LikeRight = 9,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 不相等
|
/// 不等于
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NoEqual,
|
NoEqual = 10,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为空或空
|
/// 为null或空
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IsNullOrEmpty,
|
IsNullOrEmpty = 11,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 不为空
|
/// 不为null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IsNot,
|
IsNot = 12,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 不匹配
|
/// 不匹配
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NoLike,
|
NoLike = 13,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 时间段 值用 "|" 隔开
|
/// 日期范围
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DateRange
|
/// <remarks>
|
||||||
|
/// 使用"|"分隔起始和结束日期
|
||||||
|
/// </remarks>
|
||||||
|
DateRange = 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,33 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Enums
|
namespace Yi.Framework.Core.Enums
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API返回状态码枚举
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 定义API接口统一的返回状态码
|
||||||
|
/// 遵循HTTP状态码规范
|
||||||
|
/// </remarks>
|
||||||
public enum ResultCodeEnum
|
public enum ResultCodeEnum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 操作成功。
|
/// 操作成功
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Success = 200,
|
Success = 200,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 操作不成功
|
/// 未授权访问
|
||||||
/// </summary>
|
|
||||||
NotSuccess = 500,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 无权限
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NoPermission = 401,
|
NoPermission = 401,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 被拒绝
|
/// 访问被拒绝
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Denied = 403
|
Denied = 403,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作失败
|
||||||
|
/// </summary>
|
||||||
|
NotSuccess = 500
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,110 +4,131 @@ using Microsoft.AspNetCore.Http;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Extensions
|
namespace Yi.Framework.Core.Extensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HttpContext扩展方法类
|
||||||
|
/// </summary>
|
||||||
public static class HttpContextExtensions
|
public static class HttpContextExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置文件下载名称
|
/// 设置内联文件下载响应头
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext"></param>
|
/// <param name="httpContext">HTTP上下文</param>
|
||||||
/// <param name="fileName"></param>
|
/// <param name="fileName">文件名</param>
|
||||||
public static void FileInlineHandle(this HttpContext httpContext, string fileName)
|
public static void FileInlineHandle(this HttpContext httpContext, string fileName)
|
||||||
{
|
{
|
||||||
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
|
var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
|
||||||
httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename);
|
httpContext.Response.Headers.Add("Content-Disposition", $"inline;filename={encodeFilename}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置文件附件名称
|
/// 设置附件下载响应头
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext"></param>
|
/// <param name="httpContext">HTTP上下文</param>
|
||||||
/// <param name="fileName"></param>
|
/// <param name="fileName">文件名</param>
|
||||||
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
|
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
|
||||||
{
|
{
|
||||||
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
|
var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
|
||||||
httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename);
|
httpContext.Response.Headers.Add("Content-Disposition", $"attachment;filename={encodeFilename}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取语言种类
|
/// 获取客户端首选语言
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext"></param>
|
/// <param name="httpContext">HTTP上下文</param>
|
||||||
/// <returns></returns>
|
/// <returns>语言代码,默认返回zh-CN</returns>
|
||||||
public static string GetLanguage(this HttpContext httpContext)
|
public static string GetLanguage(this HttpContext httpContext)
|
||||||
{
|
{
|
||||||
string res = "zh-CN";
|
const string defaultLanguage = "zh-CN";
|
||||||
var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
|
var acceptLanguage = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
|
||||||
if (str is not null)
|
|
||||||
{
|
|
||||||
res = str.Split(",")[0];
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
|
|
||||||
|
return string.IsNullOrEmpty(acceptLanguage)
|
||||||
|
? defaultLanguage
|
||||||
|
: acceptLanguage.Split(',')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 判断是否为异步请求
|
/// 判断是否为Ajax请求
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
/// <param name="request">HTTP请求</param>
|
||||||
/// <returns></returns>
|
/// <returns>是否为Ajax请求</returns>
|
||||||
public static bool IsAjaxRequest(this HttpRequest request)
|
public static bool IsAjaxRequest(this HttpRequest request)
|
||||||
{
|
{
|
||||||
string header = request.Headers["X-Requested-With"];
|
const string ajaxHeader = "XMLHttpRequest";
|
||||||
return "XMLHttpRequest".Equals(header);
|
return ajaxHeader.Equals(request.Headers["X-Requested-With"],
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取客户端IP
|
/// 获取客户端IP地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context">HTTP上下文</param>
|
||||||
/// <returns></returns>
|
/// <returns>客户端IP地址</returns>
|
||||||
public static string GetClientIp(this HttpContext context)
|
public static string GetClientIp(this HttpContext context)
|
||||||
{
|
{
|
||||||
if (context == null) return "";
|
const string localhost = "127.0.0.1";
|
||||||
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
|
if (context == null) return string.Empty;
|
||||||
if (string.IsNullOrEmpty(result))
|
|
||||||
|
// 尝试获取X-Forwarded-For头
|
||||||
|
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
|
||||||
|
|
||||||
|
// 如果没有代理头,则获取远程IP
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
{
|
{
|
||||||
result = context.Connection.RemoteIpAddress?.ToString();
|
ip = context.Connection.RemoteIpAddress?.ToString();
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(result) || result.Contains("::1"))
|
|
||||||
result = "127.0.0.1";
|
|
||||||
|
|
||||||
result = result.Replace("::ffff:", "127.0.0.1");
|
// 处理特殊IP
|
||||||
//如果有端口号,删除端口号
|
if (string.IsNullOrEmpty(ip) || ip.Contains("::1"))
|
||||||
result = Regex.Replace(result, @":\d{1,5}$", "");
|
{
|
||||||
//Ip规则校验
|
return localhost;
|
||||||
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";
|
// 清理IPv6格式
|
||||||
return result;
|
ip = ip.Replace("::ffff:", localhost);
|
||||||
|
|
||||||
|
// 移除端口号
|
||||||
|
ip = Regex.Replace(ip, @":\d{1,5}$", "");
|
||||||
|
|
||||||
|
// 验证IP格式
|
||||||
|
var isValidIp = Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$") ||
|
||||||
|
Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):\d{1,5}$");
|
||||||
|
|
||||||
|
return isValidIp ? ip : localhost;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取浏览器标识
|
/// 获取User-Agent信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context">HTTP上下文</param>
|
||||||
/// <returns></returns>
|
/// <returns>User-Agent字符串</returns>
|
||||||
public static string GetUserAgent(this HttpContext context)
|
public static string GetUserAgent(this HttpContext context)
|
||||||
{
|
{
|
||||||
return context.Request.Headers["User-Agent"];
|
return context.Request.Headers["User-Agent"].ToString();
|
||||||
}
|
|
||||||
|
|
||||||
public static string[]? GetUserPermissions(this HttpContext context, string permissionsName)
|
|
||||||
{
|
|
||||||
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 判断是否是 WebSocket 请求
|
/// 获取用户权限声明值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context">HTTP上下文</param>
|
||||||
/// <returns></returns>
|
/// <param name="permissionsName">权限声明名称</param>
|
||||||
|
/// <returns>权限值数组</returns>
|
||||||
|
public static string[]? GetUserPermissions(this HttpContext context, string permissionsName)
|
||||||
|
{
|
||||||
|
return context.User.Claims
|
||||||
|
.Where(x => x.Type == permissionsName)
|
||||||
|
.Select(x => x.Value)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断是否为WebSocket请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">HTTP上下文</param>
|
||||||
|
/// <returns>是否为WebSocket请求</returns>
|
||||||
public static bool IsWebSocketRequest(this HttpContext context)
|
public static bool IsWebSocketRequest(this HttpContext context)
|
||||||
{
|
{
|
||||||
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
|
return context.WebSockets.IsWebSocketRequest ||
|
||||||
|
context.Request.Path == "/ws";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ namespace Yi.Framework.Core.Helper
|
|||||||
{
|
{
|
||||||
var extension = Path.GetExtension(fileName);
|
var extension = Path.GetExtension(fileName);
|
||||||
if (ImageType.Contains(extension.ToLower()))
|
if (ImageType.Contains(extension.ToLower()))
|
||||||
return FileTypeEnum.Image;
|
return FileTypeEnum.image;
|
||||||
return FileTypeEnum.File;
|
return FileTypeEnum.file;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Core.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DateTime JSON序列化转换器
|
||||||
|
/// </summary>
|
||||||
|
public class DatetimeJsonConverter : JsonConverter<DateTime>
|
||||||
|
{
|
||||||
|
private readonly string _dateFormat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化DateTime转换器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">日期格式化字符串,默认为yyyy-MM-dd HH:mm:ss</param>
|
||||||
|
public DatetimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
{
|
||||||
|
_dateFormat = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从JSON读取DateTime值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">JSON读取器</param>
|
||||||
|
/// <param name="typeToConvert">目标类型</param>
|
||||||
|
/// <param name="options">JSON序列化选项</param>
|
||||||
|
/// <returns>DateTime值</returns>
|
||||||
|
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
return DateTime.TryParse(reader.GetString(), out DateTime dateTime)
|
||||||
|
? dateTime
|
||||||
|
: reader.GetDateTime();
|
||||||
|
}
|
||||||
|
return reader.GetDateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将DateTime写入JSON
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">JSON写入器</param>
|
||||||
|
/// <param name="value">DateTime值</param>
|
||||||
|
/// <param name="options">JSON序列化选项</param>
|
||||||
|
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.ToString(_dateFormat));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,52 +8,81 @@ using Volo.Abp.Modularity;
|
|||||||
|
|
||||||
namespace Yi.Framework.Core.Modularity;
|
namespace Yi.Framework.Core.Modularity;
|
||||||
|
|
||||||
[Dependency(ReplaceServices =true)]
|
/// <summary>
|
||||||
|
/// Yi框架模块管理器
|
||||||
|
/// </summary>
|
||||||
|
[Dependency(ReplaceServices = true)]
|
||||||
public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency
|
public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency
|
||||||
{
|
{
|
||||||
private readonly IModuleContainer _moduleContainer;
|
private readonly IModuleContainer _moduleContainer;
|
||||||
private readonly IEnumerable<IModuleLifecycleContributor> _lifecycleContributors;
|
private readonly IEnumerable<IModuleLifecycleContributor> _lifecycleContributors;
|
||||||
private readonly ILogger<YiModuleManager> _logger;
|
private readonly ILogger<YiModuleManager> _logger;
|
||||||
|
|
||||||
public YiModuleManager(IModuleContainer moduleContainer, ILogger<YiModuleManager> logger, IOptions<AbpModuleLifecycleOptions> options, IServiceProvider serviceProvider) : base(moduleContainer, logger, options, serviceProvider)
|
/// <summary>
|
||||||
|
/// 初始化模块管理器
|
||||||
|
/// </summary>
|
||||||
|
public YiModuleManager(
|
||||||
|
IModuleContainer moduleContainer,
|
||||||
|
ILogger<YiModuleManager> logger,
|
||||||
|
IOptions<AbpModuleLifecycleOptions> options,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
|
: base(moduleContainer, logger, options, serviceProvider)
|
||||||
{
|
{
|
||||||
_moduleContainer = moduleContainer;
|
_moduleContainer = moduleContainer;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_lifecycleContributors = options.Value.Contributors.Select(serviceProvider.GetRequiredService).Cast<IModuleLifecycleContributor>().ToArray();
|
_lifecycleContributors = options.Value.Contributors
|
||||||
|
.Select(serviceProvider.GetRequiredService)
|
||||||
|
.Cast<IModuleLifecycleContributor>()
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化所有模块
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">应用程序初始化上下文</param>
|
||||||
public override async Task InitializeModulesAsync(ApplicationInitializationContext context)
|
public override async Task InitializeModulesAsync(ApplicationInitializationContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
_logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块==========");
|
_logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块==========");
|
||||||
var total = 0;
|
|
||||||
var watch =new Stopwatch();
|
var moduleCount = 0;
|
||||||
long totalTime = 0;
|
var stopwatch = new Stopwatch();
|
||||||
|
var totalTime = 0L;
|
||||||
|
|
||||||
foreach (var contributor in _lifecycleContributors)
|
foreach (var contributor in _lifecycleContributors)
|
||||||
{
|
{
|
||||||
foreach (var module in _moduleContainer.Modules)
|
foreach (var module in _moduleContainer.Modules)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
watch.Restart();
|
stopwatch.Restart();
|
||||||
await contributor.InitializeAsync(context, module.Instance);
|
await contributor.InitializeAsync(context, module.Instance);
|
||||||
watch.Stop();
|
stopwatch.Stop();
|
||||||
totalTime += watch.ElapsedMilliseconds;
|
|
||||||
total++;
|
|
||||||
if (watch.ElapsedMilliseconds > 1)
|
|
||||||
{
|
|
||||||
_logger.LogDebug($"耗时-{watch.ElapsedMilliseconds}ms,已加载模块-{module.Assembly.GetName().Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
totalTime += stopwatch.ElapsedMilliseconds;
|
||||||
|
moduleCount++;
|
||||||
|
|
||||||
|
// 仅记录耗时超过1ms的模块
|
||||||
|
if (stopwatch.ElapsedMilliseconds > 1)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(
|
||||||
|
"耗时-{Time}ms,已加载模块-{ModuleName}",
|
||||||
|
stopwatch.ElapsedMilliseconds,
|
||||||
|
module.Assembly.GetName().Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}: {ex.Message}. See the inner exception for details.", ex);
|
throw new AbpInitializationException(
|
||||||
|
$"模块 {module.Type.AssemblyQualifiedName} 在 {contributor.GetType().FullName} 阶段初始化失败: {ex.Message}",
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation($"==========【{total}】个模块初始化执行完毕,总耗时【{totalTime}ms】==========");
|
_logger.LogInformation(
|
||||||
|
"==========【{Count}】个模块初始化执行完毕,总耗时【{Time}ms】==========",
|
||||||
|
moduleCount,
|
||||||
|
totalTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Yi.Framework.Core.Options;
|
||||||
|
|
||||||
|
public class SemanticKernelOptions
|
||||||
|
{
|
||||||
|
public List<string> ModelIds { get; set; }
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
}
|
||||||
@@ -2,8 +2,28 @@
|
|||||||
|
|
||||||
namespace Yi.Framework.Core
|
namespace Yi.Framework.Core
|
||||||
{
|
{
|
||||||
public class YiFrameworkCoreModule:AbpModule
|
/// <summary>
|
||||||
|
/// Yi框架核心模块
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 提供框架的基础功能和核心服务
|
||||||
|
/// </remarks>
|
||||||
|
public class YiFrameworkCoreModule : AbpModule
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 配置服务
|
||||||
|
/// </summary>
|
||||||
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
|
{
|
||||||
|
base.ConfigureServices(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用程序初始化
|
||||||
|
/// </summary>
|
||||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||||
|
{
|
||||||
|
base.OnApplicationInitialization(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,17 @@ using Volo.Abp.Application.Services;
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application.Contracts
|
namespace Yi.Framework.Ddd.Application.Contracts
|
||||||
{
|
{
|
||||||
public interface IDeletesAppService<in TKey> : IDeleteAppService< TKey> , IApplicationService, IRemoteService
|
/// <summary>
|
||||||
|
/// 批量删除服务接口
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">主键类型</typeparam>
|
||||||
|
public interface IDeletesAppService<in TKey> : IDeleteAppService<TKey>, IApplicationService, IRemoteService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 批量删除实体
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ids">要删除的实体ID集合</param>
|
||||||
|
/// <returns>删除操作的异步任务</returns>
|
||||||
Task DeleteAsync(IEnumerable<TKey> ids);
|
Task DeleteAsync(IEnumerable<TKey> ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,19 @@
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application.Contracts
|
namespace Yi.Framework.Ddd.Application.Contracts
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 带时间范围的分页查询请求接口
|
||||||
|
/// </summary>
|
||||||
public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest
|
public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 查询开始时间
|
||||||
|
/// </summary>
|
||||||
DateTime? StartTime { get; set; }
|
DateTime? StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询结束时间
|
||||||
|
/// </summary>
|
||||||
DateTime? EndTime { get; set; }
|
DateTime? EndTime { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application.Contracts
|
namespace Yi.Framework.Ddd.Application.Contracts
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 分页查询请求接口,包含时间范围和排序功能
|
||||||
|
/// </summary>
|
||||||
public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest
|
public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,47 @@ using Volo.Abp.Application.Services;
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application.Contracts
|
namespace Yi.Framework.Ddd.Application.Contracts
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Yi框架CRUD服务基础接口
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
|
||||||
|
/// <typeparam name="TKey">主键类型</typeparam>
|
||||||
public interface IYiCrudAppService<TEntityDto, in TKey> : ICrudAppService<TEntityDto, TKey>
|
public interface IYiCrudAppService<TEntityDto, in TKey> : ICrudAppService<TEntityDto, TKey>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yi框架CRUD服务接口(带查询输入)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
|
||||||
|
/// <typeparam name="TKey">主键类型</typeparam>
|
||||||
|
/// <typeparam name="TGetListInput">查询输入类型</typeparam>
|
||||||
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput> : ICrudAppService<TEntityDto, TKey, TGetListInput>
|
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput> : ICrudAppService<TEntityDto, TKey, TGetListInput>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yi框架CRUD服务接口(带查询输入和创建输入)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
|
||||||
|
/// <typeparam name="TKey">主键类型</typeparam>
|
||||||
|
/// <typeparam name="TGetListInput">查询输入类型</typeparam>
|
||||||
|
/// <typeparam name="TCreateInput">创建输入类型</typeparam>
|
||||||
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
|
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yi框架CRUD服务接口(带查询、创建和更新输入)
|
||||||
|
/// </summary>
|
||||||
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yi框架完整CRUD服务接口(包含所有操作和批量删除功能)
|
||||||
|
/// </summary>
|
||||||
public interface IYiCrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>, IDeletesAppService<TKey>
|
public interface IYiCrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>, IDeletesAppService<TKey>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,48 +2,50 @@
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application.Contracts
|
namespace Yi.Framework.Ddd.Application.Contracts
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 分页查询请求DTO,包含时间范围和自定义排序功能
|
||||||
|
/// </summary>
|
||||||
public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto
|
public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询开始时间条件
|
/// 查询开始时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? StartTime { get; set; }
|
public DateTime? StartTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询结束时间条件
|
/// 查询结束时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? EndTime { get; set; }
|
public DateTime? EndTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 排序列名,字段名对应前端
|
/// 排序列名
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? OrderByColumn { get; set; }
|
public string? OrderByColumn { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否顺序,字段名对应前端
|
/// 排序方向(ascending/descending)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? IsAsc { get; set; }
|
public string? IsAsc { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否顺序
|
/// 是否为升序排序
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanAsc => IsAsc?.ToLower() == "ascending" ? true : false;
|
public bool IsAscending => string.Equals(IsAsc, "ascending", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private string _sorting;
|
private string? _sorting;
|
||||||
|
|
||||||
//排序引用
|
/// <summary>
|
||||||
public new string? Sorting
|
/// 排序表达式
|
||||||
|
/// </summary>
|
||||||
|
public override string? Sorting
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!OrderByColumn.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(OrderByColumn))
|
||||||
{
|
{
|
||||||
return $"{OrderByColumn} {(CanAsc ? "ASC" : "DESC")}";
|
return $"{OrderByColumn} {(IsAscending ? "ASC" : "DESC")}";
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return _sorting;
|
|
||||||
}
|
}
|
||||||
|
return _sorting;
|
||||||
}
|
}
|
||||||
set => _sorting = value;
|
set => _sorting = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ using Volo.Abp.Modularity;
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application.Contracts
|
namespace Yi.Framework.Ddd.Application.Contracts
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Yi框架DDD应用层契约模块
|
||||||
|
/// </summary>
|
||||||
[DependsOn(typeof(AbpDddApplicationContractsModule))]
|
[DependsOn(typeof(AbpDddApplicationContractsModule))]
|
||||||
public class YiFrameworkDddApplicationContractsModule : AbpModule
|
public class YiFrameworkDddApplicationContractsModule : AbpModule
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,11 +6,19 @@ using Volo.Abp.MultiTenancy;
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application
|
namespace Yi.Framework.Ddd.Application
|
||||||
{
|
{
|
||||||
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
/// <summary>
|
||||||
where TEntity : class, IEntity<TKey>
|
/// 带缓存的CRUD应用服务基类
|
||||||
where TEntityDto : IEntityDto<TKey>
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||||
|
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
|
||||||
|
/// <typeparam name="TKey">主键类型</typeparam>
|
||||||
|
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey>
|
||||||
|
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||||
|
where TEntity : class, IEntity<TKey>
|
||||||
|
where TEntityDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,73 +55,92 @@ namespace Yi.Framework.Ddd.Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完整的带缓存CRUD应用服务实现
|
||||||
|
/// </summary>
|
||||||
public abstract class YiCacheCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
public abstract class YiCacheCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
: YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
: YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
where TEntity : class, IEntity<TKey>
|
where TEntity : class, IEntity<TKey>
|
||||||
where TGetOutputDto : IEntityDto<TKey>
|
where TGetOutputDto : IEntityDto<TKey>
|
||||||
where TGetListOutputDto : IEntityDto<TKey>
|
where TGetListOutputDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected IDistributedCache<TEntity> Cache => LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntity>>();
|
/// <summary>
|
||||||
|
/// 分布式缓存访问器
|
||||||
|
/// </summary>
|
||||||
|
private IDistributedCache<TEntity> EntityCache =>
|
||||||
|
LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntity>>();
|
||||||
|
|
||||||
protected string GetCacheKey(TKey id) => typeof(TEntity).Name + ":" + CurrentTenant.Id ?? Guid.Empty + ":" + id.ToString();
|
/// <summary>
|
||||||
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
/// 获取缓存键
|
||||||
|
/// </summary>
|
||||||
|
protected virtual string GenerateCacheKey(TKey id) =>
|
||||||
|
$"{typeof(TEntity).Name}:{CurrentTenant.Id ?? Guid.Empty}:{id}";
|
||||||
|
|
||||||
|
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新实体并清除缓存
|
||||||
|
/// </summary>
|
||||||
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
||||||
{
|
{
|
||||||
var output = await base.UpdateAsync(id, input);
|
var result = await base.UpdateAsync(id, input);
|
||||||
await Cache.RemoveAsync(GetCacheKey(id));
|
await EntityCache.RemoveAsync(GenerateCacheKey(id));
|
||||||
return output;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
/// <summary>
|
||||||
|
/// 获取实体列表(需要继承实现具体的缓存策略)
|
||||||
|
/// </summary>
|
||||||
|
public override Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
||||||
{
|
{
|
||||||
//两种方式:
|
// 建议实现两种缓存策略:
|
||||||
//1:全表缓存,使用缓存直接查询
|
// 1. 全表缓存: 适用于数据量小且变动不频繁的场景
|
||||||
//2:非全部缓存,查询到的数据直接添加到缓存
|
// 2. 按需缓存: 仅缓存常用数据,适用于大数据量场景
|
||||||
|
throw new NotImplementedException("请实现具体的缓存查询策略");
|
||||||
//判断是否该实体为全表缓存
|
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
//IDistributedCache 有局限性,条件查询无法进行缓存了
|
|
||||||
//if (true)
|
|
||||||
//{
|
|
||||||
// return await GetListByCacheAsync(input);
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// return await GetListByDbAsync(input);
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async Task<PagedResultDto<TGetListOutputDto>> GetListByDbAsync(TGetListInput input)
|
/// <summary>
|
||||||
|
/// 从数据库获取实体列表
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromDatabaseAsync(
|
||||||
|
TGetListInput input)
|
||||||
{
|
{
|
||||||
//如果不是全表缓存,可以走这个啦
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
protected virtual async Task<PagedResultDto<TGetListOutputDto>> GetListByCacheAsync(TGetListInput input)
|
|
||||||
{
|
|
||||||
//如果是全表缓存,可以走这个啦
|
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从缓存获取实体列表
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromCacheAsync(
|
||||||
|
TGetListInput input)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单个实体(优先从缓存获取)
|
||||||
|
/// </summary>
|
||||||
protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
|
protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
|
||||||
{
|
{
|
||||||
var output = await Cache.GetOrAddAsync(GetCacheKey(id), async () => await base.GetEntityByIdAsync(id));
|
return (await EntityCache.GetOrAddAsync(
|
||||||
return output!;
|
GenerateCacheKey(id),
|
||||||
|
async () => await base.GetEntityByIdAsync(id)))!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task DeleteAsync(IEnumerable<TKey> id)
|
/// <summary>
|
||||||
|
/// 批量删除实体并清除缓存
|
||||||
|
/// </summary>
|
||||||
|
public override async Task DeleteAsync(IEnumerable<TKey> ids)
|
||||||
{
|
{
|
||||||
await base.DeleteAsync(id);
|
await base.DeleteAsync(ids);
|
||||||
foreach (var itemId in id)
|
|
||||||
{
|
|
||||||
await Cache.RemoveAsync(GetCacheKey(itemId));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 批量清除缓存
|
||||||
|
var tasks = ids.Select(id =>
|
||||||
|
EntityCache.RemoveAsync(GenerateCacheKey(id)));
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,183 +8,258 @@ using Volo.Abp.Domain.Repositories;
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application
|
namespace Yi.Framework.Ddd.Application
|
||||||
{
|
{
|
||||||
public abstract class
|
/// <summary>
|
||||||
YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey,
|
/// CRUD应用服务基类 - 基础版本
|
||||||
PagedAndSortedResultRequestDto>
|
/// </summary>
|
||||||
|
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey>
|
||||||
|
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||||
where TEntity : class, IEntity<TKey>
|
where TEntity : class, IEntity<TKey>
|
||||||
where TEntityDto : IEntityDto<TKey>
|
where TEntityDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRUD应用服务基类 - 支持自定义查询输入
|
||||||
|
/// </summary>
|
||||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
|
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
|
||||||
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
|
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
|
||||||
where TEntity : class, IEntity<TKey>
|
where TEntity : class, IEntity<TKey>
|
||||||
where TEntityDto : IEntityDto<TKey>
|
where TEntityDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRUD应用服务基类 - 支持自定义创建输入
|
||||||
|
/// </summary>
|
||||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
|
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
|
||||||
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
|
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
|
||||||
where TEntity : class, IEntity<TKey>
|
where TEntity : class, IEntity<TKey>
|
||||||
where TEntityDto : IEntityDto<TKey>
|
where TEntityDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRUD应用服务基类 - 支持自定义更新输入
|
||||||
|
/// </summary>
|
||||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
: YiCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
: YiCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
where TEntity : class, IEntity<TKey>
|
where TEntity : class, IEntity<TKey>
|
||||||
where TEntityDto : IEntityDto<TKey>
|
where TEntityDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput,
|
/// CRUD应用服务基类 - 完整实现
|
||||||
TUpdateInput>
|
/// </summary>
|
||||||
|
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||||
where TEntity : class, IEntity<TKey>
|
where TEntity : class, IEntity<TKey>
|
||||||
where TGetOutputDto : IEntityDto<TKey>
|
where TGetOutputDto : IEntityDto<TKey>
|
||||||
where TGetListOutputDto : IEntityDto<TKey>
|
where TGetListOutputDto : IEntityDto<TKey>
|
||||||
{
|
{
|
||||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
/// <summary>
|
||||||
|
/// 临时文件存储路径
|
||||||
|
/// </summary>
|
||||||
|
private const string TempFilePath = "/wwwroot/temp";
|
||||||
|
|
||||||
|
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||||
|
: base(repository)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新实体
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">实体ID</param>
|
||||||
|
/// <param name="input">更新输入</param>
|
||||||
|
/// <returns>更新后的实体DTO</returns>
|
||||||
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
||||||
{
|
{
|
||||||
|
// 检查更新权限
|
||||||
await CheckUpdatePolicyAsync();
|
await CheckUpdatePolicyAsync();
|
||||||
|
|
||||||
|
// 获取并验证实体
|
||||||
var entity = await GetEntityByIdAsync(id);
|
var entity = await GetEntityByIdAsync(id);
|
||||||
await CheckUpdateInputDtoAsync(entity,input);
|
|
||||||
|
|
||||||
|
// 检查更新输入
|
||||||
|
await CheckUpdateInputDtoAsync(entity, input);
|
||||||
|
|
||||||
|
// 映射并更新实体
|
||||||
await MapToEntityAsync(input, entity);
|
await MapToEntityAsync(input, entity);
|
||||||
await Repository.UpdateAsync(entity, autoSave: true);
|
await Repository.UpdateAsync(entity, autoSave: true);
|
||||||
|
|
||||||
return await MapToGetOutputDtoAsync(entity);
|
return await MapToGetOutputDtoAsync(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity,TUpdateInput input)
|
/// <summary>
|
||||||
|
/// 检查更新输入数据的有效性
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity, TUpdateInput input)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建实体
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">创建输入</param>
|
||||||
|
/// <returns>创建后的实体DTO</returns>
|
||||||
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
|
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
|
||||||
{
|
{
|
||||||
|
// 检查创建权限
|
||||||
await CheckCreatePolicyAsync();
|
await CheckCreatePolicyAsync();
|
||||||
|
|
||||||
|
// 检查创建输入
|
||||||
await CheckCreateInputDtoAsync(input);
|
await CheckCreateInputDtoAsync(input);
|
||||||
|
|
||||||
|
// 映射到实体
|
||||||
var entity = await MapToEntityAsync(input);
|
var entity = await MapToEntityAsync(input);
|
||||||
|
|
||||||
|
// 设置租户ID
|
||||||
TryToSetTenantId(entity);
|
TryToSetTenantId(entity);
|
||||||
|
|
||||||
|
// 插入实体
|
||||||
await Repository.InsertAsync(entity, autoSave: true);
|
await Repository.InsertAsync(entity, autoSave: true);
|
||||||
|
|
||||||
return await MapToGetOutputDtoAsync(entity);
|
return await MapToGetOutputDtoAsync(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查创建输入数据的有效性
|
||||||
|
/// </summary>
|
||||||
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
|
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 多查
|
/// 获取实体列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input">查询输入</param>
|
||||||
/// <returns></returns>
|
/// <returns>分页结果</returns>
|
||||||
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
||||||
{
|
{
|
||||||
List<TEntity>? entites = null;
|
List<TEntity> entities;
|
||||||
//区分多查还是批量查
|
|
||||||
|
// 根据输入类型决定查询方式
|
||||||
if (input is IPagedResultRequest pagedInput)
|
if (input is IPagedResultRequest pagedInput)
|
||||||
{
|
{
|
||||||
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount,
|
// 分页查询
|
||||||
string.Empty);
|
entities = await Repository.GetPagedListAsync(
|
||||||
|
pagedInput.SkipCount,
|
||||||
|
pagedInput.MaxResultCount,
|
||||||
|
string.Empty
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entites = await Repository.GetListAsync();
|
// 查询全部
|
||||||
|
entities = await Repository.GetListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var total = await Repository.GetCountAsync();
|
// 获取总数并映射结果
|
||||||
var output = await MapToGetListOutputDtosAsync(entites);
|
var totalCount = await Repository.GetCountAsync();
|
||||||
return new PagedResultDto<TGetListOutputDto>(total, output);
|
var dtos = await MapToGetListOutputDtosAsync(entities);
|
||||||
//throw new NotImplementedException($"【{typeof(TEntity)}】实体的CrudAppService,查询为具体业务,通用查询几乎无实际场景,请重写实现!");
|
|
||||||
|
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 多删
|
/// 获取实体动态下拉框列表,子类重写该方法,通过 keywords 进行筛选
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="keywords">查询关键字</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[RemoteService(isEnabled: true)]
|
public virtual async Task<List<TGetListOutputDto>> GetSelectDataListAsync(string? keywords = null)
|
||||||
public virtual async Task DeleteAsync(IEnumerable<TKey> id)
|
|
||||||
{
|
{
|
||||||
await Repository.DeleteManyAsync(id);
|
List<TEntity> entities = await Repository.GetListAsync();
|
||||||
|
// 获取总数并映射结果
|
||||||
|
var dtos = await MapToGetListOutputDtosAsync(entities);
|
||||||
|
|
||||||
|
return dtos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 偷梁换柱
|
/// 批量删除实体
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ids">实体ID集合</param>
|
||||||
|
[RemoteService(isEnabled: true)]
|
||||||
|
public virtual async Task DeleteAsync(IEnumerable<TKey> ids)
|
||||||
|
{
|
||||||
|
await Repository.DeleteManyAsync(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单个删除实体(禁用远程访问)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[RemoteService(isEnabled: false)]
|
[RemoteService(isEnabled: false)]
|
||||||
public override Task DeleteAsync(TKey id)
|
public override Task DeleteAsync(TKey id)
|
||||||
{
|
{
|
||||||
return base.DeleteAsync(id);
|
return base.DeleteAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导出excel
|
/// 导出Excel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input">查询条件</param>
|
||||||
/// <returns></returns>
|
/// <returns>Excel文件</returns>
|
||||||
public virtual async Task<IActionResult> GetExportExcelAsync(TGetListInput input)
|
public virtual async Task<IActionResult> GetExportExcelAsync(TGetListInput input)
|
||||||
{
|
{
|
||||||
|
// 重置分页参数以获取全部数据
|
||||||
if (input is IPagedResultRequest paged)
|
if (input is IPagedResultRequest paged)
|
||||||
{
|
{
|
||||||
paged.SkipCount = 0;
|
paged.SkipCount = 0;
|
||||||
paged.MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount;
|
paged.MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
var output = await this.GetListAsync(input);
|
// 获取数据
|
||||||
var dirPath = $"/wwwroot/temp";
|
var output = await GetListAsync(input);
|
||||||
|
|
||||||
var fileName = $"{typeof(TEntity).Name}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}_{Guid.NewGuid()}";
|
// 确保临时目录存在
|
||||||
var filePath = $"{dirPath}/{fileName}.xlsx";
|
if (!Directory.Exists(TempFilePath))
|
||||||
if (!Directory.Exists(dirPath))
|
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(dirPath);
|
Directory.CreateDirectory(TempFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
MiniExcel.SaveAs(filePath, output.Items);
|
// 生成文件名和路径
|
||||||
|
var fileName = GenerateExcelFileName();
|
||||||
|
var filePath = Path.Combine(TempFilePath, fileName);
|
||||||
|
|
||||||
|
// 保存Excel文件
|
||||||
|
await MiniExcel.SaveAsAsync(filePath, output.Items);
|
||||||
|
|
||||||
return new PhysicalFileResult(filePath, "application/vnd.ms-excel");
|
return new PhysicalFileResult(filePath, "application/vnd.ms-excel");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导入excle
|
/// 生成Excel文件名
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
private string GenerateExcelFileName()
|
||||||
/// <returns></returns>
|
|
||||||
public virtual async Task PostImportExcelAsync(List<TCreateInput> input)
|
|
||||||
{
|
{
|
||||||
var entities = input.Select(x => MapToEntity(x)).ToList();
|
return $"{typeof(TEntity).Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Guid.NewGuid()}.xlsx";
|
||||||
//安全起见,该接口需要自己实现
|
}
|
||||||
throw new NotImplementedException();
|
|
||||||
//await Repository.DeleteManyAsync(entities.Select(x => x.Id));
|
/// <summary>
|
||||||
//await Repository.InsertManyAsync(entities);
|
/// 导入Excel(需要实现类重写此方法)
|
||||||
|
/// </summary>
|
||||||
|
public virtual Task PostImportExcelAsync(List<TCreateInput> input)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("请在实现类中重写此方法以支持Excel导入");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,14 +6,34 @@ using Yi.Framework.Ddd.Application.Contracts;
|
|||||||
|
|
||||||
namespace Yi.Framework.Ddd.Application
|
namespace Yi.Framework.Ddd.Application
|
||||||
{
|
{
|
||||||
[DependsOn(typeof(AbpDddApplicationModule),
|
/// <summary>
|
||||||
typeof(YiFrameworkDddApplicationContractsModule))]
|
/// Yi框架DDD应用层模块
|
||||||
|
/// </summary>
|
||||||
|
[DependsOn(
|
||||||
|
typeof(AbpDddApplicationModule),
|
||||||
|
typeof(YiFrameworkDddApplicationContractsModule)
|
||||||
|
)]
|
||||||
public class YiFrameworkDddApplicationModule : AbpModule
|
public class YiFrameworkDddApplicationModule : AbpModule
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 应用程序初始化配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">应用程序初始化上下文</param>
|
||||||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||||
{
|
{
|
||||||
//分页限制
|
// 配置分页查询的默认值和最大值限制
|
||||||
|
ConfigureDefaultPagingSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置默认分页设置
|
||||||
|
/// </summary>
|
||||||
|
private void ConfigureDefaultPagingSettings()
|
||||||
|
{
|
||||||
|
// 设置默认每页显示记录数
|
||||||
LimitedResultRequestDto.DefaultMaxResultCount = 10;
|
LimitedResultRequestDto.DefaultMaxResultCount = 10;
|
||||||
|
|
||||||
|
// 设置最大允许的每页记录数
|
||||||
LimitedResultRequestDto.MaxMaxResultCount = 10000;
|
LimitedResultRequestDto.MaxMaxResultCount = 10000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,37 @@ using Volo.Abp.ObjectMapping;
|
|||||||
|
|
||||||
namespace Yi.Framework.Mapster
|
namespace Yi.Framework.Mapster
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mapster自动对象映射提供程序
|
||||||
|
/// 实现IAutoObjectMappingProvider接口,提供对象间的自动映射功能
|
||||||
|
/// </summary>
|
||||||
public class MapsterAutoObjectMappingProvider : IAutoObjectMappingProvider
|
public class MapsterAutoObjectMappingProvider : IAutoObjectMappingProvider
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将源对象映射到目标类型
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSource">源类型</typeparam>
|
||||||
|
/// <typeparam name="TDestination">目标类型</typeparam>
|
||||||
|
/// <param name="source">源对象</param>
|
||||||
|
/// <returns>映射后的目标类型实例</returns>
|
||||||
public TDestination Map<TSource, TDestination>(object source)
|
public TDestination Map<TSource, TDestination>(object source)
|
||||||
{
|
{
|
||||||
var sss = typeof(TDestination).Name;
|
// 使用Mapster的Adapt方法进行对象映射
|
||||||
return source.Adapt<TDestination>();
|
return source.Adapt<TDestination>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将源对象映射到现有的目标对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSource">源类型</typeparam>
|
||||||
|
/// <typeparam name="TDestination">目标类型</typeparam>
|
||||||
|
/// <param name="source">源对象</param>
|
||||||
|
/// <param name="destination">目标对象</param>
|
||||||
|
/// <returns>映射后的目标对象</returns>
|
||||||
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
|
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
|
||||||
{
|
{
|
||||||
return source.Adapt<TSource, TDestination>(destination);
|
// 使用Mapster的Adapt方法进行对象映射,保留目标对象的实例
|
||||||
|
return source.Adapt(destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,51 @@ using Volo.Abp.ObjectMapping;
|
|||||||
|
|
||||||
namespace Yi.Framework.Mapster
|
namespace Yi.Framework.Mapster
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mapster对象映射器
|
||||||
|
/// 实现IObjectMapper接口,提供对象映射功能
|
||||||
|
/// </summary>
|
||||||
public class MapsterObjectMapper : IObjectMapper
|
public class MapsterObjectMapper : IObjectMapper
|
||||||
{
|
{
|
||||||
public IAutoObjectMappingProvider AutoObjectMappingProvider => throw new NotImplementedException();
|
private readonly IAutoObjectMappingProvider _autoObjectMappingProvider;
|
||||||
|
|
||||||
public TDestination Map<TSource, TDestination>(TSource source)
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="autoObjectMappingProvider">自动对象映射提供程序</param>
|
||||||
|
public MapsterObjectMapper(IAutoObjectMappingProvider autoObjectMappingProvider)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_autoObjectMappingProvider = autoObjectMappingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取自动对象映射提供程序
|
||||||
|
/// </summary>
|
||||||
|
public IAutoObjectMappingProvider AutoObjectMappingProvider => _autoObjectMappingProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将源对象映射到目标类型
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSource">源类型</typeparam>
|
||||||
|
/// <typeparam name="TDestination">目标类型</typeparam>
|
||||||
|
/// <param name="source">源对象</param>
|
||||||
|
/// <returns>映射后的目标类型实例</returns>
|
||||||
|
public TDestination Map<TSource, TDestination>(TSource source)
|
||||||
|
{
|
||||||
|
return AutoObjectMappingProvider.Map<TSource, TDestination>(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将源对象映射到现有的目标对象
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSource">源类型</typeparam>
|
||||||
|
/// <typeparam name="TDestination">目标类型</typeparam>
|
||||||
|
/// <param name="source">源对象</param>
|
||||||
|
/// <param name="destination">目标对象</param>
|
||||||
|
/// <returns>映射后的目标对象</returns>
|
||||||
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
|
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return AutoObjectMappingProvider.Map(source, destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,33 @@
|
|||||||
using MapsterMapper;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
using Volo.Abp.ObjectMapping;
|
using Volo.Abp.ObjectMapping;
|
||||||
using Yi.Framework.Core;
|
using Yi.Framework.Core;
|
||||||
|
using Mapster;
|
||||||
|
|
||||||
namespace Yi.Framework.Mapster
|
namespace Yi.Framework.Mapster
|
||||||
{
|
{
|
||||||
[DependsOn(typeof(YiFrameworkCoreModule),
|
/// <summary>
|
||||||
|
/// Yi框架Mapster模块
|
||||||
|
/// 用于配置和注册Mapster相关服务
|
||||||
|
/// </summary>
|
||||||
|
[DependsOn(
|
||||||
|
typeof(YiFrameworkCoreModule),
|
||||||
typeof(AbpObjectMappingModule)
|
typeof(AbpObjectMappingModule)
|
||||||
)]
|
)]
|
||||||
public class YiFrameworkMapsterModule : AbpModule
|
public class YiFrameworkMapsterModule : AbpModule
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 配置服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">服务配置上下文</param>
|
||||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
{
|
{
|
||||||
context.Services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
|
var services = context.Services;
|
||||||
|
// 扫描并注册所有映射配置
|
||||||
|
TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
|
||||||
|
// 注册Mapster相关服务
|
||||||
|
services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
|
||||||
|
services.AddTransient<IObjectMapper, MapsterObjectMapper>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
|
using ArgumentException = System.ArgumentException;
|
||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库连接配置选项
|
||||||
|
/// </summary>
|
||||||
public class DbConnOptions
|
public class DbConnOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接字符串(如果开启多租户,也就是默认库了),必填
|
/// 主数据库连接字符串
|
||||||
|
/// 如果开启多租户,此为默认租户数据库
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Url { get; set; }
|
public string? Url { get; set; }
|
||||||
|
|
||||||
@@ -15,44 +20,48 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
|||||||
public DbType? DbType { get; set; }
|
public DbType? DbType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启种子数据
|
/// 是否启用种子数据初始化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledDbSeed { get; set; } = false;
|
public bool EnabledDbSeed { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启驼峰转下划线
|
/// 是否启用驼峰命名转下划线命名
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableUnderLine { get; set; } = false;
|
public bool EnableUnderLine { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启codefirst
|
/// 是否启用Code First模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledCodeFirst { get; set; } = false;
|
public bool EnabledCodeFirst { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启sql日志
|
/// 是否启用SQL日志记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledSqlLog { get; set; } = true;
|
public bool EnabledSqlLog { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实体程序集
|
/// 实体类所在程序集名称列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string>? EntityAssembly { get; set; }
|
public List<string>? EntityAssembly { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启读写分离
|
/// 是否启用读写分离
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledReadWrite { get; set; } = false;
|
public bool EnabledReadWrite { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读写分离
|
/// 只读数据库连接字符串列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string>? ReadUrl { get; set; }
|
public List<string>? ReadUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启Saas多租户
|
/// 是否启用SaaS多租户
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否开启更新并发乐观锁
|
||||||
|
/// </summary>
|
||||||
|
public bool EnabledConcurrencyException { get;set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,13 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认租户表特性
|
||||||
|
/// 标记此特性的实体类将在默认租户数据库中创建表
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class DefaultTenantTableAttribute : Attribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class DefaultTenantTableAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,16 +8,19 @@ using Volo.Abp.DependencyInjection;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库上下文接口
|
||||||
|
/// </summary>
|
||||||
public interface ISqlSugarDbContext
|
public interface ISqlSugarDbContext
|
||||||
{
|
{
|
||||||
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
/// <summary>
|
||||||
|
/// 获取SqlSugar客户端实例
|
||||||
|
/// </summary>
|
||||||
ISqlSugarClient SqlSugarClient { get; }
|
ISqlSugarClient SqlSugarClient { get; }
|
||||||
DbConnOptions Options { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据库备份
|
/// 执行数据库备份
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void BackupDataBase();
|
void BackupDataBase();
|
||||||
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库上下文依赖接口
|
||||||
|
/// 定义数据库操作的各个生命周期钩子
|
||||||
|
/// </summary>
|
||||||
|
public interface ISqlSugarDbContextDependencies
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取执行顺序
|
||||||
|
/// </summary>
|
||||||
|
int ExecutionOrder { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar客户端配置时触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sqlSugarClient">SqlSugar客户端实例</param>
|
||||||
|
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据执行后触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldValue">原始值</param>
|
||||||
|
/// <param name="entityInfo">实体信息</param>
|
||||||
|
void DataExecuted(object oldValue, DataAfterModel entityInfo);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据执行前触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldValue">原始值</param>
|
||||||
|
/// <param name="entityInfo">实体信息</param>
|
||||||
|
void DataExecuting(object oldValue, DataFilterModel entityInfo);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SQL执行前触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sql">SQL语句</param>
|
||||||
|
/// <param name="parameters">SQL参数</param>
|
||||||
|
void OnLogExecuting(string sql, SugarParameter[] parameters);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SQL执行后触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sql">SQL语句</param>
|
||||||
|
/// <param name="parameters">SQL参数</param>
|
||||||
|
void OnLogExecuted(string sql, SugarParameter[] parameters);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体服务配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyInfo">属性信息</param>
|
||||||
|
/// <param name="entityColumnInfo">实体列信息</param>
|
||||||
|
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
|
||||||
|
}
|
||||||
@@ -2,87 +2,246 @@
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Uow;
|
||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity> where TEntity : class, IEntity,new ()
|
/// SqlSugar仓储接口
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||||
|
public interface ISqlSugarRepository<TEntity> : IRepository<TEntity>, IUnitOfWorkEnabled
|
||||||
|
where TEntity : class, IEntity, new()
|
||||||
{
|
{
|
||||||
|
#region 数据库访问器
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取SqlSugar客户端实例
|
||||||
|
/// </summary>
|
||||||
ISqlSugarClient _Db { get; }
|
ISqlSugarClient _Db { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取查询构造器
|
||||||
|
/// </summary>
|
||||||
ISugarQueryable<TEntity> _DbQueryable { get; }
|
ISugarQueryable<TEntity> _DbQueryable { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步获取数据库上下文
|
||||||
|
/// </summary>
|
||||||
Task<ISqlSugarClient> GetDbContextAsync();
|
Task<ISqlSugarClient> GetDbContextAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取删除操作构造器
|
||||||
|
/// </summary>
|
||||||
Task<IDeleteable<TEntity>> AsDeleteable();
|
Task<IDeleteable<TEntity>> AsDeleteable();
|
||||||
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> insertObjs);
|
|
||||||
Task<IInsertable<TEntity>> AsInsertable(TEntity insertObj);
|
/// <summary>
|
||||||
Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs);
|
/// 获取插入操作构造器
|
||||||
|
/// </summary>
|
||||||
|
Task<IInsertable<TEntity>> AsInsertable(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取批量插入操作构造器
|
||||||
|
/// </summary>
|
||||||
|
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> entities);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取查询构造器
|
||||||
|
/// </summary>
|
||||||
Task<ISugarQueryable<TEntity>> AsQueryable();
|
Task<ISugarQueryable<TEntity>> AsQueryable();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取SqlSugar客户端
|
||||||
|
/// </summary>
|
||||||
Task<ISqlSugarClient> AsSugarClient();
|
Task<ISqlSugarClient> AsSugarClient();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取租户操作接口
|
||||||
|
/// </summary>
|
||||||
Task<ITenant> AsTenant();
|
Task<ITenant> AsTenant();
|
||||||
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> updateObjs);
|
|
||||||
Task<IUpdateable<TEntity>> AsUpdateable(TEntity updateObj);
|
/// <summary>
|
||||||
|
/// 获取更新操作构造器
|
||||||
|
/// </summary>
|
||||||
Task<IUpdateable<TEntity>> AsUpdateable();
|
Task<IUpdateable<TEntity>> AsUpdateable();
|
||||||
Task<IUpdateable<TEntity>> AsUpdateable(TEntity[] updateObjs);
|
|
||||||
|
|
||||||
#region 单查
|
/// <summary>
|
||||||
//单查
|
/// 获取实体更新操作构造器
|
||||||
|
/// </summary>
|
||||||
|
Task<IUpdateable<TEntity>> AsUpdateable(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取批量更新操作构造器
|
||||||
|
/// </summary>
|
||||||
|
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> entities);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 查询操作
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据主键获取实体
|
||||||
|
/// </summary>
|
||||||
Task<TEntity> GetByIdAsync(dynamic id);
|
Task<TEntity> GetByIdAsync(dynamic id);
|
||||||
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression);
|
|
||||||
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression);
|
|
||||||
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> whereExpression);
|
|
||||||
Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression);
|
|
||||||
|
|
||||||
#endregion
|
/// <summary>
|
||||||
|
/// 获取满足条件的单个实体
|
||||||
|
/// </summary>
|
||||||
|
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取满足条件的第一个实体
|
||||||
|
/// </summary>
|
||||||
|
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
#region 多查
|
/// <summary>
|
||||||
//多查
|
/// 判断是否存在满足条件的实体
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取满足条件的实体数量
|
||||||
|
/// </summary>
|
||||||
|
Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有实体
|
||||||
|
/// </summary>
|
||||||
Task<List<TEntity>> GetListAsync();
|
Task<List<TEntity>> GetListAsync();
|
||||||
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> whereExpression);
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取满足条件的所有实体
|
||||||
|
/// </summary>
|
||||||
|
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 分页查询
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取分页数据
|
||||||
|
/// </summary>
|
||||||
|
Task<List<TEntity>> GetPageListAsync(
|
||||||
|
Expression<Func<TEntity, bool>> predicate,
|
||||||
|
int pageIndex,
|
||||||
|
int pageSize);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取排序的分页数据
|
||||||
|
/// </summary>
|
||||||
|
Task<List<TEntity>> GetPageListAsync(
|
||||||
|
Expression<Func<TEntity, bool>> predicate,
|
||||||
|
int pageIndex,
|
||||||
|
int pageSize,
|
||||||
|
Expression<Func<TEntity, object>>? orderByExpression = null,
|
||||||
|
OrderByType orderByType = OrderByType.Asc);
|
||||||
|
|
||||||
#region 分页查
|
|
||||||
//分页查
|
|
||||||
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize);
|
|
||||||
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 插入
|
#region 插入操作
|
||||||
//插入
|
|
||||||
Task<bool> InsertAsync(TEntity insertObj);
|
/// <summary>
|
||||||
Task<bool> InsertOrUpdateAsync(TEntity data);
|
/// 插入实体
|
||||||
Task<bool> InsertOrUpdateAsync(List<TEntity> datas);
|
/// </summary>
|
||||||
Task<int> InsertReturnIdentityAsync(TEntity insertObj);
|
Task<bool> InsertAsync(TEntity entity);
|
||||||
Task<long> InsertReturnBigIdentityAsync(TEntity insertObj);
|
|
||||||
Task<long> InsertReturnSnowflakeIdAsync(TEntity insertObj);
|
/// <summary>
|
||||||
Task<TEntity> InsertReturnEntityAsync(TEntity insertObj);
|
/// 插入或更新实体
|
||||||
Task<bool> InsertRangeAsync(List<TEntity> insertObjs);
|
/// </summary>
|
||||||
|
Task<bool> InsertOrUpdateAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量插入或更新实体
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> InsertOrUpdateAsync(List<TEntity> entities);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 插入实体并返回自增主键
|
||||||
|
/// </summary>
|
||||||
|
Task<int> InsertReturnIdentityAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 插入实体并返回长整型自增主键
|
||||||
|
/// </summary>
|
||||||
|
Task<long> InsertReturnBigIdentityAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 插入实体并返回雪花ID
|
||||||
|
/// </summary>
|
||||||
|
Task<long> InsertReturnSnowflakeIdAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 插入实体并返回实体
|
||||||
|
/// </summary>
|
||||||
|
Task<TEntity> InsertReturnEntityAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量插入实体
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> InsertRangeAsync(List<TEntity> entities);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 更新操作
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新实体
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> UpdateAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量更新实体
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> UpdateRangeAsync(List<TEntity> entities);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 条件更新指定列
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> UpdateAsync(
|
||||||
|
Expression<Func<TEntity, TEntity>> columns,
|
||||||
|
Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
#region 更新
|
|
||||||
//更新
|
|
||||||
Task<bool> UpdateAsync(TEntity updateObj);
|
|
||||||
Task<bool> UpdateRangeAsync(List<TEntity> updateObjs);
|
|
||||||
Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression);
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 删除
|
#region 删除操作
|
||||||
//删除
|
|
||||||
Task<bool> DeleteAsync(TEntity deleteObj);
|
/// <summary>
|
||||||
Task<bool> DeleteAsync(List<TEntity> deleteObjs);
|
/// 删除实体
|
||||||
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> whereExpression);
|
/// </summary>
|
||||||
|
Task<bool> DeleteAsync(TEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量删除实体
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> DeleteAsync(List<TEntity> entities);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 条件删除
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> predicate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据主键删除
|
||||||
|
/// </summary>
|
||||||
Task<bool> DeleteByIdAsync(dynamic id);
|
Task<bool> DeleteByIdAsync(dynamic id);
|
||||||
Task<bool> DeleteByIdsAsync(dynamic[] ids);
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据主键批量删除
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> DeleteByIdsAsync(dynamic[] ids);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public interface ISqlSugarRepository<TEntity, TKey> : ISqlSugarRepository<TEntity>,IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
|
/// SqlSugar仓储接口(带主键)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||||
|
/// <typeparam name="TKey">主键类型</typeparam>
|
||||||
|
public interface ISqlSugarRepository<TEntity, TKey> :
|
||||||
|
ISqlSugarRepository<TEntity>,
|
||||||
|
IRepository<TEntity, TKey>
|
||||||
|
where TEntity : class, IEntity<TKey>, new()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,17 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库上下文提供者接口
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
|
||||||
public interface ISugarDbContextProvider<TDbContext>
|
public interface ISugarDbContextProvider<TDbContext>
|
||||||
where TDbContext : ISqlSugarDbContext
|
where TDbContext : ISqlSugarDbContext
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 异步获取数据库上下文实例
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>数据库上下文实例</returns>
|
||||||
Task<TDbContext> GetDbContextAsync();
|
Task<TDbContext> GetDbContextAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 忽略CodeFirst特性
|
||||||
|
/// 标记此特性的实体类将不会被CodeFirst功能扫描
|
||||||
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class IgnoreCodeFirstAttribute : Attribute
|
public class IgnoreCodeFirstAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SqlSugarCoreNoDrive" Version="$(SqlSugarVersion)" />
|
<!-- <PackageReference Include="SqlSugarCoreNoDrive" Version="$(SqlSugarVersion)" />-->
|
||||||
|
|
||||||
|
<PackageReference Include="SqlSugarCore" Version="$(SqlSugarVersion)" />
|
||||||
|
|
||||||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
|
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ using Yi.Framework.Core;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar Core抽象层模块
|
||||||
|
/// 提供SqlSugar ORM的基础抽象接口和类型定义
|
||||||
|
/// </summary>
|
||||||
[DependsOn(typeof(YiFrameworkCoreModule))]
|
[DependsOn(typeof(YiFrameworkCoreModule))]
|
||||||
public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule
|
public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule
|
||||||
{
|
{
|
||||||
|
// 模块配置方法可在此添加
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,34 @@
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore
|
namespace Yi.Framework.SqlSugarCore
|
||||||
{
|
{
|
||||||
public class AsyncLocalDbContextAccessor
|
/// <summary>
|
||||||
|
/// 异步本地数据库上下文访问器
|
||||||
|
/// 用于在异步流中保存和访问数据库上下文
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AsyncLocalDbContextAccessor
|
||||||
{
|
{
|
||||||
|
private readonly AsyncLocal<ISqlSugarDbContext?> _currentScope;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取单例实例
|
||||||
|
/// </summary>
|
||||||
public static AsyncLocalDbContextAccessor Instance { get; } = new();
|
public static AsyncLocalDbContextAccessor Instance { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置当前数据库上下文
|
||||||
|
/// </summary>
|
||||||
public ISqlSugarDbContext? Current
|
public ISqlSugarDbContext? Current
|
||||||
{
|
{
|
||||||
get => _currentScope.Value;
|
get => _currentScope.Value;
|
||||||
set => _currentScope.Value = value;
|
set => _currentScope.Value = value;
|
||||||
}
|
}
|
||||||
public AsyncLocalDbContextAccessor()
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化异步本地数据库上下文访问器
|
||||||
|
/// </summary>
|
||||||
|
private AsyncLocalDbContextAccessor()
|
||||||
{
|
{
|
||||||
_currentScope = new AsyncLocal<ISqlSugarDbContext?>();
|
_currentScope = new AsyncLocal<ISqlSugarDbContext?>();
|
||||||
}
|
}
|
||||||
private readonly AsyncLocal<ISqlSugarDbContext> _currentScope;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认SqlSugar数据库上下文实现
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultSqlSugarDbContext : SqlSugarDbContext
|
||||||
|
{
|
||||||
|
#region Protected Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库连接配置选项
|
||||||
|
/// </summary>
|
||||||
|
protected DbConnOptions DbOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前用户服务
|
||||||
|
/// </summary>
|
||||||
|
protected ICurrentUser CurrentUserService => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GUID生成器
|
||||||
|
/// </summary>
|
||||||
|
protected IGuidGenerator GuidGeneratorService => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志工厂
|
||||||
|
/// </summary>
|
||||||
|
protected ILoggerFactory LoggerFactory => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前租户服务
|
||||||
|
/// </summary>
|
||||||
|
protected ICurrentTenant CurrentTenantService => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据过滤服务
|
||||||
|
/// </summary>
|
||||||
|
protected IDataFilter DataFilterService => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工作单元管理器
|
||||||
|
/// </summary>
|
||||||
|
protected IUnitOfWorkManager UnitOfWorkManagerService => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实体变更事件帮助类
|
||||||
|
/// </summary>
|
||||||
|
protected IEntityChangeEventHelper EntityChangeEventHelperService =>
|
||||||
|
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用多租户过滤
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool IsMultiTenantFilterEnabled => DataFilterService?.IsEnabled<IMultiTenant>() ?? false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用软删除过滤
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool IsSoftDeleteFilterEnabled => DataFilterService?.IsEnabled<ISoftDelete>() ?? false;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||||
|
: base(lazyServiceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义数据过滤器
|
||||||
|
/// </summary>
|
||||||
|
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||||
|
{
|
||||||
|
// 配置软删除过滤器
|
||||||
|
if (IsSoftDeleteFilterEnabled)
|
||||||
|
{
|
||||||
|
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(entity => !entity.IsDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置多租户过滤器
|
||||||
|
if (IsMultiTenantFilterEnabled)
|
||||||
|
{
|
||||||
|
var currentTenantId = CurrentTenantService.Id;
|
||||||
|
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(entity => entity.TenantId == currentTenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据执行前的处理
|
||||||
|
/// </summary>
|
||||||
|
public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
HandleAuditFields(oldValue, entityInfo);
|
||||||
|
HandleEntityEvents(entityInfo);
|
||||||
|
HandleDomainEvents(entityInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理审计字段
|
||||||
|
/// </summary>
|
||||||
|
private void HandleAuditFields(object oldValue, DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
switch (entityInfo.OperationType)
|
||||||
|
{
|
||||||
|
case DataFilterType.UpdateByObject:
|
||||||
|
HandleUpdateAuditFields(oldValue, entityInfo);
|
||||||
|
break;
|
||||||
|
case DataFilterType.InsertByObject:
|
||||||
|
HandleInsertAuditFields(oldValue, entityInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理更新时的审计字段
|
||||||
|
/// </summary>
|
||||||
|
private void HandleUpdateAuditFields(object oldValue, DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
|
||||||
|
{
|
||||||
|
entityInfo.SetValue(DateTime.MinValue.Equals(oldValue) ? null : DateTime.Now);
|
||||||
|
}
|
||||||
|
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId))
|
||||||
|
&& entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(Guid?))
|
||||||
|
{
|
||||||
|
entityInfo.SetValue(Guid.Empty.Equals(oldValue) ? null : CurrentUserService.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理插入时的审计字段
|
||||||
|
/// </summary>
|
||||||
|
private void HandleInsertAuditFields(object oldValue, DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
|
||||||
|
{
|
||||||
|
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||||
|
{
|
||||||
|
if (Guid.Empty.Equals(oldValue))
|
||||||
|
{
|
||||||
|
entityInfo.SetValue(GuidGeneratorService.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)))
|
||||||
|
{
|
||||||
|
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
|
||||||
|
{
|
||||||
|
if (CurrentUserService.Id is not null)
|
||||||
|
{
|
||||||
|
entityInfo.SetValue(CurrentUserService.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||||
|
{
|
||||||
|
if (CurrentTenantService.Id is not null)
|
||||||
|
{
|
||||||
|
entityInfo.SetValue(CurrentTenantService.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理实体变更事件
|
||||||
|
/// </summary>
|
||||||
|
private void HandleEntityEvents(DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
// 实体变更领域事件
|
||||||
|
switch (entityInfo.OperationType)
|
||||||
|
{
|
||||||
|
case DataFilterType.InsertByObject:
|
||||||
|
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||||
|
{
|
||||||
|
EntityChangeEventHelperService.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DataFilterType.UpdateByObject:
|
||||||
|
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||||
|
{
|
||||||
|
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
||||||
|
{
|
||||||
|
if (softDelete.IsDeleted == true)
|
||||||
|
{
|
||||||
|
EntityChangeEventHelperService.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DataFilterType.DeleteByObject:
|
||||||
|
if (entityInfo.EntityValue is IEnumerable entityValues)
|
||||||
|
{
|
||||||
|
foreach (var entityValue in entityValues)
|
||||||
|
{
|
||||||
|
EntityChangeEventHelperService.PublishEntityDeletedEvent(entityValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理领域事件
|
||||||
|
/// </summary>
|
||||||
|
private void HandleDomainEvents(DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
// 实体领域事件-所有操作类型
|
||||||
|
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||||
|
{
|
||||||
|
var eventReport = CreateEventReport(entityInfo.EntityValue);
|
||||||
|
PublishEntityEvents(eventReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
UnitOfWorkManagerService.Current?.AddOrReplaceLocalEvent(
|
||||||
|
new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var distributedEvent in changeReport.DistributedEvents)
|
||||||
|
{
|
||||||
|
UnitOfWorkManagerService.Current?.AddOrReplaceDistributedEvent(
|
||||||
|
new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||||
|
{
|
||||||
|
if (DbOptions.EnabledSqlLog)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("==========Yi-SQL执行:==========");
|
||||||
|
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
|
||||||
|
sb.AppendLine("===============================");
|
||||||
|
LoggerFactory.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||||
|
{
|
||||||
|
if (DbOptions.EnabledSqlLog)
|
||||||
|
{
|
||||||
|
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
|
||||||
|
LoggerFactory.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||||
|
{
|
||||||
|
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
|
||||||
|
{
|
||||||
|
entityColumnInfo.IsEnableUpdateVersionValidation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
|
||||||
|
{
|
||||||
|
entityColumnInfo.IsIgnore = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyInfo.Name == nameof(Entity<object>.Id))
|
||||||
|
{
|
||||||
|
entityColumnInfo.IsPrimarykey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
using System.Linq;
|
using System.Linq.Expressions;
|
||||||
using System.Linq.Expressions;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Text;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Nito.AsyncEx;
|
using Nito.AsyncEx;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp;
|
|
||||||
using Volo.Abp.Auditing;
|
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Linq;
|
using Volo.Abp.Linq;
|
||||||
@@ -17,38 +14,48 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
{
|
{
|
||||||
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
|
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
|
||||||
{
|
{
|
||||||
|
[Obsolete("使用GetDbContextAsync()")]
|
||||||
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
|
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
|
||||||
|
|
||||||
|
[Obsolete("使用AsQueryable()")]
|
||||||
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
|
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
|
||||||
|
|
||||||
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
|
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
|
||||||
|
|
||||||
|
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||||
|
|
||||||
|
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
|
||||||
|
/// <summary>
|
||||||
|
/// 异步查询执行器
|
||||||
|
/// </summary>
|
||||||
public IAsyncQueryableExecuter AsyncExecuter { get; }
|
public IAsyncQueryableExecuter AsyncExecuter { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用变更追踪
|
||||||
|
/// </summary>
|
||||||
public bool? IsChangeTrackingEnabled => false;
|
public bool? IsChangeTrackingEnabled => false;
|
||||||
|
|
||||||
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider)
|
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider)
|
||||||
{
|
{
|
||||||
_sugarDbContextProvider = sugarDbContextProvider;
|
_dbContextProvider = dbContextProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取DB
|
/// 获取数据库上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
|
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
|
||||||
{
|
{
|
||||||
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
|
var dbContext = await _dbContextProvider.GetDbContextAsync();
|
||||||
return db;
|
return dbContext.SqlSugarClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取简单Db
|
/// 获取简单数据库客户端
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync()
|
public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync()
|
||||||
{
|
{
|
||||||
var db = await GetDbContextAsync();
|
var dbContext = await GetDbContextAsync();
|
||||||
return new SimpleClient<TEntity>(db);
|
return new SimpleClient<TEntity>(dbContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Abp模块
|
#region Abp模块
|
||||||
@@ -259,6 +266,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
|
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
|
||||||
{
|
{
|
||||||
var entity = await GetByIdAsync(id);
|
var entity = await GetByIdAsync(id);
|
||||||
|
if (entity == null) return false;
|
||||||
//反射赋值
|
//反射赋值
|
||||||
ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
|
ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
|
||||||
return await UpdateAsync(entity);
|
return await UpdateAsync(entity);
|
||||||
@@ -314,12 +322,12 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
|
|
||||||
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize)
|
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize)
|
||||||
{
|
{
|
||||||
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression, new PageModel() { PageIndex = pageNum, PageSize = pageSize });
|
return await (await AsQueryable()).Where(whereExpression).ToPageListAsync(pageNum, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
|
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
|
||||||
{
|
{
|
||||||
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression, new PageModel { PageIndex = pageNum, PageSize = pageSize }, orderByExpression, orderByType);
|
return await (await AsQueryable()).Where(whereExpression) .OrderBy( orderByExpression,orderByType).ToPageListAsync(pageNum, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
|
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||||
@@ -374,20 +382,24 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
|
|
||||||
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
|
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
|
||||||
{
|
{
|
||||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>())//带版本号乐观锁更新
|
if (Options is not null && Options.EnabledConcurrencyException)
|
||||||
{
|
{
|
||||||
try
|
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
|
||||||
{
|
{
|
||||||
int num = await (await GetDbSimpleClientAsync())
|
try
|
||||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
{
|
||||||
return num>0;
|
int num = await (await GetDbSimpleClientAsync())
|
||||||
}
|
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||||
catch (VersionExceptions ex)
|
return num > 0;
|
||||||
{
|
}
|
||||||
|
catch (VersionExceptions ex)
|
||||||
throw new AbpDbConcurrencyException($"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
{
|
||||||
|
throw new AbpDbConcurrencyException(
|
||||||
|
$"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar Core扩展方法
|
||||||
|
/// </summary>
|
||||||
|
public static class SqlSugarCoreExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 添加数据库上下文
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
|
||||||
|
/// <param name="services">服务集合</param>
|
||||||
|
/// <param name="serviceLifetime">服务生命周期</param>
|
||||||
|
/// <returns>服务集合</returns>
|
||||||
|
public static IServiceCollection AddYiDbContext<TDbContext>(
|
||||||
|
this IServiceCollection services,
|
||||||
|
ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
|
||||||
|
where TDbContext : class, ISqlSugarDbContextDependencies
|
||||||
|
{
|
||||||
|
services.Add(new ServiceDescriptor(
|
||||||
|
typeof(ISqlSugarDbContextDependencies),
|
||||||
|
typeof(TDbContext),
|
||||||
|
serviceLifetime));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加数据库上下文并配置选项
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
|
||||||
|
/// <param name="services">服务集合</param>
|
||||||
|
/// <param name="configureOptions">配置选项委托</param>
|
||||||
|
/// <returns>服务集合</returns>
|
||||||
|
public static IServiceCollection AddYiDbContext<TDbContext>(
|
||||||
|
this IServiceCollection services,
|
||||||
|
Action<DbConnOptions> configureOptions)
|
||||||
|
where TDbContext : class, ISqlSugarDbContextDependencies
|
||||||
|
{
|
||||||
|
services.Configure(configureOptions);
|
||||||
|
services.AddYiDbContext<TDbContext>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,123 +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
|
|
||||||
{
|
|
||||||
// 处理表
|
|
||||||
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);// 驼峰转下划线
|
|
||||||
|
|
||||||
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,362 +1,83 @@
|
|||||||
using System.Collections;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp.Auditing;
|
|
||||||
using Volo.Abp.Data;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
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;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
using Check = Volo.Abp.Check;
|
|
||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore
|
namespace Yi.Framework.SqlSugarCore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库上下文基类
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
|
||||||
{
|
{
|
||||||
public class SqlSugarDbContext : ISqlSugarDbContext
|
/// <summary>
|
||||||
|
/// 服务提供者
|
||||||
|
/// </summary>
|
||||||
|
protected IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库客户端实例
|
||||||
|
/// </summary>
|
||||||
|
protected ISqlSugarClient SqlSugarClient { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行顺序
|
||||||
|
/// </summary>
|
||||||
|
public virtual int ExecutionOrder => 0;
|
||||||
|
|
||||||
|
protected SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||||
{
|
{
|
||||||
/// <summary>
|
LazyServiceProvider = lazyServiceProvider;
|
||||||
/// SqlSugar 客户端
|
}
|
||||||
/// </summary>
|
|
||||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
|
||||||
|
|
||||||
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
/// <summary>
|
||||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
/// 配置SqlSugar客户端
|
||||||
|
/// </summary>
|
||||||
|
public virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||||
|
{
|
||||||
|
SqlSugarClient = sqlSugarClient;
|
||||||
|
CustomDataFilter(sqlSugarClient);
|
||||||
|
}
|
||||||
|
|
||||||
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
/// <summary>
|
||||||
private ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
/// 自定义数据过滤器
|
||||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
/// </summary>
|
||||||
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||||
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
/// <summary>
|
||||||
|
/// 数据执行后事件
|
||||||
|
/// </summary>
|
||||||
|
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
/// <summary>
|
||||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
/// 数据执行前事件
|
||||||
|
/// </summary>
|
||||||
|
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private ISerializeService SerializeService=> LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
/// <summary>
|
||||||
|
/// SQL执行前事件
|
||||||
|
/// </summary>
|
||||||
|
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
/// <summary>
|
||||||
{
|
/// SQL执行后事件
|
||||||
SqlSugarClient = sqlSugarClient;
|
/// </summary>
|
||||||
}
|
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||||
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
{
|
||||||
{
|
}
|
||||||
LazyServiceProvider = lazyServiceProvider;
|
|
||||||
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
|
|
||||||
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();
|
|
||||||
}));
|
|
||||||
//统一使用aop处理
|
|
||||||
connectionCreator.SetDbAop(SqlSugarClient);
|
|
||||||
//替换默认序列化器
|
|
||||||
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// db切换多库支持
|
/// 实体服务配置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||||
protected virtual string GetCurrentConnectionString()
|
{
|
||||||
{
|
|
||||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
|
||||||
var connectionString = connectionStringResolver.ResolveAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 上下文对象扩展
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sqlSugarClient"></param>
|
|
||||||
protected virtual void OnSqlSugarClientConfig(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);
|
|
||||||
}
|
|
||||||
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 (DateTime.MinValue.Equals(oldValue))
|
|
||||||
{
|
|
||||||
entityInfo.SetValue(DateTime.Now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
|
||||||
{
|
|
||||||
if (CurrentUser.Id is not null)
|
|
||||||
{
|
|
||||||
entityInfo.SetValue(CurrentUser.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
|
||||||
{
|
|
||||||
if (CurrentTenant.Id is not null)
|
|
||||||
{
|
|
||||||
entityInfo.SetValue(CurrentTenant.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//领域事件
|
|
||||||
switch (entityInfo.OperationType)
|
|
||||||
{
|
|
||||||
case DataFilterType.InsertByObject:
|
|
||||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
|
||||||
{
|
|
||||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DataFilterType.UpdateByObject:
|
|
||||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
|
||||||
{
|
|
||||||
//软删除,发布的是删除事件
|
|
||||||
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
|
||||||
{
|
|
||||||
if (softDelete.IsDeleted == true)
|
|
||||||
{
|
|
||||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DataFilterType.DeleteByObject:
|
|
||||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
|
||||||
{
|
|
||||||
//这里sqlsugar有个特殊,删除会返回批量的结果
|
|
||||||
if (entityInfo.EntityValue is IEnumerable entityValues)
|
|
||||||
{
|
|
||||||
foreach (var entityValue in entityValues)
|
|
||||||
{
|
|
||||||
|
|
||||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
|
|
||||||
{
|
|
||||||
// column.IsOnlyIgnoreInsert = true;
|
|
||||||
// column.IsOnlyIgnoreUpdate = true;
|
|
||||||
column.IsEnableUpdateVersionValidation = 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("其他数据库备份未实现");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,26 +4,52 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore;
|
namespace Yi.Framework.SqlSugarCore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库上下文创建上下文
|
||||||
|
/// </summary>
|
||||||
public class SqlSugarDbContextCreationContext
|
public class SqlSugarDbContextCreationContext
|
||||||
{
|
{
|
||||||
public static SqlSugarDbContextCreationContext Current => _current.Value;
|
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> CurrentContextHolder =
|
||||||
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> _current = new AsyncLocal<SqlSugarDbContextCreationContext>();
|
new AsyncLocal<SqlSugarDbContextCreationContext>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前上下文
|
||||||
|
/// </summary>
|
||||||
|
public static SqlSugarDbContextCreationContext Current => CurrentContextHolder.Value!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接字符串名称
|
||||||
|
/// </summary>
|
||||||
public string ConnectionStringName { get; }
|
public string ConnectionStringName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接字符串
|
||||||
|
/// </summary>
|
||||||
public string ConnectionString { get; }
|
public string ConnectionString { get; }
|
||||||
|
|
||||||
public DbConnection ExistingConnection { get; internal set; }
|
/// <summary>
|
||||||
|
/// 现有数据库连接
|
||||||
|
/// </summary>
|
||||||
|
public DbConnection? ExistingConnection { get; internal set; }
|
||||||
|
|
||||||
public SqlSugarDbContextCreationContext(string connectionStringName, string connectionString)
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
public SqlSugarDbContextCreationContext(
|
||||||
|
string connectionStringName,
|
||||||
|
string connectionString)
|
||||||
{
|
{
|
||||||
ConnectionStringName = connectionStringName;
|
ConnectionStringName = connectionStringName;
|
||||||
ConnectionString = connectionString;
|
ConnectionString = connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用指定的上下文
|
||||||
|
/// </summary>
|
||||||
public static IDisposable Use(SqlSugarDbContextCreationContext context)
|
public static IDisposable Use(SqlSugarDbContextCreationContext context)
|
||||||
{
|
{
|
||||||
var previousValue = Current;
|
var previousContext = Current;
|
||||||
_current.Value = context;
|
CurrentContextHolder.Value = context;
|
||||||
return new DisposeAction(() => _current.Value = previousValue);
|
return new DisposeAction(() => CurrentContextHolder.Value = previousContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库上下文工厂类
|
||||||
|
/// 负责创建和配置SqlSugar客户端实例
|
||||||
|
/// </summary>
|
||||||
|
public class SqlSugarDbContextFactory : ISqlSugarDbContext
|
||||||
|
{
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar客户端实例
|
||||||
|
/// </summary>
|
||||||
|
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 延迟服务提供者
|
||||||
|
/// </summary>
|
||||||
|
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 租户配置包装器
|
||||||
|
/// </summary>
|
||||||
|
private TenantConfigurationWrapper TenantConfigurationWrapper =>
|
||||||
|
LazyServiceProvider.LazyGetRequiredService<TenantConfigurationWrapper>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前租户信息
|
||||||
|
/// </summary>
|
||||||
|
private ICurrentTenant CurrentTenant =>
|
||||||
|
LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库连接配置选项
|
||||||
|
/// </summary>
|
||||||
|
private DbConnOptions DbConnectionOptions =>
|
||||||
|
LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 序列化服务
|
||||||
|
/// </summary>
|
||||||
|
private ISerializeService SerializeService =>
|
||||||
|
LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar上下文依赖项集合
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
|
||||||
|
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接配置缓存字典
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lazyServiceProvider">延迟服务提供者</param>
|
||||||
|
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
|
||||||
|
{
|
||||||
|
LazyServiceProvider = lazyServiceProvider;
|
||||||
|
|
||||||
|
// 异步获取租户配置
|
||||||
|
var tenantConfiguration = AsyncHelper.RunSync(async () => await TenantConfigurationWrapper.GetAsync());
|
||||||
|
|
||||||
|
// 构建数据库连接配置
|
||||||
|
var connectionConfig = BuildConnectionConfig(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = tenantConfiguration.GetCurrentConnectionString();
|
||||||
|
options.DbType = GetCurrentDbType(tenantConfiguration.GetCurrentConnectionName());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建SqlSugar客户端实例
|
||||||
|
SqlSugarClient = new SqlSugarClient(connectionConfig);
|
||||||
|
|
||||||
|
// 配置数据库AOP
|
||||||
|
ConfigureDbAop(SqlSugarClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置数据库AOP操作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sqlSugarClient">SqlSugar客户端实例</param>
|
||||||
|
protected virtual void ConfigureDbAop(ISqlSugarClient sqlSugarClient)
|
||||||
|
{
|
||||||
|
// 配置序列化服务
|
||||||
|
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||||
|
|
||||||
|
// 初始化AOP事件处理器
|
||||||
|
Action<string, SugarParameter[]> onLogExecuting = null;
|
||||||
|
Action<string, SugarParameter[]> onLogExecuted = null;
|
||||||
|
Action<object, DataFilterModel> dataExecuting = null;
|
||||||
|
Action<object, DataAfterModel> dataExecuted = null;
|
||||||
|
Action<ISqlSugarClient> onClientConfig = null;
|
||||||
|
|
||||||
|
// 按执行顺序聚合所有依赖项的AOP处理器
|
||||||
|
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
|
||||||
|
{
|
||||||
|
onLogExecuting += dependency.OnLogExecuting;
|
||||||
|
onLogExecuted += dependency.OnLogExecuted;
|
||||||
|
dataExecuting += dependency.DataExecuting;
|
||||||
|
dataExecuted += dependency.DataExecuted;
|
||||||
|
onClientConfig += dependency.OnSqlSugarClientConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置SqlSugar客户端
|
||||||
|
onClientConfig?.Invoke(sqlSugarClient);
|
||||||
|
|
||||||
|
// 设置AOP事件
|
||||||
|
sqlSugarClient.Aop.OnLogExecuting = onLogExecuting;
|
||||||
|
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
|
||||||
|
sqlSugarClient.Aop.DataExecuting = dataExecuting;
|
||||||
|
sqlSugarClient.Aop.DataExecuted = dataExecuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建数据库连接配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configAction">配置操作委托</param>
|
||||||
|
/// <returns>连接配置对象</returns>
|
||||||
|
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig> configAction = null)
|
||||||
|
{
|
||||||
|
var dbConnOptions = DbConnectionOptions;
|
||||||
|
|
||||||
|
// 验证数据库类型配置
|
||||||
|
if (dbConnOptions.DbType is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("未配置数据库类型(DbType)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置读写分离
|
||||||
|
var slaveConfigs = new List<SlaveConnectionConfig>();
|
||||||
|
if (dbConnOptions.EnabledReadWrite)
|
||||||
|
{
|
||||||
|
if (dbConnOptions.ReadUrl is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("启用读写分离但未配置读库连接字符串");
|
||||||
|
}
|
||||||
|
|
||||||
|
slaveConfigs.AddRange(dbConnOptions.ReadUrl.Select(url =>
|
||||||
|
new SlaveConnectionConfig { ConnectionString = url }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建连接配置
|
||||||
|
var connectionConfig = new ConnectionConfig
|
||||||
|
{
|
||||||
|
ConfigId = ConnectionStrings.DefaultConnectionStringName,
|
||||||
|
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
|
||||||
|
ConnectionString = dbConnOptions.Url,
|
||||||
|
IsAutoCloseConnection = true,
|
||||||
|
SlaveConnectionConfigs = slaveConfigs,
|
||||||
|
ConfigureExternalServices = CreateExternalServices(dbConnOptions)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用额外配置
|
||||||
|
configAction?.Invoke(connectionConfig);
|
||||||
|
|
||||||
|
return connectionConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建外部服务配置
|
||||||
|
/// </summary>
|
||||||
|
private ConfigureExternalServices CreateExternalServices(DbConnOptions dbConnOptions)
|
||||||
|
{
|
||||||
|
return new ConfigureExternalServices
|
||||||
|
{
|
||||||
|
EntityNameService = (type, entity) =>
|
||||||
|
{
|
||||||
|
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
|
||||||
|
{
|
||||||
|
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EntityService = (propertyInfo, columnInfo) =>
|
||||||
|
{
|
||||||
|
// 配置空值处理
|
||||||
|
if (new NullabilityInfoContext().Create(propertyInfo).WriteState
|
||||||
|
is NullabilityState.Nullable)
|
||||||
|
{
|
||||||
|
columnInfo.IsNullable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理下划线命名
|
||||||
|
if (dbConnOptions.EnableUnderLine && !columnInfo.IsIgnore
|
||||||
|
&& !columnInfo.DbColumnName.Contains('_'))
|
||||||
|
{
|
||||||
|
columnInfo.DbColumnName = UtilMethods.ToUnderLine(columnInfo.DbColumnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 聚合所有依赖项的实体服务
|
||||||
|
Action<PropertyInfo, EntityColumnInfo> entityService = null;
|
||||||
|
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
|
||||||
|
{
|
||||||
|
entityService += dependency.EntityService;
|
||||||
|
}
|
||||||
|
|
||||||
|
entityService?.Invoke(propertyInfo, columnInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前数据库类型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tenantName">租户名称</param>
|
||||||
|
/// <returns>数据库类型</returns>
|
||||||
|
protected virtual DbType GetCurrentDbType(string tenantName)
|
||||||
|
{
|
||||||
|
return tenantName == ConnectionStrings.DefaultConnectionStringName
|
||||||
|
? DbConnectionOptions.DbType!.Value
|
||||||
|
: GetDbTypeFromTenantName(tenantName)
|
||||||
|
?? throw new ArgumentException($"无法从租户名称{tenantName}中解析数据库类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从租户名称解析数据库类型
|
||||||
|
/// 格式:TenantName@DbType
|
||||||
|
/// </summary>
|
||||||
|
private DbType? GetDbTypeFromTenantName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var atIndex = name.LastIndexOf('@');
|
||||||
|
if (atIndex == -1 || atIndex == name.Length - 1)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbTypeString = name[(atIndex + 1)..];
|
||||||
|
return Enum.TryParse<DbType>(dbTypeString, out var dbType)
|
||||||
|
? dbType
|
||||||
|
: throw new ArgumentException($"不支持的数据库类型: {dbTypeString}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备份数据库
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BackupDataBase()
|
||||||
|
{
|
||||||
|
const string backupDirectory = "database_backup";
|
||||||
|
var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}_{SqlSugarClient.Ado.Connection.Database}";
|
||||||
|
|
||||||
|
Directory.CreateDirectory(backupDirectory);
|
||||||
|
|
||||||
|
switch (DbConnectionOptions.DbType)
|
||||||
|
{
|
||||||
|
case DbType.MySql:
|
||||||
|
SqlSugarClient.DbMaintenance.BackupDataBase(
|
||||||
|
SqlSugarClient.Ado.Connection.Database,
|
||||||
|
Path.Combine(backupDirectory, $"{fileName}.sql"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DbType.Sqlite:
|
||||||
|
SqlSugarClient.DbMaintenance.BackupDataBase(
|
||||||
|
null,
|
||||||
|
$"{fileName}.db");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DbType.SqlServer:
|
||||||
|
SqlSugarClient.DbMaintenance.BackupDataBase(
|
||||||
|
SqlSugarClient.Ado.Connection.Database,
|
||||||
|
Path.Combine(backupDirectory, $"{fileName}.bak"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException($"数据库类型 {DbConnectionOptions.DbType} 的备份操作尚未实现");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,14 +63,14 @@ public class SqlSugarNonPublicSerializer : ISerializeService
|
|||||||
|
|
||||||
// 调用 SerializeObject 方法序列化对象
|
// 调用 SerializeObject 方法序列化对象
|
||||||
T json = (T)methods.MakeGenericMethod(typeof(T))
|
T json = (T)methods.MakeGenericMethod(typeof(T))
|
||||||
.Invoke(null, new object[] { value, null });
|
.Invoke(null, new object[] { value, null! });
|
||||||
return json;
|
return json!;
|
||||||
}
|
}
|
||||||
var jSetting = new JsonSerializerSettings
|
var jSetting = new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
NullValueHandling = NullValueHandling.Ignore,
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect
|
ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect
|
||||||
};
|
};
|
||||||
return JsonConvert.DeserializeObject<T>(value, jSetting);
|
return JsonConvert.DeserializeObject<T>(value, jSetting)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
namespace Yi.Framework.SqlSugarCore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 租户配置包装器
|
||||||
|
/// </summary>
|
||||||
|
public class TenantConfigurationWrapper : ITransientDependency
|
||||||
|
{
|
||||||
|
private readonly IAbpLazyServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
private ICurrentTenant CurrentTenantService =>
|
||||||
|
_serviceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||||
|
|
||||||
|
private ITenantStore TenantStoreService =>
|
||||||
|
_serviceProvider.LazyGetRequiredService<ITenantStore>();
|
||||||
|
|
||||||
|
private DbConnOptions DbConnectionOptions =>
|
||||||
|
_serviceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
public TenantConfigurationWrapper(IAbpLazyServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取租户配置信息
|
||||||
|
/// </summary>
|
||||||
|
public async Task<TenantConfiguration?> GetAsync()
|
||||||
|
{
|
||||||
|
if (!DbConnectionOptions.EnabledSaasMultiTenancy)
|
||||||
|
{
|
||||||
|
return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetTenantConfigurationByCurrentTenant();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TenantConfiguration?> GetTenantConfigurationByCurrentTenant()
|
||||||
|
{
|
||||||
|
// 通过租户ID查找
|
||||||
|
if (CurrentTenantService.Id.HasValue)
|
||||||
|
{
|
||||||
|
var config = await TenantStoreService.FindAsync(CurrentTenantService.Id.Value);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenantService.Id}");
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过租户名称查找
|
||||||
|
if (!string.IsNullOrEmpty(CurrentTenantService.Name))
|
||||||
|
{
|
||||||
|
var config = await TenantStoreService.FindAsync(CurrentTenantService.Name);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenantService.Name}");
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回默认配置
|
||||||
|
return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前连接字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<string> GetCurrentConnectionStringAsync()
|
||||||
|
{
|
||||||
|
return (await GetAsync()).ConnectionStrings.Default!;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前连接名
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<string> GetCurrentConnectionNameAsync()
|
||||||
|
{
|
||||||
|
return (await GetAsync()).Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TenantConfigurationExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前连接字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetCurrentConnectionString(this TenantConfiguration tenantConfiguration)
|
||||||
|
{
|
||||||
|
return tenantConfiguration.ConnectionStrings.Default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前连接名
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetCurrentConnectionName(this TenantConfiguration tenantConfiguration)
|
||||||
|
{
|
||||||
|
return tenantConfiguration.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,10 +8,20 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Uow
|
namespace Yi.Framework.SqlSugarCore.Uow
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar数据库API实现
|
||||||
|
/// </summary>
|
||||||
public class SqlSugarDatabaseApi : IDatabaseApi
|
public class SqlSugarDatabaseApi : IDatabaseApi
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库上下文
|
||||||
|
/// </summary>
|
||||||
public ISqlSugarDbContext DbContext { get; }
|
public ISqlSugarDbContext DbContext { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化SqlSugar数据库API
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dbContext">数据库上下文</param>
|
||||||
public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext)
|
public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext)
|
||||||
{
|
{
|
||||||
DbContext = dbContext;
|
DbContext = dbContext;
|
||||||
|
|||||||
@@ -3,33 +3,48 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
|||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore.Uow
|
namespace Yi.Framework.SqlSugarCore.Uow
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar事务API实现
|
||||||
|
/// </summary>
|
||||||
public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback
|
public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback
|
||||||
{
|
{
|
||||||
private ISqlSugarDbContext _sqlsugarDbContext;
|
private readonly ISqlSugarDbContext _dbContext;
|
||||||
|
|
||||||
public SqlSugarTransactionApi(ISqlSugarDbContext sqlsugarDbContext)
|
public SqlSugarTransactionApi(ISqlSugarDbContext dbContext)
|
||||||
{
|
{
|
||||||
_sqlsugarDbContext = sqlsugarDbContext;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取数据库上下文
|
||||||
|
/// </summary>
|
||||||
public ISqlSugarDbContext GetDbContext()
|
public ISqlSugarDbContext GetDbContext()
|
||||||
{
|
{
|
||||||
return _sqlsugarDbContext;
|
return _dbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提交事务
|
||||||
|
/// </summary>
|
||||||
public async Task CommitAsync(CancellationToken cancellationToken = default)
|
public async Task CommitAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync();
|
await _dbContext.SqlSugarClient.Ado.CommitTranAsync();
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_sqlsugarDbContext.SqlSugarClient.Ado.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 回滚事务
|
||||||
|
/// </summary>
|
||||||
public async Task RollbackAsync(CancellationToken cancellationToken = default)
|
public async Task RollbackAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await _sqlsugarDbContext.SqlSugarClient.Ado.RollbackTranAsync();
|
await _dbContext.SqlSugarClient.Ado.RollbackTranAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_dbContext.SqlSugarClient.Ado.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,138 +13,121 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
|||||||
{
|
{
|
||||||
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
||||||
{
|
{
|
||||||
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
/// <summary>
|
||||||
|
/// 日志记录器
|
||||||
|
/// </summary>
|
||||||
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 服务提供者
|
||||||
|
/// </summary>
|
||||||
public IServiceProvider ServiceProvider { get; set; }
|
public IServiceProvider ServiceProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库上下文访问器实例
|
||||||
|
/// </summary>
|
||||||
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
|
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
|
||||||
protected readonly IUnitOfWorkManager UnitOfWorkManager;
|
|
||||||
protected readonly IConnectionStringResolver ConnectionStringResolver;
|
private readonly TenantConfigurationWrapper _tenantConfigurationWrapper;
|
||||||
protected readonly ICancellationTokenProvider CancellationTokenProvider;
|
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||||
protected readonly ICurrentTenant CurrentTenant;
|
private readonly IConnectionStringResolver _connectionStringResolver;
|
||||||
|
private readonly ICancellationTokenProvider _cancellationTokenProvider;
|
||||||
|
private readonly ICurrentTenant _currentTenant;
|
||||||
|
|
||||||
public UnitOfWorkSqlsugarDbContextProvider(
|
public UnitOfWorkSqlsugarDbContextProvider(
|
||||||
IUnitOfWorkManager unitOfWorkManager,
|
IUnitOfWorkManager unitOfWorkManager,
|
||||||
IConnectionStringResolver connectionStringResolver,
|
IConnectionStringResolver connectionStringResolver,
|
||||||
ICancellationTokenProvider cancellationTokenProvider,
|
ICancellationTokenProvider cancellationTokenProvider,
|
||||||
ICurrentTenant currentTenant,
|
ICurrentTenant currentTenant,
|
||||||
ISqlSugarDbConnectionCreator dbConnectionCreator
|
TenantConfigurationWrapper tenantConfigurationWrapper)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
UnitOfWorkManager = unitOfWorkManager;
|
_unitOfWorkManager = unitOfWorkManager;
|
||||||
ConnectionStringResolver = connectionStringResolver;
|
_connectionStringResolver = connectionStringResolver;
|
||||||
CancellationTokenProvider = cancellationTokenProvider;
|
_cancellationTokenProvider = cancellationTokenProvider;
|
||||||
CurrentTenant = currentTenant;
|
_currentTenant = currentTenant;
|
||||||
|
_tenantConfigurationWrapper = tenantConfigurationWrapper;
|
||||||
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
|
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
|
||||||
_dbConnectionCreator = dbConnectionCreator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//private static object _databaseApiLock = new object();
|
/// <summary>
|
||||||
|
/// 获取数据库上下文
|
||||||
|
/// </summary>
|
||||||
public virtual async Task<TDbContext> GetDbContextAsync()
|
public virtual async Task<TDbContext> GetDbContextAsync()
|
||||||
{
|
{
|
||||||
|
// 获取当前租户配置
|
||||||
|
var tenantConfiguration = await _tenantConfigurationWrapper.GetAsync();
|
||||||
|
|
||||||
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
|
// 获取连接字符串信息
|
||||||
|
var connectionStringName = tenantConfiguration.GetCurrentConnectionName();
|
||||||
//获取当前连接字符串,未多租户时,默认为空
|
var connectionString = tenantConfiguration.GetCurrentConnectionString();
|
||||||
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
|
|
||||||
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
|
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
|
||||||
|
|
||||||
|
var unitOfWork = _unitOfWorkManager.Current;
|
||||||
var unitOfWork = UnitOfWorkManager.Current;
|
if (unitOfWork == null)
|
||||||
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/)
|
|
||||||
{
|
{
|
||||||
var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
|
throw new AbpException(
|
||||||
//提高体验,取消工作单元强制性
|
"DbContext 只能在工作单元内工作,当前DbContext没有工作单元,如需创建新线程并发操作,请手动创建工作单元");
|
||||||
//throw new AbpException("A DbContext can only be created inside a unit of work!");
|
|
||||||
//如果不启用工作单元,创建一个新的db,不开启事务即可
|
|
||||||
return dbContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 尝试从当前工作单元获取数据库API
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//尝试当前工作单元获取db
|
|
||||||
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
||||||
|
|
||||||
//当前没有db创建一个新的db
|
// 当前没有数据库API则创建新的
|
||||||
if (databaseApi == null)
|
if (databaseApi == null)
|
||||||
{
|
{
|
||||||
//db根据连接字符串来创建
|
|
||||||
databaseApi = new SqlSugarDatabaseApi(
|
databaseApi = new SqlSugarDatabaseApi(
|
||||||
await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
|
await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
|
||||||
);
|
);
|
||||||
|
|
||||||
//await Console.Out.WriteLineAsync(">>>----------------实例化了db"+ ((SqlSugarDatabaseApi)databaseApi).DbContext.SqlSugarClient.ContextID.ToString());
|
|
||||||
//创建的db加入到当前工作单元中
|
|
||||||
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
|
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext;
|
return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建数据库上下文
|
||||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
|
/// </summary>
|
||||||
|
protected virtual async Task<TDbContext> CreateDbContextAsync(
|
||||||
|
IUnitOfWork unitOfWork,
|
||||||
|
string connectionStringName,
|
||||||
|
string connectionString)
|
||||||
{
|
{
|
||||||
var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString);
|
var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString);
|
||||||
//将连接key进行传值
|
|
||||||
using (SqlSugarDbContextCreationContext.Use(creationContext))
|
using (SqlSugarDbContextCreationContext.Use(creationContext))
|
||||||
{
|
{
|
||||||
var dbContext = await CreateDbContextAsync(unitOfWork);
|
return await CreateDbContextAsync(unitOfWork);
|
||||||
return dbContext;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据工作单元创建数据库上下文
|
||||||
|
/// </summary>
|
||||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
|
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
return unitOfWork.Options.IsTransactional
|
return unitOfWork.Options.IsTransactional
|
||||||
? await CreateDbContextWithTransactionAsync(unitOfWork)
|
? await CreateDbContextWithTransactionAsync(unitOfWork)
|
||||||
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建带事务的数据库上下文
|
||||||
|
/// </summary>
|
||||||
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
|
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
//事务key
|
var transactionApiKey = $"SqlSugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
|
||||||
var transactionApiKey = $"SqlsugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
|
|
||||||
|
|
||||||
//尝试查找事务
|
|
||||||
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
|
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
|
||||||
|
|
||||||
//该db还没有进行开启事务
|
|
||||||
if (activeTransaction == null)
|
if (activeTransaction == null)
|
||||||
{
|
{
|
||||||
//获取到db添加事务即可
|
|
||||||
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||||
var transaction = new SqlSugarTransactionApi(
|
var transaction = new SqlSugarTransactionApi(dbContext);
|
||||||
dbContext
|
|
||||||
);
|
|
||||||
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
||||||
|
|
||||||
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
||||||
return dbContext;
|
return dbContext;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return (TDbContext)activeTransaction.GetDbContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
return (TDbContext)activeTransaction.GetDbContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected virtual async Task<string> ResolveConnectionStringAsync(string connectionStringName)
|
|
||||||
{
|
|
||||||
if (typeof(TDbContext).IsDefined(typeof(IgnoreMultiTenancyAttribute), false))
|
|
||||||
{
|
|
||||||
using (CurrentTenant.Change(null))
|
|
||||||
{
|
|
||||||
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,111 +6,189 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp;
|
|
||||||
using Volo.Abp.Auditing;
|
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.Domain;
|
using Volo.Abp.Domain;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Guids;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
using Volo.Abp.MultiTenancy.ConfigurationStore;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
using Yi.Framework.SqlSugarCore.Repositories;
|
using Yi.Framework.SqlSugarCore.Repositories;
|
||||||
using Yi.Framework.SqlSugarCore.Uow;
|
using Yi.Framework.SqlSugarCore.Uow;
|
||||||
|
|
||||||
namespace Yi.Framework.SqlSugarCore
|
namespace Yi.Framework.SqlSugarCore
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SqlSugar Core模块
|
||||||
|
/// </summary>
|
||||||
[DependsOn(typeof(AbpDddDomainModule))]
|
[DependsOn(typeof(AbpDddDomainModule))]
|
||||||
public class YiFrameworkSqlSugarCoreModule : AbpModule
|
public class YiFrameworkSqlSugarCoreModule : AbpModule
|
||||||
{
|
{
|
||||||
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
|
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
|
||||||
{
|
{
|
||||||
var service = context.Services;
|
var services = context.Services;
|
||||||
var configuration = service.GetConfiguration();
|
var configuration = services.GetConfiguration();
|
||||||
var section = configuration.GetSection("DbConnOptions");
|
|
||||||
Configure<DbConnOptions>(section);
|
|
||||||
|
|
||||||
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
|
// 配置数据库连接选项
|
||||||
|
ConfigureDbOptions(services, configuration);
|
||||||
|
|
||||||
//不开放sqlsugarClient
|
// 配置GUID生成器
|
||||||
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
|
ConfigureGuidGenerator(services);
|
||||||
|
|
||||||
|
// 注册仓储和服务
|
||||||
service.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
|
RegisterRepositories(services);
|
||||||
service.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
|
|
||||||
service.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
|
|
||||||
service.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
|
|
||||||
|
|
||||||
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
|
|
||||||
//替换Sqlsugar默认序列化器,用来解决.Select()不支持嵌套对象/匿名对象的非公有访问器 值无法绑定,如Id属性
|
|
||||||
context.Services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
|
|
||||||
|
|
||||||
var dbConfig = section.Get<DbConnOptions>();
|
|
||||||
//将默认db传递给abp连接字符串模块
|
|
||||||
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ConfigureDbOptions(IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var section = configuration.GetSection("DbConnOptions");
|
||||||
|
Configure<DbConnOptions>(section);
|
||||||
|
|
||||||
|
var dbConnOptions = new DbConnOptions();
|
||||||
|
section.Bind(dbConnOptions);
|
||||||
|
|
||||||
|
// 配置默认连接字符串
|
||||||
|
Configure<AbpDbConnectionOptions>(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionStrings.Default = dbConnOptions.Url;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 配置默认租户
|
||||||
|
ConfigureDefaultTenant(services, dbConnOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureGuidGenerator(IServiceCollection services)
|
||||||
|
{
|
||||||
|
var dbConnOptions = services.GetConfiguration()
|
||||||
|
.GetSection("DbConnOptions")
|
||||||
|
.Get<DbConnOptions>();
|
||||||
|
|
||||||
|
var guidType = GetSequentialGuidType(dbConnOptions?.DbType);
|
||||||
|
Configure<AbpSequentialGuidGeneratorOptions>(options =>
|
||||||
|
{
|
||||||
|
options.DefaultSequentialGuidType = guidType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterRepositories(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.TryAddTransient<ISqlSugarDbContext, SqlSugarDbContextFactory>();
|
||||||
|
services.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
|
||||||
|
services.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
|
||||||
|
services.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
|
||||||
|
services.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
|
||||||
|
services.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
|
||||||
|
services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
|
||||||
|
services.AddYiDbContext<DefaultSqlSugarDbContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureDefaultTenant(IServiceCollection services, DbConnOptions dbConfig)
|
||||||
|
{
|
||||||
|
Configure<AbpDefaultTenantStoreOptions>(options =>
|
||||||
|
{
|
||||||
|
var tenants = options.Tenants.ToList();
|
||||||
|
|
||||||
|
// 规范化租户名称
|
||||||
|
foreach (var tenant in tenants)
|
||||||
|
{
|
||||||
|
tenant.NormalizedName = tenant.Name.Contains("@")
|
||||||
|
? tenant.Name.Substring(0, tenant.Name.LastIndexOf("@"))
|
||||||
|
: tenant.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加默认租户
|
||||||
|
tenants.Insert(0, new TenantConfiguration
|
||||||
|
{
|
||||||
|
Id = Guid.Empty,
|
||||||
|
Name = ConnectionStrings.DefaultConnectionStringName,
|
||||||
|
NormalizedName = ConnectionStrings.DefaultConnectionStringName,
|
||||||
|
ConnectionStrings = new ConnectionStrings
|
||||||
|
{
|
||||||
|
{ ConnectionStrings.DefaultConnectionStringName, dbConfig.Url }
|
||||||
|
},
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
options.Tenants = tenants.ToArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private SequentialGuidType GetSequentialGuidType(DbType? dbType)
|
||||||
|
{
|
||||||
|
return dbType switch
|
||||||
|
{
|
||||||
|
DbType.MySql or DbType.PostgreSQL => SequentialGuidType.SequentialAsString,
|
||||||
|
DbType.SqlServer => SequentialGuidType.SequentialAtEnd,
|
||||||
|
DbType.Oracle => SequentialGuidType.SequentialAsBinary,
|
||||||
|
_ => SequentialGuidType.SequentialAtEnd
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
|
public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||||
{
|
{
|
||||||
//进行CodeFirst
|
var serviceProvider = context.ServiceProvider;
|
||||||
var service = context.ServiceProvider;
|
var options = serviceProvider.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||||
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
var logger = serviceProvider.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
|
||||||
|
|
||||||
var logger = service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
|
// 记录配置信息
|
||||||
|
LogConfiguration(logger, options);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine("==========Yi-SQL配置:==========");
|
|
||||||
sb.AppendLine($"数据库连接字符串:{options.Url}");
|
|
||||||
sb.AppendLine($"数据库类型:{options.DbType.ToString()}");
|
|
||||||
sb.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}");
|
|
||||||
sb.AppendLine($"是否开启CodeFirst:{options.EnabledCodeFirst}");
|
|
||||||
sb.AppendLine($"是否开启Saas多租户:{options.EnabledSaasMultiTenancy}");
|
|
||||||
sb.AppendLine("===============================");
|
|
||||||
|
|
||||||
|
|
||||||
logger.LogInformation(sb.ToString());
|
|
||||||
//Todo:准备支持多租户种子数据及CodeFirst
|
|
||||||
|
|
||||||
|
// 初始化数据库
|
||||||
if (options.EnabledCodeFirst)
|
if (options.EnabledCodeFirst)
|
||||||
{
|
{
|
||||||
CodeFirst(service);
|
await InitializeDatabase(serviceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化种子数据
|
||||||
if (options.EnabledDbSeed)
|
if (options.EnabledDbSeed)
|
||||||
{
|
{
|
||||||
await DataSeedAsync(service);
|
await InitializeSeedData(serviceProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CodeFirst(IServiceProvider service)
|
private void LogConfiguration(ILogger logger, DbConnOptions options)
|
||||||
{
|
{
|
||||||
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
var logMessage = new StringBuilder()
|
||||||
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
.AppendLine()
|
||||||
|
.AppendLine("==========Yi-SQL配置:==========")
|
||||||
|
.AppendLine($"数据库连接字符串:{options.Url}")
|
||||||
|
.AppendLine($"数据库类型:{options.DbType}")
|
||||||
|
.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}")
|
||||||
|
.AppendLine($"是否开启CodeFirst:{options.EnabledCodeFirst}")
|
||||||
|
.AppendLine($"是否开启Saas多租户:{options.EnabledSaasMultiTenancy}")
|
||||||
|
.AppendLine("===============================")
|
||||||
|
.ToString();
|
||||||
|
|
||||||
//尝试创建数据库
|
logger.LogInformation(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitializeDatabase(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var moduleContainer = serviceProvider.GetRequiredService<IModuleContainer>();
|
||||||
|
var db = serviceProvider.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
||||||
|
|
||||||
|
// 创建数据库
|
||||||
db.DbMaintenance.CreateDatabase();
|
db.DbMaintenance.CreateDatabase();
|
||||||
|
|
||||||
List<Type> types = new List<Type>();
|
// 获取需要创建表的实体类型
|
||||||
foreach (var module in moduleContainer.Modules)
|
var entityTypes = moduleContainer.Modules
|
||||||
{
|
.SelectMany(m => m.Assembly.GetTypes())
|
||||||
types.AddRange(module.Assembly.GetTypes()
|
.Where(t => t.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null
|
||||||
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
|
&& t.GetCustomAttribute<SugarTable>() != null
|
||||||
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
|
&& t.GetCustomAttribute<SplitTableAttribute>() == null)
|
||||||
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
.ToList();
|
||||||
}
|
|
||||||
|
|
||||||
if (types.Count > 0)
|
if (entityTypes.Any())
|
||||||
{
|
{
|
||||||
db.CopyNew().CodeFirst.InitTables(types.ToArray());
|
db.CopyNew().CodeFirst.InitTables(entityTypes.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DataSeedAsync(IServiceProvider service)
|
private async Task InitializeSeedData(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
var dataSeeder = service.GetRequiredService<IDataSeeder>();
|
var dataSeeder = serviceProvider.GetRequiredService<IDataSeeder>();
|
||||||
await dataSeeder.SeedAsync();
|
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 IUnitOfWorkManager UnitOfWorkManager { get; }
|
||||||
protected AbpAuditingOptions Options { get; }
|
protected AbpAuditingOptions Options { get; }
|
||||||
protected IAuditLogInfoToAuditLogConverter Converter { get; }
|
protected IAuditLogInfoToAuditLogConverter Converter { get; }
|
||||||
|
|
||||||
public AuditingStore(
|
public AuditingStore(
|
||||||
IAuditLogRepository auditLogRepository,
|
IAuditLogRepository auditLogRepository,
|
||||||
IUnitOfWorkManager unitOfWorkManager,
|
IUnitOfWorkManager unitOfWorkManager,
|
||||||
@@ -52,7 +53,7 @@ public class AuditingStore : IAuditingStore, ITransientDependency
|
|||||||
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
|
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss"));
|
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 AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
|
||||||
await uow.CompleteAsync();
|
await uow.CompleteAsync();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class AuditLogActionEntity : Entity<Guid>, IMultiTenant
|
|||||||
|
|
||||||
public virtual string? MethodName { get; protected set; }
|
public virtual string? MethodName { get; protected set; }
|
||||||
|
|
||||||
|
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
|
||||||
public virtual string? Parameters { get; protected set; }
|
public virtual string? Parameters { get; protected set; }
|
||||||
|
|
||||||
public virtual DateTime? ExecutionTime { get; protected set; }
|
public virtual DateTime? ExecutionTime { get; protected set; }
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ namespace Yi.Framework.AuditLogging.Domain.Entities
|
|||||||
|
|
||||||
public virtual string? CorrelationId { get; set; }
|
public virtual string? CorrelationId { get; set; }
|
||||||
|
|
||||||
|
[SugarColumn(Length = 2000)]
|
||||||
public virtual string? BrowserInfo { get; protected set; }
|
public virtual string? BrowserInfo { get; protected set; }
|
||||||
|
|
||||||
public virtual string? HttpMethod { get; protected set; }
|
public virtual string? HttpMethod { get; protected set; }
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.AccessLog
|
|
||||||
{
|
|
||||||
public class AccessLogDto
|
|
||||||
{
|
|
||||||
public long Number { get; set; }
|
|
||||||
public DateTime? LastModificationTime { get; set; }
|
|
||||||
public DateTime CreationTime { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
|
|
||||||
|
|
||||||
public class RegisterAnalyseDto
|
|
||||||
{
|
|
||||||
public RegisterAnalyseDto(DateTime time, int number)
|
|
||||||
{
|
|
||||||
Time = time;
|
|
||||||
Number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 时间
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Time { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 人数
|
|
||||||
/// </summary>
|
|
||||||
public int Number { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Argee
|
|
||||||
{
|
|
||||||
public class AgreeDto
|
|
||||||
{
|
|
||||||
public AgreeDto(bool isAgree)
|
|
||||||
{
|
|
||||||
IsAgree = isAgree;
|
|
||||||
if (isAgree)
|
|
||||||
{
|
|
||||||
|
|
||||||
Message = "点赞成功,点赞+1";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
Message = "取消点赞,点赞-1";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAgree { get; set; }
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using Volo.Abp.Application.Dtos;
|
|
||||||
|
|
||||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
|
||||||
{
|
|
||||||
public class ArticleAllOutputDto : EntityDto<Guid>
|
|
||||||
{
|
|
||||||
|
|
||||||
//批量查询,不给内容,性能考虑
|
|
||||||
//public string Content { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public Guid DiscussId { get; set; }
|
|
||||||
public Guid ParentId { get; set; }
|
|
||||||
|
|
||||||
public List<ArticleAllOutputDto>? Children { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Article输入创建对象
|
|
||||||
/// </summary>
|
|
||||||
public class ArticleCreateInputVo
|
|
||||||
{
|
|
||||||
public string Content { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public Guid DiscussId { get; set; }
|
|
||||||
public Guid ParentId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user