mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-03 00:00:58 +08:00
Compare commits
693 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9e8b2b01f | ||
|
|
4d09243efd | ||
|
|
5934056fe6 | ||
|
|
2a81062fa3 | ||
|
|
fdc868323f | ||
|
|
593b3a4cdd | ||
|
|
2b12e18e6c | ||
|
|
345ed80ec8 | ||
|
|
29dc1ae250 | ||
|
|
9fdd41b134 | ||
|
|
31ee5e8ffb | ||
|
|
d7922bb71d | ||
|
|
fa3ac91ba4 | ||
|
|
e7c152e955 | ||
|
|
0223b5c104 | ||
|
|
9e41a7c446 | ||
|
|
2ba0ffc1b7 | ||
|
|
7d038e1266 | ||
|
|
b98285f314 | ||
|
|
73438da666 | ||
|
|
85f2e1b579 | ||
|
|
ece89ebad0 | ||
|
|
6e2dd39246 | ||
|
|
a61286e534 | ||
|
|
4f944a5466 | ||
|
|
d29aac088a | ||
|
|
8abd122773 | ||
|
|
08084aa0bc | ||
|
|
e69cd5a73c | ||
|
|
76aa3bdc64 | ||
|
|
93251104af | ||
|
|
3cae477f3e | ||
|
|
25c736dc0a | ||
|
|
96a09d8980 | ||
|
|
72387235a0 | ||
|
|
1b00e505b7 | ||
|
|
1c54e47b9e | ||
|
|
ba07e2c905 | ||
|
|
bde4611a50 | ||
|
|
e7326fea7b | ||
|
|
d13b23ad2e | ||
|
|
8b1830a711 | ||
|
|
b70c530754 | ||
|
|
d90e24f9ed | ||
|
|
1fbd521d1a | ||
|
|
2ae6183e7f | ||
|
|
7905911624 | ||
|
|
c5b6b33d8e | ||
|
|
5d29fd6d3b | ||
|
|
ad8f48f36b | ||
|
|
f9843c13d4 | ||
|
|
6bd561b094 | ||
|
|
d2c6238df1 | ||
|
|
1d108983e8 | ||
|
|
b768bca638 | ||
|
|
28fcd6c9ce | ||
|
|
10559a925c | ||
|
|
942e218a9e | ||
|
|
f6af9edc38 | ||
|
|
e0f6331ec3 | ||
|
|
06f0c6caa7 | ||
|
|
56ec260e3a | ||
|
|
176cf84369 | ||
|
|
6de3b722ed | ||
|
|
2cf6326764 | ||
|
|
ec27ee58b4 | ||
|
|
4e42e2202e | ||
|
|
e60d8eceb7 | ||
|
|
08fb939b38 | ||
|
|
482dd73afd | ||
|
|
f09a9fee75 | ||
|
|
9a31d14b41 | ||
|
|
2fd7f88f04 | ||
|
|
9d4cc802e9 | ||
|
|
ee6b4827fa | ||
|
|
48d8c528f6 | ||
|
|
40c0a5ac64 | ||
|
|
3a60bcc174 | ||
|
|
2b3fad16fd | ||
|
|
f0cf6bf5c8 | ||
|
|
0ba4e3240b | ||
|
|
9332b17fc1 | ||
|
|
4ec4023f40 | ||
|
|
d9971541f2 | ||
|
|
7b0e4fcc73 | ||
|
|
cfde73d13a | ||
|
|
c17c9000a8 | ||
|
|
42d537a68b | ||
|
|
25eebec8f7 | ||
|
|
bbe5b01872 | ||
|
|
6b31536de5 | ||
|
|
2e5db5500f | ||
|
|
7038d31c53 | ||
|
|
3eb27c3d35 | ||
|
|
a9c3a1bcec | ||
|
|
384926e73a | ||
|
|
4335c12659 | ||
|
|
e6e4829164 | ||
|
|
f3c67cf598 | ||
|
|
4681d468ce | ||
|
|
63e7d3d5f5 | ||
|
|
f47d8c8ce3 | ||
|
|
6f69f45ddc | ||
|
|
e73678c788 | ||
|
|
09a2f91cbf | ||
|
|
29da7499a4 | ||
|
|
5b024e9443 | ||
|
|
225932eff1 | ||
|
|
65d5f5ae86 | ||
|
|
3e647ef14d | ||
|
|
7cb3aea2e6 | ||
|
|
7f4b8f1c8a | ||
|
|
0a2710b865 | ||
|
|
2a301c4983 | ||
|
|
faa8131a1b | ||
|
|
71bd885bd0 | ||
|
|
691a1e50f0 | ||
|
|
ef6e9fd16d | ||
|
|
17f9ac6d54 | ||
|
|
3f8e6e48c0 | ||
|
|
bda4fdf69d | ||
|
|
5c85ed13fd | ||
|
|
1986901031 | ||
|
|
e1d3ec21e5 | ||
|
|
f45283dade | ||
|
|
31c44d8df7 | ||
|
|
bf443963c8 | ||
|
|
a0eb234539 | ||
|
|
b6d670c240 | ||
|
|
b5fb2c42c6 | ||
|
|
d72cc529ba | ||
|
|
660bd00cae | ||
|
|
b5489711ec | ||
|
|
76717c4f8a | ||
|
|
3d53d0bcd6 | ||
|
|
c7c9428b68 | ||
|
|
991a970d6a | ||
|
|
cbe93b9f7e | ||
|
|
5d7217b775 | ||
|
|
d6836b8bcf | ||
|
|
c367651c78 | ||
|
|
77123fd971 | ||
|
|
9d73a6837b | ||
|
|
651f0157dc | ||
|
|
90e8dbe449 | ||
|
|
ccba2667bc | ||
|
|
3ce9fc9790 | ||
|
|
2f24dd77bf | ||
|
|
2bc07cb3df | ||
|
|
30678dbbb4 | ||
|
|
c5b0f69b51 | ||
|
|
e593f2cba4 | ||
|
|
10f7499066 | ||
|
|
36b7e495f7 | ||
|
|
94b96e3c19 | ||
|
|
0d1ee18da0 | ||
|
|
cab0b61ee0 | ||
|
|
8e6611d76d | ||
|
|
1ef82e5f93 | ||
|
|
43dc962606 | ||
|
|
020d674ca2 | ||
|
|
bb0e1081cc | ||
|
|
5162f9ce3b | ||
|
|
57fae7fe4b | ||
|
|
17412d7de7 | ||
|
|
d59f40dfba | ||
|
|
5953be63cb | ||
|
|
58a4311947 | ||
|
|
c5a9b9a15f | ||
|
|
716c344780 | ||
|
|
9af8c4897b | ||
|
|
ca72024a68 | ||
|
|
0d2bc585a9 | ||
|
|
4e3edefb35 | ||
|
|
9408242726 | ||
|
|
4710208e81 | ||
|
|
4fc6a1e818 | ||
|
|
c9b79a074b | ||
|
|
2e79eb346f | ||
|
|
3f88bd4158 | ||
|
|
f58e079741 | ||
|
|
6f1eb1f4b9 | ||
|
|
826d529997 | ||
|
|
43e60eab4a | ||
|
|
6c33024790 | ||
|
|
d27e625fde | ||
|
|
23cecb9360 | ||
|
|
7e4c835ced | ||
|
|
aff460f555 | ||
|
|
52961b459e | ||
|
|
0af2f867fc | ||
|
|
85e291e0b8 | ||
|
|
6d8a859b20 | ||
|
|
a70dfb0769 | ||
|
|
c637d412e6 | ||
|
|
e996bc2d7f | ||
|
|
15be047371 | ||
|
|
0a0e0bca10 | ||
|
|
9a8f3bd161 | ||
|
|
7e2c035692 | ||
|
|
44b2ade9bc | ||
|
|
1200d02fbf | ||
|
|
b020f48325 | ||
|
|
917857f1ff | ||
|
|
69d8ff1034 | ||
|
|
9a334101ca | ||
|
|
ee53b3d9c4 | ||
|
|
01a5ad5302 | ||
|
|
f12f0e1f84 | ||
|
|
6aefcdbed8 | ||
|
|
3d22a2ef65 | ||
|
|
a33c6dbf1a | ||
|
|
2b7c779e14 | ||
|
|
0e36f7c0b3 | ||
|
|
228a309545 | ||
|
|
b15ad8eb5e | ||
|
|
a525735b0b | ||
|
|
6a58af8dfb | ||
|
|
0089e63832 | ||
|
|
d4f00eb89f | ||
|
|
d15e6e395b | ||
|
|
39eb4bef07 | ||
|
|
03de576d8c | ||
|
|
216b57a4c7 | ||
|
|
5383d2d40e | ||
|
|
1d7a2013e3 | ||
|
|
24d2908cca | ||
|
|
330845a387 | ||
|
|
bbedd01a72 | ||
|
|
2b07061c18 | ||
|
|
01a3c81359 | ||
|
|
96e275efa6 | ||
|
|
12eb6c73c3 | ||
|
|
4166eddd28 | ||
|
|
6ea1592c19 | ||
|
|
f8799a073c | ||
|
|
0eb83fc930 | ||
|
|
a5dd3946f8 | ||
|
|
2732df24af | ||
|
|
f0ae27a50b | ||
|
|
c5037ea397 | ||
|
|
695aaedfba | ||
|
|
4f71d874bd | ||
|
|
c69729fadd | ||
|
|
64d04996af | ||
|
|
8eea510583 | ||
|
|
04c2b246f6 | ||
|
|
a46eb176d7 | ||
|
|
2bea88f1a3 | ||
|
|
06617de984 | ||
|
|
6459d7c024 | ||
|
|
bd4af8039f | ||
|
|
8aaa22cea3 | ||
|
|
7d902682f8 | ||
|
|
a81be99100 | ||
|
|
b6dfe93d2c | ||
|
|
35aa022984 | ||
|
|
dfe2d4cc37 | ||
|
|
1d16502d32 | ||
|
|
25c88187a3 | ||
|
|
ac04e846fa | ||
|
|
29985e2118 | ||
|
|
3b74dfd49a | ||
|
|
6abcc49ed4 | ||
|
|
f16e1cd7a6 | ||
|
|
4341b8a24b | ||
|
|
a89e11d132 | ||
|
|
bc91a8cff2 | ||
|
|
8040010b98 | ||
|
|
b39f15c798 | ||
|
|
c3cf49c63e | ||
|
|
899bd7e316 | ||
|
|
890727d495 | ||
|
|
8a8e69596a | ||
|
|
58fcc92e4d | ||
|
|
0cd795f57a | ||
|
|
4830be6388 | ||
|
|
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 | ||
|
|
59d9674aeb | ||
|
|
c4e79e46cf | ||
|
|
36ed5400be | ||
|
|
6378c69764 | ||
|
|
b44db20938 | ||
|
|
61cd7b42d4 | ||
|
|
77d64796e0 | ||
|
|
a4001c21b1 | ||
|
|
4fadba27dc | ||
|
|
9e1d01774f | ||
|
|
7eab4dd5b1 | ||
|
|
be4f0a2a90 | ||
|
|
0e6d380b7e | ||
|
|
1a73f7bef3 | ||
|
|
c45c17748e | ||
|
|
a518e7b210 | ||
|
|
57ad7ae1a3 | ||
|
|
e2dae1c4ab | ||
|
|
998d97b669 | ||
|
|
453d95a460 | ||
|
|
0d00f91b31 | ||
|
|
d3b87e8984 | ||
|
|
fcaad5c6cc | ||
|
|
8ca741792a | ||
|
|
0f687a7e34 | ||
|
|
736995c35b | ||
|
|
4ae548cc5b | ||
|
|
d55545849a | ||
|
|
942868f17f | ||
|
|
8a57bf52f9 | ||
|
|
260d87c97e | ||
|
|
cb1bac25a3 | ||
|
|
ef20ef7014 | ||
|
|
b88cad7b80 | ||
|
|
22ba44c271 | ||
|
|
ae2cc7ad9b | ||
|
|
3383e86064 | ||
|
|
a0ef3af155 | ||
|
|
c880f32d33 | ||
|
|
e8fcab4c6b | ||
|
|
7b20b68b6a | ||
|
|
974f264272 | ||
|
|
bcbb2b5139 | ||
|
|
1c6a795061 | ||
|
|
36246c2945 | ||
|
|
7af54f600f | ||
|
|
e09aaa2dc7 | ||
|
|
e3178d7579 | ||
|
|
b59dfbc3fd | ||
|
|
0f21688b3c | ||
|
|
801e30c1dc | ||
|
|
7049175827 | ||
|
|
f27a5a135b | ||
|
|
82ad9e249a | ||
|
|
fcff711a04 | ||
|
|
0c78b8d868 | ||
|
|
b6b54164a8 | ||
|
|
983daddebc | ||
|
|
605db9340c | ||
|
|
36ea04bd70 | ||
|
|
6f691e45d8 | ||
|
|
2c6558874d | ||
|
|
d60b432f0c | ||
|
|
adb9849650 | ||
|
|
48abbbf83e | ||
|
|
9e5361338c | ||
|
|
c5ecd71c6e | ||
|
|
b09bbba21b | ||
|
|
2db543573c | ||
|
|
cadd4df5d0 | ||
|
|
427de4b42f | ||
|
|
79cb82ea24 | ||
|
|
1b472c4ad7 | ||
|
|
21ab4950c8 | ||
|
|
1b2977d591 | ||
|
|
c54cf0bca2 | ||
|
|
935c990fe4 | ||
|
|
44f94f8398 | ||
|
|
8380cb1084 | ||
|
|
83fc4f46b2 | ||
|
|
9a97134a37 | ||
|
|
912010ed70 | ||
|
|
09b0bd8b09 | ||
|
|
c0dece8936 | ||
|
|
571b610417 | ||
|
|
658339047c | ||
|
|
13120712b1 | ||
|
|
10e1fad7f3 | ||
|
|
d7629763ef | ||
|
|
94ee0fb058 | ||
|
|
d4e8ce9c89 | ||
|
|
707e241f25 | ||
|
|
58fa94e8b8 | ||
|
|
72d307503e | ||
|
|
d9fd9163e4 | ||
|
|
21807c3a66 | ||
|
|
f499d2d8a9 | ||
|
|
0f9958bb26 | ||
|
|
8e66a9880c | ||
|
|
38e112fb06 | ||
|
|
bb0e48cd41 | ||
|
|
fd0edd93ea | ||
|
|
6359696bde | ||
|
|
1fee392989 | ||
|
|
dfdced9644 | ||
|
|
e50c1c374a | ||
|
|
dbcd051aae | ||
|
|
96571bb999 | ||
|
|
0620f6b6e3 | ||
|
|
ae163167b6 | ||
|
|
ba0bb32b5f | ||
|
|
b15e789b0b | ||
|
|
8c7afa2e7a | ||
|
|
87e30b9edf | ||
|
|
887ebe6f2f | ||
|
|
099581dddc | ||
|
|
3e84890765 | ||
|
|
d2fb0791d9 | ||
|
|
a2f22007cf | ||
|
|
d4dd531ac4 | ||
|
|
f7790c46d2 | ||
|
|
775e31c5e9 | ||
|
|
e1ea210fe9 | ||
|
|
d9022a0383 | ||
|
|
0b0c1405ea | ||
|
|
eb8d1626ea | ||
|
|
db94cd32d5 | ||
|
|
67c7ef37e6 | ||
|
|
2e22f4ea67 | ||
|
|
b985c2c784 | ||
|
|
dc242420f8 | ||
|
|
0656e3f536 | ||
|
|
cfffcda068 | ||
|
|
187885fdb9 | ||
|
|
f67b60dd82 | ||
|
|
92a2421a9b |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -20,7 +20,7 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[L]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
@@ -263,11 +263,17 @@ src/Acme.BookStore.Blazor.Server.Tiered/Logs/*
|
||||
|
||||
# Use abp install-libs to restore.
|
||||
**/wwwroot/libs/*
|
||||
public
|
||||
dist
|
||||
.vscode
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.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.Production.json
|
||||
/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/appsettings.Development.json
|
||||
database_backup
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Staging.json
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/
|
||||
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
|
||||
|
||||
package-lock.json
|
||||
|
||||
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 .
|
||||
|
||||
225
README-en.md
Normal file
225
README-en.md
Normal file
@@ -0,0 +1,225 @@
|
||||
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi-Framework</h1>
|
||||
<h4 align="center">A .NET 8 Web open-source Asp.NetCore framework focused on user experience.</h4>
|
||||
<h5 align="center">Supports Native/Abp.vNext/Furion/Ruoyi/Pure</h5>
|
||||
<h2 align="center">A comprehensive solution that ultimately becomes a wheel</h2>
|
||||
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
|
||||
English | [简体中文](README.md)
|
||||
****
|
||||
## 🍍 Introduction:
|
||||
YiFramework is a DDD (Domain-Driven Design) backend open-source framework based on .Net8, Abp.vNext, and SqlSugar.
|
||||
|
||||
Who says ABP is complex? Who says DDD is difficult?`Breaking conventions, simplifying complexity.`,Newcomer-friendly and one of the best approaches for project extensions.
|
||||
|
||||
Modular design allows for the independent inclusion or exclusion of components based on business needs. It is an all-encompassing framework where you may gain unique insights.
|
||||
|
||||
A Comprehensive Solution, Ultimately Just Another Wheel.
|
||||
|
||||
(Frequent updates, feel free to watch for continuous updates.)
|
||||
|
||||
— This is not just a program; it is also a work of art, focused on artistic development!
|
||||
|
||||
> Core Features: Simple and easy to use, the framework is not referenced in a packaged form, but is provided directly with the project alongside the source code. It offers maximum freedom and complies with the MIT license, allowing for unrestricted modifications (please indicate the source).
|
||||
|
||||
**Branch Directory:**
|
||||
|
||||
- Branch **Abp**: Based on the Abp.vNext branch, DDD (Domain-Driven Design) simplifies the essence of development, providing support for multiple frontends from one backend.
|
||||
|
||||
- Yi.Abp.Net8:Backend
|
||||
- Yi.Bbs.Vue3:Bbs Community - Frontend
|
||||
- Yi.Doc.Md: Open Source Documentation Tutorial
|
||||
- Yi.Pure.Vue3:Pure TS Backend Frontend
|
||||
- Yi.RuoYi.Vue3:RuoYi JS Backend Frontend
|
||||
|
||||
****
|
||||
## 🍉 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:
|
||||
|
||||
Let's get straight to the point and provide the link.
|
||||
|
||||
YiCommunity official website URL.(Bbs):[ccnetcore.com](https://ccnetcore.com) (Now live, welcome to join!)
|
||||
|
||||
Rbac:https://ccnetcore.com:1000 (userName cc\password 123456)
|
||||
|
||||
Pure:https://ccnetcore.com:1001 (userNamecc\password 123456)
|
||||
|
||||
## 🍏 Support:
|
||||
|
||||
- [x] Fully supports monolithic application architecture
|
||||
- [x] Fully supports distributed application architecture
|
||||
- [x] Fully supports microservices architecture
|
||||
|
||||
****
|
||||
## 🍇 Explosive Detail Yi Framework Tutorial Navigation:
|
||||
|
||||
1. [Framework Quick Start Guide](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(Completed)
|
||||
2. [Framework Functionality Module Tutorials](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(Completed)
|
||||
3. [Practical Development Exercises](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(Completed)
|
||||
4. [Chengzi Ops CI/CD Tutorial](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(Completed)
|
||||
5. [Version Update Log](https://ccnetcore.com/article/e9e69a38-ce1e-06f5-7944-3a0fdc942ef3)(Completed)
|
||||
|
||||
****
|
||||
## 🍓 Its philosophy:
|
||||
Who says ABP is complicated? Who says DDD is difficult? Break the norm, simplify complexity, and serve as one of the best ways for newcomers and project second development.
|
||||
|
||||
> For every hundred people, there are a hundred different interpretations of DDD. The YiFramework may not strict adherence to DDD principles, but it is built on the shoulders of giants, distilled from numerous projects to craft a best practice.
|
||||
|
||||
Effortlessly achieve rapid development; typically, simplicity and elegance are hard to reconcile. The YiFramework does not solely pursue extreme decoupling but considers user experience and ease of use.
|
||||
|
||||
A user-oriented rapid development backend framework.
|
||||
|
||||
> Once you truly get hands-on, you'll understand this: extreme simplicity is also a form of elegance.
|
||||
****
|
||||
|
||||
## 🍍 Features
|
||||
- A user-oriented backend framework that is easy to use, suitable for small, medium, and enterprise-level projects.
|
||||
- The project comes with the source code directly embedded, without packaging, making it ideal for secondary development and modification.
|
||||
- Includes a large number of reusable modules for common scenarios.
|
||||
- Elegantly supports distributed and microservices architectures.
|
||||
- And more…
|
||||
|
||||
## 🥭 Core Technologies
|
||||
#### Backend
|
||||
C# Asp.NetCore 8.0
|
||||
- [x] Dynamic API: Abp.vNext
|
||||
- [x] Authentication and Authorization: Jwt
|
||||
- [x] Logging: Serilog
|
||||
- [x] Modularization: Abp.vNext
|
||||
- [x] Dependency Injection: Autofac
|
||||
- [x] Object Mapping: Mapster
|
||||
- [x] ORM: SqlsugarCore
|
||||
- [x] Multi-tenancy: Abp.vNext
|
||||
- [x] Background Tasks: Quartz.Net
|
||||
- [x] Local Caching: Abp.vNext
|
||||
- [x] Distributed Caching: Abp.vNext
|
||||
- [x] Event Bus: Abp.vNext
|
||||
|
||||
#### Frontend
|
||||
js Vue3
|
||||
- [x] Asynchronous Requests: axios
|
||||
- [x] Charts: echarts
|
||||
- [x] UI: element-plus
|
||||
- [x] State Management: pinia
|
||||
- [x] Routing: vue-router
|
||||
- [x] Bundling: vite
|
||||
|
||||
#### DevOps
|
||||
- [x] Deployment: nginx
|
||||
- [x] CICD: gitlab+Jenkins
|
||||
- [x] Docker: harbor
|
||||
|
||||
#### 🍉 Demo:
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/101.png"/></td>
|
||||
<td><img src="readme/102.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/103.png"/></td>
|
||||
<td><img src="readme/104.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/201.png"/></td>
|
||||
<td><img src="readme/202.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/203.png"/></td>
|
||||
<td><img src="readme/204.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/205.png"/></td>
|
||||
<td><img src="readme/206.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/1.png"/></td>
|
||||
<td><img src="readme/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/3.png"/></td>
|
||||
<td><img src="readme/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/3.png"/></td>
|
||||
<td><img src="readme/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/5.png"/></td>
|
||||
<td><img src="readme/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/7.png"/></td>
|
||||
<td><img src="readme/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/9.png"/></td>
|
||||
<td><img src="readme/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/11.png"/></td>
|
||||
<td><img src="readme/12.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 🌶 Thank you:
|
||||
|
||||
[橙子]https://ccnetcore.com
|
||||
|
||||
[XWen]https://gitee.com/on-wensil
|
||||
|
||||
[朝夕教育]https://www.zhaoxiedu.net
|
||||
|
||||
[Sqlsugar老杰哥]https://www.donet5.com/Home/Doc
|
||||
|
||||
[车神]微信公众号搜索Dotnet技术进阶
|
||||
|
||||
[RuYiAdmin如意老兄]https://gitee.com/pang-mingjun/RuYiAdmin
|
||||
|
||||
[ZrAdminNetCore字母老哥]https://gitee.com/izory/ZrAdminNetCore
|
||||
|
||||
[Admin.NET]https://gitee.com/zuohuaijun/Admin.NET
|
||||
|
||||
[Furion百小僧]https://furion.baiqian.ltd/
|
||||
|
||||
****
|
||||
## 🌽 Contact Us:
|
||||
|
||||
Author's QQ:`454313500`
|
||||
|
||||
QQ group chat:官方一群(Full)、官方二群(Full)、官方三群:`786308927`(Full)、官方四群:`498310311`(Full)、官方五群:`981136525`(New)
|
||||
|
||||
WeChat Group Chat:官方微信一群(Full)、官方微信二群
|
||||
|
||||
WeChat Community: Add the author's WeChat chengzilaoge520 (橙子老哥520),Note: Join the group.
|
||||
|
||||
Contact the author, everyone here is a consultant.
|
||||
|
||||
Official website message area:[ccnetcore.com](https://ccnetcore.com)
|
||||
|
||||
****
|
||||
## 🍄 FQA:
|
||||
|
||||
Visit the official website to view the message board.
|
||||
|
||||
[the message board](https://ccnetcore.com/discuss/1641030787056930818)
|
||||
99
README.md
99
README.md
@@ -1,6 +1,6 @@
|
||||
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi框架</h1>
|
||||
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
|
||||
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端后台接入Ruoyi Vue3.0</h5>
|
||||
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端接入Ruoyi/Pure Vue</h5>
|
||||
<h2 align="center">集大成者,终究轮子</h2>
|
||||
|
||||
[](https://gitee.com/ccnetcore/Yi)
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
[English](README-en.md) | 简体中文
|
||||
****
|
||||
## :tw-1f34e: 简介:
|
||||
## 🍍 简介:
|
||||
YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端开源框架
|
||||
|
||||
谁说Abp复杂?谁说DDD难?`打破常规,化繁为简`,新人入门,项目二开,最佳方式之一
|
||||
@@ -22,7 +22,7 @@ YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端
|
||||
|
||||
Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
|
||||
与Sqlsugar理念一致,以用户体验出发。
|
||||
适合.Net8学习、Sqlsugar学习 、项目二次开发。
|
||||
全生态拥抱AI,接入AI,100%代码经过AI洗礼
|
||||
集大成者,终究轮子
|
||||
|
||||
(更新频繁,可watching持续关注。)
|
||||
@@ -31,44 +31,59 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
|
||||
|
||||
> 核心特点:简单好用,框架不以打包形式引用,而是直接以项目附带源码给出,自由度拉满,遵循Mit协议,允许随意修改(请注明来源即可)
|
||||
|
||||
**分支:**
|
||||
**分支目录:**
|
||||
|
||||
- (推荐) **Abp**: 基于Abp.vNext分支,DDD领域驱动设计,回归开发本质,极度简单,用起来贼爽
|
||||
- 分支**Abp**: 基于Abp.vNext分支,DDD领域驱动设计,回归开发本质,极度简单,一个后台支持以下多个前端
|
||||
|
||||
- **Furion**: 基于Furion分支
|
||||
- Yi.Abp.Net8:后端
|
||||
- Yi.Bbs.Vue3:Bbs社区 前端
|
||||
- Yi.Doc.Md: 开源文档教程
|
||||
- Yi.Pure.Vue3:Pure ts后台前端
|
||||
- Yi.RuoYi.Vue3:RuoYi js后台前端
|
||||
|
||||
****
|
||||
## 🍉 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操作,我们更希望你能通过此种方式二开构建属于自己的镜像
|
||||
|
||||
****
|
||||
|
||||
## :tw-1f350: 官网及演示地址:
|
||||
## 🍊 官网及演示地址:
|
||||
|
||||
废话少说直接上地址
|
||||
|
||||
Yi社区官网网址:[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
|
||||
Yi社区官网网址(Bbs社区正式):[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
|
||||
|
||||
Rbac后台管理系统:已上线,暂不提供演示地址,可本地部署访问
|
||||
Rbac后台演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
App移动端系统:已上线,暂不提供演示地址,可本地部署访问
|
||||
Pure后台演示地址:https://ccnetcore.com:1001 (用户cc、密码123456)
|
||||
|
||||
Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
|
||||
## :tw-1f351: 支持:
|
||||
## 🍏 支持:
|
||||
|
||||
- [x] 完全支持单体应用架构
|
||||
- [x] 完全支持分布式应用架构
|
||||
- [x] 完全支持微服务架构
|
||||
|
||||
****
|
||||
## :tw-1f352: 详细到爆炸的Yi框架教程导航:
|
||||
|
||||
## 🍇 详细到爆炸的Yi框架教程导航:
|
||||
|
||||
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)(已完成)
|
||||
2. [框架功能模块教程](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(已完成)
|
||||
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)
|
||||
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(已完成)
|
||||
4. [橙子运维CICD教程](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(已完成)
|
||||
5. [版本更新日志](https://ccnetcore.com/article/e9e69a38-ce1e-06f5-7944-3a0fdc942ef3)(已完成)
|
||||
|
||||
****
|
||||
## :tw-1f353: 它的理念:
|
||||
## 🍓 它的理念:
|
||||
谁说Abp复杂?谁说DDD难?打破常规,化繁为简,新人入门,项目二开,最佳方式之一
|
||||
|
||||
> 一百个人,就有一百种DDD,Yi框架不一定是极度严格的DDD,而是站在巨人的肩膀上,经过极多项目的提炼,摸索出一种最佳实践
|
||||
@@ -78,17 +93,17 @@ Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
> 一个面向用户的快速开发后端框架
|
||||
|
||||
在真正的使用这,你会明白这一点,极致的简单,也是优雅的一种体现。
|
||||
在真正的使用过,你会明白这一点,极致的简单,也是优雅的一种体现。
|
||||
****
|
||||
|
||||
## :tw-1f354: 特点
|
||||
## 🍍 特点
|
||||
- 面向用户的后端框架,使用简单,适合小型、中型、企业级项目
|
||||
- 项目直接内置源码,不打包,非常适合进行二开改造
|
||||
- 内置包含大量通用场景模块
|
||||
- 优雅支持分布式及微服务架构
|
||||
- 等等
|
||||
|
||||
## :tw-1f340: 基础设施简介
|
||||
## 🍍 基础设施简介
|
||||
|
||||
以下全部功能可直接使用:
|
||||
|
||||
@@ -96,14 +111,14 @@ Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
||||
|
||||
- [SqlSugar官网](https://www.donet5.com/home/doc)
|
||||
|
||||
## :tw-1f341: 内置模块简介
|
||||
- Rbac权限管理系统(已上线)
|
||||
## 🍅 内置模块简介
|
||||
- Rbac权限管理系统(已上线)(支持pure、ruoyi前端)
|
||||
- Bbs论坛社区系统(已上线)
|
||||
|
||||
> 重复的东西,无需再写一遍,这也是优雅的体现之一
|
||||
|
||||
****
|
||||
## :tw-1f31e: 核心技术
|
||||
## 🥭 核心技术
|
||||
#### 后端
|
||||
C# Asp.NetCore 8.0
|
||||
- [x] 动态Api:Abp.vNext
|
||||
@@ -120,7 +135,7 @@ C# Asp.NetCore 8.0
|
||||
- [x] 事件总线:Abp.vNext
|
||||
|
||||
#### 前端
|
||||
js Vue3.2
|
||||
js Vue3
|
||||
- [x] 异步请求:axios
|
||||
- [x] 图表:echarts
|
||||
- [x] ui:element-plus
|
||||
@@ -135,9 +150,9 @@ js Vue3.2
|
||||
|
||||
|
||||
****
|
||||
## :tw-1f366: 业务支持模块:
|
||||
## 🍌 业务支持模块:
|
||||
|
||||
#### :tw-1f42f: RABC权限管理系统(持续更新)
|
||||
#### 🍒 RABC权限管理系统(持续更新)
|
||||
(采用ruoyi前端)
|
||||
- 用户管理
|
||||
- 角色管理
|
||||
@@ -152,9 +167,8 @@ js Vue3.2
|
||||
- 定时任务
|
||||
- 缓存列表
|
||||
- 服务监控
|
||||
- WebFirst代码生成工具
|
||||
|
||||
#### :tw-1f431: BBS社区论坛系统(持续更新)
|
||||
#### 🍐 BBS社区论坛系统(持续更新)
|
||||
(采用vue3前端)
|
||||
- 文章功能
|
||||
- 板块功能
|
||||
@@ -163,7 +177,7 @@ js Vue3.2
|
||||
- 授权中心
|
||||
- 权限管理
|
||||
|
||||
#### :star: 演示截图:
|
||||
#### 🍉 演示截图:
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/101.png"/></td>
|
||||
@@ -174,7 +188,22 @@ js Vue3.2
|
||||
<td><img src="readme/104.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="readme/201.png"/></td>
|
||||
<td><img src="readme/202.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/203.png"/></td>
|
||||
<td><img src="readme/204.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="readme/205.png"/></td>
|
||||
<td><img src="readme/206.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -207,7 +236,7 @@ js Vue3.2
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## :tw-1f44f: 感谢:
|
||||
## 🌶 感谢:
|
||||
|
||||
[橙子]https://ccnetcore.com
|
||||
|
||||
@@ -228,11 +257,13 @@ js Vue3.2
|
||||
[Furion百小僧]https://furion.baiqian.ltd/
|
||||
|
||||
****
|
||||
## :tw-1f438: 联系我们:
|
||||
## 🌽 联系我们:
|
||||
|
||||
作者QQ:`454313500`,2029年之前作者24小时在线,时刻保持活跃更新。
|
||||
|
||||
QQ交流群:官方一群(已满)、官方二群(已满)、官方三群:`786308927`(已满)、官方四群:`498310311`(基本已满)、官方五群:`981136525`(新群)
|
||||
QQ交流群:官方一群(已满)、官方二群(已满)、官方三群:`786308927`(已满)、官方四群:`498310311`(已满)、官方五群:`981136525`
|
||||
|
||||
微信交流群:官方微信一群(已满)、官方微信二群(已满)、官方微信三群
|
||||
|
||||
微信交流群:加作者微信 chengzilaoge520 (橙子老哥520),备注拉群
|
||||
|
||||
@@ -241,7 +272,7 @@ QQ交流群:官方一群(已满)、官方二群(已满)、官方三群
|
||||
官方网址留言区:[ccnetcore.com](https://ccnetcore.com)
|
||||
|
||||
****
|
||||
## :tw-1f41e: FQA:
|
||||
## 🍄 FQA:
|
||||
|
||||
前往官网查看留言区
|
||||
|
||||
|
||||
@@ -27,4 +27,7 @@ README.md
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.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"]
|
||||
@@ -35,6 +35,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
usings.props = usings.props
|
||||
version.props = version.props
|
||||
publish.bat = publish.bat
|
||||
publish_Demo.bat = publish_Demo.bat
|
||||
Dockerfile = Dockerfile
|
||||
DockerfileFast = DockerfileFast
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SqlSugarCore.Abstractions", "framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj", "{FD6D6860-3753-4747-8A26-977E4A3001F9}"
|
||||
@@ -79,20 +82,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.S
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AspNetCore.Authentication.OAuth", "framework\Yi.Framework.AspNetCore.Authentication.OAuth\Yi.Framework.AspNetCore.Authentication.OAuth.csproj", "{791AC2FA-50D3-4408-8D68-31DA72F608BE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{01300F0F-686E-47B3-821D-12424177867B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Web", "sample\Acme.BookStore.Web\Acme.BookStore.Web.csproj", "{576DBC97-4E5D-4444-B65C-F41649A5F8E0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain.Shared", "sample\Acme.BookStore.Domain.Shared\Acme.BookStore.Domain.Shared.csproj", "{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain", "sample\Acme.BookStore.Domain\Acme.BookStore.Domain.csproj", "{B615847F-8568-41D1-8B7E-63D61AE69F3D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application.Contracts", "sample\Acme.BookStore.Application.Contracts\Acme.BookStore.Application.Contracts.csproj", "{20827DB5-5CDE-491A-82E8-3CAB82618C1E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application", "sample\Acme.BookStore.Application\Acme.BookStore.Application.csproj", "{320273B6-7AE3-42DA-9675-D9AD4928A289}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}"
|
||||
@@ -169,6 +158,46 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Tool.HttpApi.Client"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.SettingManagement.Application", "module\setting-management\Yi.Framework.SettingManagement.Application\Yi.Framework.SettingManagement.Application.csproj", "{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "digital-collectibles", "digital-collectibles", "{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application\Yi.Framework.DigitalCollectibles.Application.csproj", "{236B88D4-F018-4A5F-A506-7458F2308C70}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application.Contracts", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj", "{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain\Yi.Framework.DigitalCollectibles.Domain.csproj", "{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain.Shared", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain.Shared\Yi.Framework.DigitalCollectibles.Domain.Shared.csproj", "{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.SqlSugarCore", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.SqlSugarCore\Yi.Framework.DigitalCollectibles.SqlSugarCore.csproj", "{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.WeChat.MiniProgram", "framework\Yi.Framework.WeChat.MiniProgram\Yi.Framework.WeChat.MiniProgram.csproj", "{81CEA2ED-917B-41D8-BE0D-39A785B050C0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.BackgroundWorkers.Hangfire", "framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj", "{862CA181-BEE6-4870-82D2-B662E527ED8C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai-stock", "ai-stock", "{DB46873F-981A-43D8-91B0-D464CCB65943}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application", "module\ai-stock\Yi.Framework.Stock.Application\Yi.Framework.Stock.Application.csproj", "{B79CE23C-10F8-48A5-A039-5940A188CF5A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application.Contracts", "module\ai-stock\Yi.Framework.Stock.Application.Contracts\Yi.Framework.Stock.Application.Contracts.csproj", "{846B781A-B77E-4F86-A31F-0B5B57AB0775}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain", "module\ai-stock\Yi.Framework.Stock.Domain\Yi.Framework.Stock.Domain.csproj", "{162821E4-8FE0-4A68-B3C0-49BD6596446F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain.Shared", "module\ai-stock\Yi.Framework.Stock.Domain.Shared\Yi.Framework.Stock.Domain.Shared.csproj", "{10273544-715D-4BB3-893C-6F010D947BDD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.SqlSugarCore", "module\ai-stock\Yi.Framework.Stock.SqlSugarCore\Yi.Framework.Stock.SqlSugarCore.csproj", "{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai-hub", "ai-hub", "{7AD5DBAE-44F9-474B-8F7B-837EDE908934}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Application", "module\ai-hub\Yi.Framework.AiHub.Application\Yi.Framework.AiHub.Application.csproj", "{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Application.Contracts", "module\ai-hub\Yi.Framework.AiHub.Application.Contracts\Yi.Framework.AiHub.Application.Contracts.csproj", "{123D1C81-D667-4060-8E85-FFE7FB4584AD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Domain", "module\ai-hub\Yi.Framework.AiHub.Domain\Yi.Framework.AiHub.Domain.csproj", "{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Domain.Shared", "module\ai-hub\Yi.Framework.AiHub.Domain.Shared\Yi.Framework.AiHub.Domain.Shared.csproj", "{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.SqlSugarCore", "module\ai-hub\Yi.Framework.AiHub.SqlSugarCore\Yi.Framework.AiHub.SqlSugarCore.csproj", "{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -275,30 +304,6 @@ Global
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -427,6 +432,74 @@ Global
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{10273544-715D-4BB3-893C-6F010D947BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{10273544-715D-4BB3-893C-6F010D947BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{10273544-715D-4BB3-893C-6F010D947BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{10273544-715D-4BB3-893C-6F010D947BDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -460,12 +533,6 @@ Global
|
||||
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
|
||||
{791AC2FA-50D3-4408-8D68-31DA72F608BE} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
{576DBC97-4E5D-4444-B65C-F41649A5F8E0} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{B615847F-8568-41D1-8B7E-63D61AE69F3D} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{20827DB5-5CDE-491A-82E8-3CAB82618C1E} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7} = {01300F0F-686E-47B3-821D-12424177867B}
|
||||
{68627BC2-F049-4C69-AD17-81DF9478E8CE} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
|
||||
{499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
|
||||
@@ -502,6 +569,26 @@ Global
|
||||
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||
{C8F97775-D903-4365-A4FF-3DA97E318CD2} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
|
||||
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
|
||||
{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{236B88D4-F018-4A5F-A506-7458F2308C70} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
|
||||
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
|
||||
{DB46873F-981A-43D8-91B0-D464CCB65943} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{B79CE23C-10F8-48A5-A039-5940A188CF5A} = {DB46873F-981A-43D8-91B0-D464CCB65943}
|
||||
{846B781A-B77E-4F86-A31F-0B5B57AB0775} = {DB46873F-981A-43D8-91B0-D464CCB65943}
|
||||
{162821E4-8FE0-4A68-B3C0-49BD6596446F} = {DB46873F-981A-43D8-91B0-D464CCB65943}
|
||||
{10273544-715D-4BB3-893C-6F010D947BDD} = {DB46873F-981A-43D8-91B0-D464CCB65943}
|
||||
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983} = {DB46873F-981A-43D8-91B0-D464CCB65943}
|
||||
{7AD5DBAE-44F9-474B-8F7B-837EDE908934} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
|
||||
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
|
||||
{123D1C81-D667-4060-8E85-FFE7FB4584AD} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
|
||||
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
|
||||
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
|
||||
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Authentication;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// 可刷新的鉴权提供者
|
||||
/// </summary>
|
||||
public class RefreshAuthenticationHandlerProvider : IRefreshAuthenticationHandlerProvider
|
||||
{
|
||||
private Dictionary<string, IAuthenticationHandler> _handlerMap =
|
||||
new Dictionary<string, IAuthenticationHandler>((IEqualityComparer<string>)StringComparer.Ordinal);
|
||||
|
||||
/// <summary>Constructor.</summary>
|
||||
/// <param name="schemes">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider" />.</param>
|
||||
public RefreshAuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
|
||||
{
|
||||
this.Schemes = schemes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider" />.
|
||||
/// </summary>
|
||||
public IAuthenticationSchemeProvider Schemes { get; }
|
||||
|
||||
/// <summary>Returns the handler instance that will be used.</summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="authenticationScheme">The name of the authentication scheme being handled.</param>
|
||||
/// <returns>The handler instance.</returns>
|
||||
public async Task<IAuthenticationHandler?> GetHandlerAsync(
|
||||
HttpContext context,
|
||||
string authenticationScheme)
|
||||
{
|
||||
IAuthenticationHandler handlerAsync;
|
||||
if (this._handlerMap.TryGetValue(authenticationScheme, out handlerAsync))
|
||||
return handlerAsync;
|
||||
AuthenticationScheme schemeAsync = await this.Schemes.GetSchemeAsync(authenticationScheme);
|
||||
if (schemeAsync == null)
|
||||
return (IAuthenticationHandler)null;
|
||||
|
||||
if ((context.RequestServices.GetService(schemeAsync.HandlerType) ??
|
||||
ActivatorUtilities.CreateInstance(context.RequestServices, schemeAsync.HandlerType)) is
|
||||
IAuthenticationHandler handler)
|
||||
{
|
||||
handlerAsync = handler;
|
||||
await handler.InitializeAsync(schemeAsync, context);
|
||||
this._handlerMap[authenticationScheme] = handler;
|
||||
}
|
||||
|
||||
return handlerAsync;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 刷新鉴权
|
||||
/// </summary>
|
||||
public void RefreshAuthentication()
|
||||
{
|
||||
_handlerMap = new Dictionary<string, IAuthenticationHandler>((IEqualityComparer<string>)StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,23 @@ using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供API信息处理的应用程序构建器扩展方法
|
||||
/// </summary>
|
||||
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>();
|
||||
return app;
|
||||
|
||||
// 添加API信息处理中间件到请求管道
|
||||
builder.UseMiddleware<ApiInfoMiddleware>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,49 +5,101 @@ using Volo.Abp.AspNetCore.Mvc;
|
||||
|
||||
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;
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
if (app == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
this.Name = name;
|
||||
Url = url ?? throw new ArgumentNullException(nameof(url));
|
||||
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.Net.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Json;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
using static System.Net.WebRequestMethods;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// API响应信息处理中间件
|
||||
/// 主要用于处理特定文件类型的响应头信息
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
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)
|
||||
{
|
||||
context.Response.OnStarting([DebuggerStepThrough] () =>
|
||||
// 在响应开始时处理文件下载相关的响应头
|
||||
context.Response.OnStarting(() =>
|
||||
{
|
||||
if (context.Response.StatusCode == StatusCodes.Status200OK
|
||||
&& context.Response.Headers["Content-Type"].ToString() == "application/vnd.ms-excel")
|
||||
{
|
||||
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");
|
||||
}
|
||||
HandleFileDownloadResponse(context);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// 继续处理管道中的下一个中间件
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,157 +1,229 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.AspNetCore.Mvc.Conventions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Swagger生成器扩展类
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();
|
||||
|
||||
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
|
||||
|
||||
// 获取MVC配置选项
|
||||
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
|
||||
|
||||
// 获取并去重远程服务名称
|
||||
var remoteServiceSettings = mvcOptions.ConventionalControllers
|
||||
.ConventionalControllerSettings
|
||||
.DistinctBy(x => x.RemoteServiceName);
|
||||
|
||||
services.AddAbpSwaggerGen(
|
||||
options =>
|
||||
{
|
||||
if (action is not null)
|
||||
options =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
|
||||
/// Swagger文档枚举字段显示过滤器
|
||||
/// </summary>
|
||||
public class EnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现接口
|
||||
/// 应用枚举架构过滤器
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="context"></param>
|
||||
|
||||
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
||||
/// <param name="schema">OpenAPI架构</param>
|
||||
/// <param name="context">架构过滤器上下文</param>
|
||||
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||
{
|
||||
if (context.Type.IsEnum)
|
||||
{
|
||||
model.Enum.Clear();
|
||||
model.Type = "string";
|
||||
model.Format = null;
|
||||
if (!context.Type.IsEnum) return;
|
||||
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
Enum.GetNames(context.Type)
|
||||
.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.Enum.Clear();
|
||||
schema.Type = "string";
|
||||
schema.Format = null;
|
||||
|
||||
var enumDescriptions = new StringBuilder();
|
||||
foreach (var enumName in Enum.GetNames(context.Type))
|
||||
{
|
||||
var enumValue = (Enum)Enum.Parse(context.Type, enumName);
|
||||
var description = GetEnumDescription(enumValue);
|
||||
var enumIntValue = Convert.ToInt64(enumValue);
|
||||
|
||||
schema.Enum.Add(new OpenApiString(enumName));
|
||||
enumDescriptions.AppendLine(
|
||||
$"【枚举:{enumName}{(description is null ? string.Empty : $"({description})")}={enumIntValue}】");
|
||||
}
|
||||
schema.Description = enumDescriptions.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举描述特性值
|
||||
/// </summary>
|
||||
private static string? GetEnumDescription(Enum value)
|
||||
{
|
||||
var fieldInfo = value.GetType().GetField(value.ToString());
|
||||
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
return attributes.Length > 0 ? attributes[0].Description : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class AddRequiredHeaderParameter : IOperationFilter
|
||||
/// <summary>
|
||||
/// 租户头部参数过滤器
|
||||
/// </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)
|
||||
{
|
||||
if (operation.Parameters == null)
|
||||
operation.Parameters = new List<OpenApiParameter>();
|
||||
operation.Parameters ??= new List<OpenApiParameter>();
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = HeaderKey,
|
||||
Name = TenantHeaderKey,
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
AllowEmptyValue = true,
|
||||
Description="租户id或者租户名称(可空为默认租户)"
|
||||
Description = "租户ID或租户名称(留空表示默认租户)"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,65 +9,129 @@ using Volo.Abp.Reflection;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义路由构建器,用于生成API路由规则
|
||||
/// </summary>
|
||||
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
|
||||
[ExposeServices(typeof(IConventionalRouteBuilder))]
|
||||
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(
|
||||
string rootPath,
|
||||
string controllerName,
|
||||
ActionModel action,
|
||||
string httpMethod,
|
||||
[CanBeNull] ConventionalControllerSetting configuration)
|
||||
string rootPath,
|
||||
string controllerName,
|
||||
ActionModel action,
|
||||
string httpMethod,
|
||||
[CanBeNull] ConventionalControllerSetting configuration)
|
||||
{
|
||||
|
||||
// 获取API路由前缀
|
||||
var apiRoutePrefix = GetApiRoutePrefix(action, configuration);
|
||||
var controllerNameInUrl =
|
||||
NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
|
||||
|
||||
// 规范化控制器名称
|
||||
var normalizedControllerName = NormalizeUrlControllerName(
|
||||
rootPath,
|
||||
controllerName,
|
||||
action,
|
||||
httpMethod,
|
||||
configuration);
|
||||
|
||||
var url = $"{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}";
|
||||
// 构建基础URL
|
||||
var url = $"{rootPath}/{NormalizeControllerNameCase(normalizedControllerName, configuration)}";
|
||||
|
||||
//Add {id} path if needed
|
||||
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
|
||||
if (idParameterModel != null)
|
||||
{
|
||||
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
|
||||
{
|
||||
url += "/{id}";
|
||||
}
|
||||
else
|
||||
{
|
||||
var properties = idParameterModel
|
||||
.ParameterType
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
// 处理ID参数路由
|
||||
url = BuildIdParameterRoute(url, action, configuration);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
url += "/{" + NormalizeIdPropertyNameCase(property, configuration) + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Add action name if needed
|
||||
var actionNameInUrl = NormalizeUrlActionName(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)}}}";
|
||||
}
|
||||
}
|
||||
// 处理Action名称路由
|
||||
url = BuildActionNameRoute(url, rootPath, controllerName, action, httpMethod, configuration);
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义服务约定实现,用于处理API路由和HTTP方法约束
|
||||
/// </summary>
|
||||
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
|
||||
[ExposeServices(typeof(IAbpServiceConvention))]
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 根据选择器是否存在执行不同的配置
|
||||
if (!action.Selectors.Any())
|
||||
{
|
||||
AddAbpServiceSelector(rootPath, controllerName, action, configuration);
|
||||
@@ -41,56 +63,92 @@ namespace Yi.Framework.AspNetCore.Mvc
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
|
||||
{
|
||||
base.AddAbpServiceSelector(rootPath, controllerName, action, configuration);
|
||||
}
|
||||
|
||||
protected override void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
|
||||
/// <summary>
|
||||
/// 规范化选择器路由
|
||||
/// </summary>
|
||||
protected override void NormalizeSelectorRoutes(
|
||||
string rootPath,
|
||||
string controllerName,
|
||||
ActionModel action,
|
||||
ConventionalControllerSetting? configuration)
|
||||
{
|
||||
foreach (var selector in action.Selectors)
|
||||
{
|
||||
var httpMethod = selector.ActionConstraints
|
||||
.OfType<HttpMethodActionConstraint>()
|
||||
.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 }));
|
||||
}
|
||||
// 获取HTTP方法约束
|
||||
var httpMethod = GetOrCreateHttpMethod(selector, action, configuration);
|
||||
|
||||
// 处理路由模板
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.AspNetCore.WebClientInfo;
|
||||
|
||||
namespace Yi.Framework.AspNetCore;
|
||||
|
||||
/// <summary>
|
||||
/// 真实IP地址提供程序,支持代理服务器场景
|
||||
/// </summary>
|
||||
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
|
||||
{
|
||||
private const string XForwardedForHeader = "X-Forwarded-For";
|
||||
|
||||
/// <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()
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpContext = HttpContextAccessor.HttpContext;
|
||||
if (httpContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Logger.LogWarning(ex, "获取客户端IP地址时发生异常");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,55 @@
|
||||
namespace Yi.Framework.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// 远程服务成功响应信息
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RemoteServiceSuccessInfo
|
||||
{
|
||||
/// <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>
|
||||
public RemoteServiceSuccessInfo()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>.
|
||||
/// 使用指定参数初始化远程服务成功响应信息的新实例
|
||||
/// </summary>
|
||||
/// <param name="code">Error code</param>
|
||||
/// <param name="details">Error details</param>
|
||||
/// <param name="message">Error message</param>
|
||||
/// <param name="data">Error data</param>
|
||||
public RemoteServiceSuccessInfo(string message, string? details = null, string? code = null, object? data = null)
|
||||
/// <param name="message">响应消息</param>
|
||||
/// <param name="details">详细信息</param>
|
||||
/// <param name="code">响应代码</param>
|
||||
/// <param name="data">响应数据</param>
|
||||
public RemoteServiceSuccessInfo(
|
||||
string message,
|
||||
string? details = null,
|
||||
string? code = null,
|
||||
object? data = null)
|
||||
{
|
||||
Message = message;
|
||||
Details = details;
|
||||
Code = code;
|
||||
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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 异常元数据
|
||||
/// </summary>
|
||||
public sealed class ExceptionMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int StatusCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public object ErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(没被复写过的 ErrorCode )
|
||||
/// </summary>
|
||||
public object OriginErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误对象(信息)
|
||||
/// </summary>
|
||||
public object Errors { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外数据
|
||||
/// </summary>
|
||||
public object Data { get; internal set; }
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
/// <summary>
|
||||
/// 友好异常拦截器
|
||||
/// </summary>
|
||||
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常拦截
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnExceptionAsync(ExceptionContext context)
|
||||
{
|
||||
|
||||
// 排除 WebSocket 请求处理
|
||||
if (context.HttpContext.IsWebSocketRequest()) return;
|
||||
|
||||
// 如果异常在其他地方被标记了处理,那么这里不再处理
|
||||
if (context.ExceptionHandled) return;
|
||||
|
||||
// 解析异常信息
|
||||
var exceptionMetadata = GetExceptionMetadata(context);
|
||||
|
||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||
// 执行规范化异常处理
|
||||
context.Result = unifyResult.OnException(context, exceptionMetadata);
|
||||
|
||||
// 创建日志记录器
|
||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
|
||||
|
||||
// 记录拦截日常
|
||||
logger.LogError(context.Exception, context.Exception.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取异常元数据
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static ExceptionMetadata GetExceptionMetadata(ActionContext context)
|
||||
{
|
||||
object errorCode = default;
|
||||
object originErrorCode = default;
|
||||
object errors = default;
|
||||
object data = default;
|
||||
var statusCode = StatusCodes.Status500InternalServerError;
|
||||
var isValidationException = false; // 判断是否是验证异常
|
||||
var isFriendlyException = false;
|
||||
|
||||
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
|
||||
var exception = context is ExceptionContext exContext
|
||||
? exContext.Exception
|
||||
: (
|
||||
context is ActionExecutedContext edContext
|
||||
? edContext.Exception
|
||||
: default
|
||||
);
|
||||
|
||||
// 判断是否是友好异常
|
||||
if (exception is UserFriendlyException friendlyException)
|
||||
{
|
||||
int statusCode2 = 500;
|
||||
int.TryParse(friendlyException.Code, out statusCode2);
|
||||
isFriendlyException = true;
|
||||
errorCode = friendlyException.Code;
|
||||
originErrorCode = friendlyException.Code;
|
||||
statusCode = statusCode2==0?403:statusCode2;
|
||||
isValidationException = false;
|
||||
errors = friendlyException.Message;
|
||||
data = friendlyException.Data;
|
||||
}
|
||||
|
||||
return new ExceptionMetadata
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
ErrorCode = errorCode,
|
||||
OriginErrorCode = originErrorCode,
|
||||
Errors = errors,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.Core.Extensions;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化结构(请求成功)过滤器
|
||||
/// </summary>
|
||||
public class SucceededUnifyResultFilter : IAsyncActionFilter, IOrderedFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// 过滤器排序
|
||||
/// </summary>
|
||||
private const int FilterOrder = 8888;
|
||||
|
||||
/// <summary>
|
||||
/// 排序属性
|
||||
/// </summary>
|
||||
public int Order => FilterOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 处理规范化结果
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
// 执行 Action 并获取结果
|
||||
var actionExecutedContext = await next();
|
||||
|
||||
// 排除 WebSocket 请求处理
|
||||
if (actionExecutedContext.HttpContext.IsWebSocketRequest()) return;
|
||||
|
||||
// 处理已经含有状态码结果的 Result
|
||||
if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult &&
|
||||
statusCodeResult.StatusCode != null)
|
||||
{
|
||||
// 小于 200 或者 大于 299 都不是成功值,直接跳过
|
||||
if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299)
|
||||
{
|
||||
// 处理规范化结果
|
||||
if (!CheckStatusCodeNonUnify(context.HttpContext, out var unifyRes))
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
var statusCode = statusCodeResult.StatusCode.Value;
|
||||
|
||||
// 解决刷新 Token 时间和 Token 时间相近问题
|
||||
if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized
|
||||
&& httpContext.Response.Headers.ContainsKey("access-token")
|
||||
&& httpContext.Response.Headers.ContainsKey("x-access-token"))
|
||||
{
|
||||
httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden;
|
||||
}
|
||||
|
||||
// 如果 Response 已经完成输出,则禁止写入
|
||||
if (httpContext.Response.HasStarted) return;
|
||||
await unifyRes.OnResponseStatusCodes(httpContext, statusCode,
|
||||
httpContext.RequestServices.GetService<IOptions<UnifyResultSettingsOptions>>()?.Value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果出现异常,则不会进入该过滤器
|
||||
if (actionExecutedContext.Exception != null) return;
|
||||
|
||||
// 获取控制器信息
|
||||
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
||||
|
||||
// 判断是否支持 MVC 规范化处理,检测配置而已
|
||||
// if (!UnifyContext.CheckSupportMvcController(context.HttpContext, actionDescriptor, out _)) return;
|
||||
|
||||
// 判断是否跳过规范化处理,检测NonUnifyAttribute而已
|
||||
if (CheckSucceededNonUnify(actionDescriptor.MethodInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
|
||||
|
||||
// 处理 BadRequestObjectResult 类型规范化处理
|
||||
if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult)
|
||||
{
|
||||
// 解析验证消息
|
||||
var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value);
|
||||
|
||||
var result = unifyResult.OnValidateFailed(context, validationMetadata);
|
||||
if (result != null) actionExecutedContext.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
IActionResult result = default;
|
||||
|
||||
// 检查是否是有效的结果(可进行规范化的结果)
|
||||
if (CheckVaildResult(actionExecutedContext.Result, out var data))
|
||||
{
|
||||
result = unifyResult.OnSucceeded(actionExecutedContext, data);
|
||||
}
|
||||
|
||||
// 如果是不能规范化的结果类型,则跳过
|
||||
if (result == null) return;
|
||||
|
||||
actionExecutedContext.Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取验证错误信息
|
||||
/// </summary>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
private static ValidationMetadata GetValidationMetadata(object errors)
|
||||
{
|
||||
ModelStateDictionary _modelState = null;
|
||||
object validationResults = null;
|
||||
(string message, string firstErrorMessage, string firstErrorProperty) = (default, default, default);
|
||||
|
||||
// 判断是否是集合类型
|
||||
if (errors is IEnumerable && errors is not string)
|
||||
{
|
||||
// 如果是模型验证字典类型
|
||||
if (errors is ModelStateDictionary modelState)
|
||||
{
|
||||
_modelState = modelState;
|
||||
// 将验证错误信息转换成字典并序列化成 Json
|
||||
validationResults = modelState.Where(u => modelState[u.Key].ValidationState == ModelValidationState.Invalid)
|
||||
.ToDictionary(u => u.Key, u => modelState[u.Key].Errors.Select(c => c.ErrorMessage).ToArray());
|
||||
}
|
||||
// 如果是 ValidationProblemDetails 特殊类型
|
||||
else if (errors is ValidationProblemDetails validation)
|
||||
{
|
||||
validationResults = validation.Errors
|
||||
.ToDictionary(u => u.Key, u => u.Value.ToArray());
|
||||
}
|
||||
// 如果是字典类型
|
||||
else if (errors is Dictionary<string, string[]> dicResults)
|
||||
{
|
||||
validationResults = dicResults;
|
||||
}
|
||||
|
||||
message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true
|
||||
});
|
||||
firstErrorMessage = (validationResults as Dictionary<string, string[]>).First().Value[0];
|
||||
firstErrorProperty = (validationResults as Dictionary<string, string[]>).First().Key;
|
||||
}
|
||||
// 其他类型
|
||||
else
|
||||
{
|
||||
validationResults = firstErrorMessage = message = errors?.ToString();
|
||||
}
|
||||
|
||||
return new ValidationMetadata
|
||||
{
|
||||
ValidationResult = validationResults,
|
||||
Message = message,
|
||||
ModelState = _modelState,
|
||||
FirstErrorProperty = firstErrorProperty,
|
||||
FirstErrorMessage = firstErrorMessage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否是有效的结果(可进行规范化的结果)
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
private bool CheckVaildResult(IActionResult result, out object data)
|
||||
{
|
||||
data = default;
|
||||
|
||||
// 排除以下结果,跳过规范化处理
|
||||
var isDataResult = result switch
|
||||
{
|
||||
ViewResult => false,
|
||||
PartialViewResult => false,
|
||||
FileResult => false,
|
||||
ChallengeResult => false,
|
||||
SignInResult => false,
|
||||
SignOutResult => false,
|
||||
RedirectToPageResult => false,
|
||||
RedirectToRouteResult => false,
|
||||
RedirectResult => false,
|
||||
RedirectToActionResult => false,
|
||||
LocalRedirectResult => false,
|
||||
ForbidResult => false,
|
||||
ViewComponentResult => false,
|
||||
PageResult => false,
|
||||
NotFoundResult => false,
|
||||
NotFoundObjectResult => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// 目前支持返回值 ActionResult
|
||||
if (isDataResult) data = result switch
|
||||
{
|
||||
// 处理内容结果
|
||||
ContentResult content => content.Content,
|
||||
// 处理对象结果
|
||||
ObjectResult obj => obj.Value,
|
||||
// 处理 JSON 对象
|
||||
JsonResult json => json.Value,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return isDataResult;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 检查短路状态码(>=400)是否进行规范化处理
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="unifyResult"></param>
|
||||
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
|
||||
internal static bool CheckStatusCodeNonUnify(HttpContext context, out IUnifyResultProvider unifyResult)
|
||||
{
|
||||
// 获取终点路由特性
|
||||
var endpointFeature = context.Features.Get<IEndpointFeature>();
|
||||
if (endpointFeature == null) return (unifyResult = null) == null;
|
||||
|
||||
// 判断是否跳过规范化处理
|
||||
var isSkip = context.GetEndpoint()?.Metadata?.GetMetadata<NonUnifyAttribute>()!= null
|
||||
|| endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null
|
||||
|| context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase)
|
||||
|| context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isSkip == true) unifyResult = null;
|
||||
else
|
||||
{
|
||||
unifyResult = context.RequestServices.GetRequiredService<IUnifyResultProvider>();
|
||||
}
|
||||
|
||||
return unifyResult == null || isSkip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查请求成功是否进行规范化处理
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="isWebRequest"></param>
|
||||
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
|
||||
private bool CheckSucceededNonUnify(MethodInfo method, bool isWebRequest = true)
|
||||
{
|
||||
// 判断是否跳过规范化处理
|
||||
var isSkip = method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|
||||
|| method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData");
|
||||
|
||||
if (!isWebRequest)
|
||||
{
|
||||
return isSkip;
|
||||
}
|
||||
return isSkip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化结果提供器
|
||||
/// </summary>
|
||||
public interface IUnifyResultProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnSucceeded(ActionExecutedContext context, object data);
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata);
|
||||
|
||||
/// <summary>
|
||||
/// 拦截返回状态码
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = default);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 禁止规范化处理
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
public sealed class NonUnifyAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// RESTful 风格返回值
|
||||
/// </summary>
|
||||
[Dependency(TryRegister = true)]
|
||||
[ExposeServices(typeof(IUnifyResultProvider))]
|
||||
public class RESTfulResultProvider : IUnifyResultProvider,ITransientDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置响应状态码
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
public static void SetResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
if (unifyResultSettings == null) return;
|
||||
|
||||
// 篡改响应状态码
|
||||
if (unifyResultSettings.AdaptStatusCodes != null && unifyResultSettings.AdaptStatusCodes.Length > 0)
|
||||
{
|
||||
var adaptStatusCode = unifyResultSettings.AdaptStatusCodes.FirstOrDefault(u => u[0] == statusCode);
|
||||
if (adaptStatusCode != null && adaptStatusCode.Length > 0 && adaptStatusCode[0] > 0)
|
||||
{
|
||||
context.Response.StatusCode = adaptStatusCode[1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果为 null,则所有请求错误的状态码设置为 200
|
||||
if (unifyResultSettings.Return200StatusCodes == null) context.Response.StatusCode = 200;
|
||||
// 否则只有里面的才设置为 200
|
||||
else if (unifyResultSettings.Return200StatusCodes.Contains(statusCode)) context.Response.StatusCode = 200;
|
||||
else { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnSucceeded(ActionExecutedContext context, object data)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败/业务异常返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
|
||||
{
|
||||
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 特定状态码返回值
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="unifyResultSettings"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
|
||||
{
|
||||
// 设置响应状态码
|
||||
SetResponseStatusCodes(context, statusCode, unifyResultSettings);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
// 处理 401 状态码
|
||||
case StatusCodes.Status401Unauthorized:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 Unauthorized"));
|
||||
break;
|
||||
// 处理 403 状态码
|
||||
case StatusCodes.Status403Forbidden:
|
||||
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 Forbidden"));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回 RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <param name="succeeded"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
public static RESTfulResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)
|
||||
{
|
||||
return new RESTfulResult<object>
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Succeeded = succeeded,
|
||||
Data = data,
|
||||
Errors = errors,
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// RESTful 风格结果集
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class RESTfulResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int? StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行成功
|
||||
/// </summary>
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public object Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public object Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳
|
||||
/// </summary>
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
|
||||
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化接口
|
||||
/// 由于太多人反应,想兼容一套类似furion的返回情况,200状态码包一层更符合国内习惯,既然如此,不如直接搬过来
|
||||
/// </summary>
|
||||
public static class UnifyResultExtensions
|
||||
{
|
||||
public static IServiceCollection AddFurionUnifyResultApi(this IServiceCollection services)
|
||||
{
|
||||
//成功规范接口
|
||||
services.AddTransient<SucceededUnifyResultFilter>();
|
||||
//异常规范接口
|
||||
services.AddTransient<FriendlyExceptionFilter>();
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Filters.AddService<SucceededUnifyResultFilter>(99);
|
||||
options.Filters.AddService<FriendlyExceptionFilter>(100);
|
||||
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 规范化配置选项
|
||||
/// </summary>
|
||||
public sealed class UnifyResultSettingsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置返回 200 状态码列表
|
||||
/// <para>默认:401,403,如果设置为 null,则标识所有状态码都返回 200 </para>
|
||||
/// </summary>
|
||||
public int[] Return200StatusCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适配(篡改)Http 状态码(只支持短路状态码,比如 401,403,500 等)
|
||||
/// </summary>
|
||||
public int[][] AdaptStatusCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持 MVC 控制台规范化处理
|
||||
/// </summary>
|
||||
public bool? SupportMvcController { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项后期配置
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public void PostConfigure(UnifyResultSettingsOptions options, IConfiguration configuration)
|
||||
{
|
||||
options.Return200StatusCodes ??= new[] { 401, 403 };
|
||||
options.SupportMvcController ??= false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// MIT 许可证
|
||||
//
|
||||
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
|
||||
//
|
||||
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
|
||||
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
|
||||
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
|
||||
//
|
||||
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
|
||||
//
|
||||
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
|
||||
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
|
||||
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Yi.Framework.AspNetCore.UnifyResult;
|
||||
|
||||
/// <summary>
|
||||
/// 验证信息元数据
|
||||
/// </summary>
|
||||
public sealed class ValidationMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证结果
|
||||
/// </summary>
|
||||
/// <remarks>返回字典或字符串类型</remarks>
|
||||
public object ValidationResult { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常消息
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态
|
||||
/// </summary>
|
||||
public ModelStateDictionary ModelState { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public object ErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(没被复写过的 ErrorCode )
|
||||
/// </summary>
|
||||
public object OriginErrorCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态码
|
||||
/// </summary>
|
||||
public int? StatusCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首个错误属性
|
||||
/// </summary>
|
||||
public string FirstErrorProperty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 首个错误消息
|
||||
/// </summary>
|
||||
public string FirstErrorMessage { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外数据
|
||||
/// </summary>
|
||||
public object Data { get; internal set; }
|
||||
}
|
||||
@@ -1,27 +1,37 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
using Yi.Framework.AspNetCore.Mvc;
|
||||
using Volo.Abp.AspNetCore.WebClientInfo;
|
||||
using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Authentication;
|
||||
using Yi.Framework.Core;
|
||||
using Yi.Framework.Core.Authentication;
|
||||
|
||||
namespace Yi.Framework.AspNetCore
|
||||
{
|
||||
[DependsOn(typeof(YiFrameworkCoreModule)
|
||||
)]
|
||||
/// <summary>
|
||||
/// Yi框架ASP.NET Core模块
|
||||
/// </summary>
|
||||
[DependsOn(typeof(YiFrameworkCoreModule))]
|
||||
public class YiFrameworkAspNetCoreModule : AbpModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置服务后的处理
|
||||
/// </summary>
|
||||
public override void PostConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var services = context.Services;
|
||||
|
||||
// 替换默认的WebClientInfoProvider为支持代理的实现
|
||||
services.Replace(new ServiceDescriptor(
|
||||
typeof(IWebClientInfoProvider),
|
||||
typeof(RealIpHttpContextWebClientInfoProvider),
|
||||
ServiceLifetime.Transient));
|
||||
|
||||
// 替换默认的AuthenticationHandlerProvider为支持刷新鉴权
|
||||
services.Replace(new ServiceDescriptor(
|
||||
typeof(IAuthenticationHandlerProvider),
|
||||
typeof(RefreshAuthenticationHandlerProvider),
|
||||
ServiceLifetime.Scoped));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,91 @@
|
||||
using System.Linq.Expressions;
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
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)
|
||||
{
|
||||
if (!context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value.IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 获取后台任务管理器和所有 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
|
||||
{
|
||||
[Dependency(ReplaceServices =true)]
|
||||
/// <summary>
|
||||
/// 缓存键标准化处理器
|
||||
/// 用于处理缓存键的格式化和多租户支持
|
||||
/// </summary>
|
||||
[Dependency(ReplaceServices = true)]
|
||||
public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
|
||||
{
|
||||
protected ICurrentTenant CurrentTenant { get; }
|
||||
|
||||
protected AbpDistributedCacheOptions DistributedCacheOptions { get; }
|
||||
private readonly ICurrentTenant _currentTenant;
|
||||
private readonly AbpDistributedCacheOptions _distributedCacheOptions;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="currentTenant">当前租户服务</param>
|
||||
/// <param name="distributedCacheOptions">分布式缓存配置选项</param>
|
||||
public YiDistributedCacheKeyNormalizer(
|
||||
ICurrentTenant currentTenant,
|
||||
IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
|
||||
{
|
||||
CurrentTenant = currentTenant;
|
||||
DistributedCacheOptions = distributedCacheOptions.Value;
|
||||
_currentTenant = currentTenant;
|
||||
_distributedCacheOptions = distributedCacheOptions.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标准化缓存键
|
||||
/// </summary>
|
||||
/// <param name="args">缓存键标准化参数</param>
|
||||
/// <returns>标准化后的缓存键</returns>
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FreeRedis;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Volo.Abp.Caching;
|
||||
@@ -7,26 +8,57 @@ using Volo.Abp.Caching;
|
||||
namespace Yi.Framework.Caching.FreeRedis
|
||||
{
|
||||
/// <summary>
|
||||
/// 此模块得益于FreeRedis作者支持IDistributedCache,使用湿滑
|
||||
/// FreeRedis缓存模块
|
||||
/// 提供基于FreeRedis的分布式缓存实现
|
||||
/// </summary>
|
||||
[DependsOn(typeof(AbpCachingModule))]
|
||||
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)
|
||||
{
|
||||
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
|
||||
var redisEnabled = configuration["Redis:IsEnabled"];
|
||||
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
|
||||
// 检查Redis是否启用
|
||||
if (!IsRedisEnabled(configuration))
|
||||
{
|
||||
var redisConfiguration = configuration["Redis:Configuration"];
|
||||
RedisClient redisClient = new RedisClient(redisConfiguration);
|
||||
|
||||
context.Services.AddSingleton<IRedisClient>(redisClient);
|
||||
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(new
|
||||
DistributedCache(redisClient)));
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Yi.Framework.Core.Authentication;
|
||||
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
public static void RefreshAuthentication(this HttpContext context)
|
||||
{
|
||||
var currentAuthenticationHandler =
|
||||
context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
|
||||
if (currentAuthenticationHandler is IRefreshAuthenticationHandlerProvider refreshAuthenticationHandler)
|
||||
{
|
||||
refreshAuthenticationHandler.RefreshAuthentication();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Yi.Framework.Core.Authentication;
|
||||
|
||||
public interface IRefreshAuthenticationHandlerProvider: IAuthenticationHandlerProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 刷新鉴权
|
||||
/// </summary>
|
||||
void RefreshAuthentication();
|
||||
}
|
||||
@@ -6,8 +6,21 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.Core.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 排序接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 实现此接口的实体类将支持排序功能
|
||||
/// 通常用于列表数据的展示顺序控制
|
||||
/// </remarks>
|
||||
public interface IOrderNum
|
||||
{
|
||||
/// <summary>
|
||||
/// 排序号
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 数字越小越靠前,默认为0
|
||||
/// </remarks>
|
||||
int OrderNum { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,21 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.Core.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 实现此接口的实体类将支持启用/禁用状态管理
|
||||
/// 用于控制数据记录的可用状态
|
||||
/// </remarks>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义公共文件路径
|
||||
/// 文件类型枚举
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于定义系统支持的文件类型分类
|
||||
/// 主要用于文件上传和存储时的类型区分
|
||||
/// </remarks>
|
||||
public enum FileTypeEnum
|
||||
{
|
||||
File,
|
||||
Image,
|
||||
Thumbnail,
|
||||
Excel,
|
||||
Temp
|
||||
/// <summary>
|
||||
/// 普通文件
|
||||
/// </summary>
|
||||
file = 0,
|
||||
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// 排序方向枚举
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于定义数据查询时的排序方向
|
||||
/// 常用于列表数据排序
|
||||
/// </remarks>
|
||||
public enum OrderByEnum
|
||||
{
|
||||
Asc,
|
||||
Desc
|
||||
/// <summary>
|
||||
/// 升序排列
|
||||
/// </summary>
|
||||
Asc = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 降序排列
|
||||
/// </summary>
|
||||
Desc = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,67 +6,91 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.Core.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询操作符枚举
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 定义查询条件中支持的操作符类型
|
||||
/// 用于构建动态查询条件
|
||||
/// </remarks>
|
||||
public enum QueryOperatorEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 相等
|
||||
/// 等于
|
||||
/// </summary>
|
||||
Equal,
|
||||
Equal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 匹配
|
||||
/// 模糊匹配
|
||||
/// </summary>
|
||||
Like,
|
||||
Like = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 大于
|
||||
/// </summary>
|
||||
GreaterThan,
|
||||
GreaterThan = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 大于或等于
|
||||
/// </summary>
|
||||
GreaterThanOrEqual,
|
||||
GreaterThanOrEqual = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 小于
|
||||
/// </summary>
|
||||
LessThan,
|
||||
LessThan = 4,
|
||||
|
||||
/// <summary>
|
||||
/// 小于或等于
|
||||
/// </summary>
|
||||
LessThanOrEqual,
|
||||
LessThanOrEqual = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 等于集合
|
||||
/// 在指定集合中
|
||||
/// </summary>
|
||||
In,
|
||||
In = 6,
|
||||
|
||||
/// <summary>
|
||||
/// 不等于集合
|
||||
/// 不在指定集合中
|
||||
/// </summary>
|
||||
NotIn,
|
||||
NotIn = 7,
|
||||
|
||||
/// <summary>
|
||||
/// 左边匹配
|
||||
/// 左侧模糊匹配
|
||||
/// </summary>
|
||||
LikeLeft,
|
||||
LikeLeft = 8,
|
||||
|
||||
/// <summary>
|
||||
/// 右边匹配
|
||||
/// 右侧模糊匹配
|
||||
/// </summary>
|
||||
LikeRight,
|
||||
LikeRight = 9,
|
||||
|
||||
/// <summary>
|
||||
/// 不相等
|
||||
/// 不等于
|
||||
/// </summary>
|
||||
NoEqual,
|
||||
NoEqual = 10,
|
||||
|
||||
/// <summary>
|
||||
/// 为空或空
|
||||
/// 为null或空
|
||||
/// </summary>
|
||||
IsNullOrEmpty,
|
||||
IsNullOrEmpty = 11,
|
||||
|
||||
/// <summary>
|
||||
/// 不为空
|
||||
/// 不为null
|
||||
/// </summary>
|
||||
IsNot,
|
||||
IsNot = 12,
|
||||
|
||||
/// <summary>
|
||||
/// 不匹配
|
||||
/// </summary>
|
||||
NoLike,
|
||||
NoLike = 13,
|
||||
|
||||
/// <summary>
|
||||
/// 时间段 值用 "|" 隔开
|
||||
/// 日期范围
|
||||
/// </summary>
|
||||
DateRange
|
||||
/// <remarks>
|
||||
/// 使用"|"分隔起始和结束日期
|
||||
/// </remarks>
|
||||
DateRange = 14
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,33 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.Core.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// API返回状态码枚举
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 定义API接口统一的返回状态码
|
||||
/// 遵循HTTP状态码规范
|
||||
/// </remarks>
|
||||
public enum ResultCodeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作成功。
|
||||
/// 操作成功
|
||||
/// </summary>
|
||||
Success = 200,
|
||||
|
||||
/// <summary>
|
||||
/// 操作不成功
|
||||
/// </summary>
|
||||
NotSuccess = 500,
|
||||
|
||||
/// <summary>
|
||||
/// 无权限
|
||||
/// 未授权访问
|
||||
/// </summary>
|
||||
NoPermission = 401,
|
||||
|
||||
/// <summary>
|
||||
/// 被拒绝
|
||||
/// 访问被拒绝
|
||||
/// </summary>
|
||||
Denied = 403
|
||||
Denied = 403,
|
||||
|
||||
/// <summary>
|
||||
/// 操作失败
|
||||
/// </summary>
|
||||
NotSuccess = 500
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +1,134 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Yi.Framework.Core.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// HttpContext扩展方法类
|
||||
/// </summary>
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置文件下载名称
|
||||
/// 设置内联文件下载响应头
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="httpContext">HTTP上下文</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
public static void FileInlineHandle(this HttpContext httpContext, string fileName)
|
||||
{
|
||||
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
|
||||
httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename);
|
||||
|
||||
var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
|
||||
httpContext.Response.Headers.Add("Content-Disposition", $"inline;filename={encodeFilename}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文件附件名称
|
||||
/// 设置附件下载响应头
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="httpContext">HTTP上下文</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
|
||||
{
|
||||
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
|
||||
httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename);
|
||||
|
||||
var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
|
||||
httpContext.Response.Headers.Add("Content-Disposition", $"attachment;filename={encodeFilename}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取语言种类
|
||||
/// 获取客户端首选语言
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="httpContext">HTTP上下文</param>
|
||||
/// <returns>语言代码,默认返回zh-CN</returns>
|
||||
public static string GetLanguage(this HttpContext httpContext)
|
||||
{
|
||||
string res = "zh-CN";
|
||||
var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
|
||||
if (str is not null)
|
||||
{
|
||||
res = str.Split(",")[0];
|
||||
}
|
||||
return res;
|
||||
|
||||
const string defaultLanguage = "zh-CN";
|
||||
var acceptLanguage = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
|
||||
|
||||
return string.IsNullOrEmpty(acceptLanguage)
|
||||
? defaultLanguage
|
||||
: acceptLanguage.Split(',')[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为异步请求
|
||||
/// 判断是否为Ajax请求
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="request">HTTP请求</param>
|
||||
/// <returns>是否为Ajax请求</returns>
|
||||
public static bool IsAjaxRequest(this HttpRequest request)
|
||||
{
|
||||
string header = request.Headers["X-Requested-With"];
|
||||
return "XMLHttpRequest".Equals(header);
|
||||
const string ajaxHeader = "XMLHttpRequest";
|
||||
return ajaxHeader.Equals(request.Headers["X-Requested-With"],
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取客户端IP
|
||||
/// 获取客户端IP地址
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="context">HTTP上下文</param>
|
||||
/// <returns>客户端IP地址</returns>
|
||||
public static string GetClientIp(this HttpContext context)
|
||||
{
|
||||
if (context == null) return "";
|
||||
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(result))
|
||||
const string localhost = "127.0.0.1";
|
||||
if (context == null) return string.Empty;
|
||||
|
||||
// 尝试获取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"))
|
||||
{
|
||||
return localhost;
|
||||
}
|
||||
|
||||
//Ip规则校验
|
||||
var regResult = Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
|
||||
// 清理IPv6格式
|
||||
ip = ip.Replace("::ffff:", localhost);
|
||||
|
||||
// 移除端口号
|
||||
ip = Regex.Replace(ip, @":\d{1,5}$", "");
|
||||
|
||||
result = regResult ? result : "127.0.0.1";
|
||||
return result;
|
||||
// 验证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>
|
||||
/// 获取浏览器标识
|
||||
/// 获取User-Agent信息
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="context">HTTP上下文</param>
|
||||
/// <returns>User-Agent字符串</returns>
|
||||
public static string GetUserAgent(this HttpContext context)
|
||||
{
|
||||
return context.Request.Headers["User-Agent"];
|
||||
return context.Request.Headers["User-Agent"].ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户权限声明值
|
||||
/// </summary>
|
||||
/// <param name="context">HTTP上下文</param>
|
||||
/// <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();
|
||||
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)
|
||||
{
|
||||
return context.WebSockets.IsWebSocketRequest ||
|
||||
context.Request.Path == "/ws";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +78,25 @@ namespace Yi.Framework.Core.Helper
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CPU使用情况
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static CPUMetrics GetCPUMetrics()
|
||||
{
|
||||
CPUMetrics cpuMetrics = new CPUMetrics();
|
||||
var cpudetail = GetCPUDetails();
|
||||
cpuMetrics.CoreTotal = cpudetail.Cores;
|
||||
cpuMetrics.LogicalProcessors =cpudetail.LogicalProcessors;
|
||||
cpuMetrics.CPURate = Math.Ceiling(ParseToDouble(GetCPURate()));
|
||||
cpuMetrics.FreeRate = 1 - cpuMetrics.CPURate;
|
||||
return cpuMetrics;
|
||||
}
|
||||
/// <summary>
|
||||
/// 内存使用情况
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static MemoryMetrics GetComputerInfo()
|
||||
public static MemoryMetrics GetMemoryMetrics()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -94,7 +107,7 @@ namespace Yi.Framework.Core.Helper
|
||||
memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB";
|
||||
memoryMetrics.TotalRAM = Math.Round(memoryMetrics.Total / 1024, 2) + "GB";
|
||||
memoryMetrics.RAMRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total).ToString() + "%";
|
||||
memoryMetrics.CPURate = Math.Ceiling(ParseToDouble(GetCPURate()));
|
||||
|
||||
return memoryMetrics;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -105,7 +118,7 @@ namespace Yi.Framework.Core.Helper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内存大小
|
||||
/// 获取磁盘信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<DiskInfo> GetDiskInfos()
|
||||
@@ -174,7 +187,7 @@ namespace Yi.Framework.Core.Helper
|
||||
var isUnix = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
return isUnix;
|
||||
}
|
||||
|
||||
|
||||
public static string GetCPURate()
|
||||
{
|
||||
string cpuRate;
|
||||
@@ -221,8 +234,69 @@ namespace Yi.Framework.Core.Helper
|
||||
}
|
||||
return runTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static CPUInfo GetCPUDetails()
|
||||
{
|
||||
int logicalProcessors = 0;
|
||||
int cores = 0;
|
||||
|
||||
if (IsUnix())
|
||||
{
|
||||
string logicalOutput = ShellHelper.Bash("lscpu | grep '^CPU(s):' | awk '{print $2}'");
|
||||
logicalProcessors = int.Parse(logicalOutput.Trim());
|
||||
|
||||
string coresOutput = ShellHelper.Bash("lscpu | grep 'Core(s) per socket:' | awk '{print $4}'");
|
||||
string socketsOutput = ShellHelper.Bash("lscpu | grep 'Socket(s):' | awk '{print $2}'");
|
||||
cores = int.Parse(coresOutput.Trim()) * int.Parse(socketsOutput.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
string output = ShellHelper.Cmd("wmic", "cpu get NumberOfCores,NumberOfLogicalProcessors /format:csv");
|
||||
var lines = output.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (lines.Length > 1)
|
||||
{
|
||||
var values = lines[1].Split(',');
|
||||
|
||||
cores = int.Parse(values[1].Trim());
|
||||
logicalProcessors =int.Parse(values[2].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
return new CPUInfo
|
||||
{
|
||||
LogicalProcessors = logicalProcessors,
|
||||
Cores = cores
|
||||
};
|
||||
}
|
||||
}
|
||||
public class CPUInfo
|
||||
{
|
||||
public int LogicalProcessors { get; set; }
|
||||
public int Cores { get; set; }
|
||||
}
|
||||
public class CPUMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// 内核数
|
||||
/// </summary>
|
||||
public int CoreTotal { get; set; }
|
||||
/// <summary>
|
||||
/// 逻辑处理器数
|
||||
/// </summary>
|
||||
public int LogicalProcessors { get; set; }
|
||||
/// <summary>
|
||||
/// CPU使用率%
|
||||
/// </summary>
|
||||
public double CPURate { get; set; }
|
||||
/// <summary>
|
||||
/// CPU空闲率%
|
||||
/// </summary>
|
||||
public double FreeRate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内存信息
|
||||
/// </summary>
|
||||
@@ -236,10 +310,7 @@ namespace Yi.Framework.Core.Helper
|
||||
public double Free { get; set; }
|
||||
|
||||
public string UsedRam { get; set; }
|
||||
/// <summary>
|
||||
/// CPU使用率%
|
||||
/// </summary>
|
||||
public double CPURate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总内存 GB
|
||||
/// </summary>
|
||||
@@ -306,20 +377,25 @@ namespace Yi.Framework.Core.Helper
|
||||
/// <returns></returns>
|
||||
public MemoryMetrics GetUnixMetrics()
|
||||
{
|
||||
string output = ShellHelper.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'");
|
||||
string output = ShellHelper.Bash(@"
|
||||
# 从 /proc/meminfo 文件中提取总内存
|
||||
total_mem=$(cat /proc/meminfo | grep -i ""MemTotal"" | awk '{print $2}')
|
||||
# 从 /proc/meminfo 文件中提取剩余内存
|
||||
free_mem=$(cat /proc/meminfo | grep -i ""MemFree"" | awk '{print $2}')
|
||||
# 显示提取的信息
|
||||
echo $total_mem $used_mem $free_mem
|
||||
");
|
||||
var metrics = new MemoryMetrics();
|
||||
var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (lines.Length <= 0) return metrics;
|
||||
|
||||
if (lines != null && lines.Length > 0)
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length >= 3)
|
||||
var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
|
||||
if (memory.Length >= 2)
|
||||
{
|
||||
metrics.Total = double.Parse(memory[0]);
|
||||
metrics.Used = double.Parse(memory[1]);
|
||||
metrics.Free = double.Parse(memory[2]);//m
|
||||
metrics.Total = Math.Round(double.Parse(memory[0]) / 1024, 0);
|
||||
|
||||
metrics.Free = Math.Round(double.Parse(memory[1])/ 1024, 0);//m
|
||||
metrics.Used = metrics.Total - metrics.Free;
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace Yi.Framework.Core.Helper
|
||||
{
|
||||
var extension = Path.GetExtension(fileName);
|
||||
if (ImageType.Contains(extension.ToLower()))
|
||||
return FileTypeEnum.Image;
|
||||
return FileTypeEnum.File;
|
||||
return FileTypeEnum.image;
|
||||
return FileTypeEnum.file;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,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;
|
||||
|
||||
[Dependency(ReplaceServices =true)]
|
||||
/// <summary>
|
||||
/// Yi框架模块管理器
|
||||
/// </summary>
|
||||
[Dependency(ReplaceServices = true)]
|
||||
public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency
|
||||
{
|
||||
private readonly IModuleContainer _moduleContainer;
|
||||
private readonly IEnumerable<IModuleLifecycleContributor> _lifecycleContributors;
|
||||
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;
|
||||
_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)
|
||||
{
|
||||
|
||||
_logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块==========");
|
||||
var total = 0;
|
||||
var watch =new Stopwatch();
|
||||
long totalTime = 0;
|
||||
|
||||
var moduleCount = 0;
|
||||
var stopwatch = new Stopwatch();
|
||||
var totalTime = 0L;
|
||||
|
||||
foreach (var contributor in _lifecycleContributors)
|
||||
{
|
||||
foreach (var module in _moduleContainer.Modules)
|
||||
{
|
||||
try
|
||||
{
|
||||
watch.Restart();
|
||||
stopwatch.Restart();
|
||||
await contributor.InitializeAsync(context, module.Instance);
|
||||
watch.Stop();
|
||||
totalTime += watch.ElapsedMilliseconds;
|
||||
total++;
|
||||
if (watch.ElapsedMilliseconds > 1)
|
||||
stopwatch.Stop();
|
||||
|
||||
totalTime += stopwatch.ElapsedMilliseconds;
|
||||
moduleCount++;
|
||||
|
||||
// 仅记录耗时超过1ms的模块
|
||||
if (stopwatch.ElapsedMilliseconds > 1)
|
||||
{
|
||||
_logger.LogDebug($"耗时-{watch.ElapsedMilliseconds}ms,已加载模块-{module.Assembly.GetName().Name}");
|
||||
_logger.LogDebug(
|
||||
"耗时-{Time}ms,已加载模块-{ModuleName}",
|
||||
stopwatch.ElapsedMilliseconds,
|
||||
module.Assembly.GetName().Name);
|
||||
}
|
||||
|
||||
}
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,19 @@
|
||||
|
||||
namespace Yi.Framework.Ddd.Application.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// 带时间范围的分页查询请求接口
|
||||
/// </summary>
|
||||
public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询开始时间
|
||||
/// </summary>
|
||||
DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询结束时间
|
||||
/// </summary>
|
||||
DateTime? EndTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Yi.Framework.Ddd.Application.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页查询请求接口,包含时间范围和排序功能
|
||||
/// </summary>
|
||||
public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest
|
||||
{
|
||||
}
|
||||
|
||||
@@ -7,24 +7,47 @@ using Volo.Abp.Application.Services;
|
||||
|
||||
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>
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Yi框架CRUD服务接口(带查询、创建和更新输入)
|
||||
/// </summary>
|
||||
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>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,52 @@
|
||||
|
||||
namespace Yi.Framework.Ddd.Application.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页查询请求DTO,包含时间范围和自定义排序功能
|
||||
/// </summary>
|
||||
public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询开始时间条件
|
||||
/// 查询开始时间
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询结束时间条件
|
||||
/// 查询结束时间
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序列名
|
||||
/// </summary>
|
||||
public string? OrderByColumn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序方向(ascending/descending)
|
||||
/// </summary>
|
||||
public string? IsAsc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为升序排序
|
||||
/// </summary>
|
||||
public bool IsAscending => string.Equals(IsAsc, "ascending", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private string? _sorting;
|
||||
|
||||
/// <summary>
|
||||
/// 排序表达式
|
||||
/// </summary>
|
||||
public override string? Sorting
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(OrderByColumn))
|
||||
{
|
||||
return $"{OrderByColumn} {(IsAscending ? "ASC" : "DESC")}";
|
||||
}
|
||||
return _sorting;
|
||||
}
|
||||
set => _sorting = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ using Volo.Abp.Modularity;
|
||||
|
||||
namespace Yi.Framework.Ddd.Application.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Yi框架DDD应用层契约模块
|
||||
/// </summary>
|
||||
[DependsOn(typeof(AbpDddApplicationContractsModule))]
|
||||
public class YiFrameworkDddApplicationContractsModule : AbpModule
|
||||
{
|
||||
|
||||
@@ -6,11 +6,19 @@ using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Yi.Framework.Ddd.Application
|
||||
{
|
||||
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TEntityDto : IEntityDto<TKey>
|
||||
/// <summary>
|
||||
/// 带缓存的CRUD应用服务基类
|
||||
/// </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>
|
||||
: YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TGetOutputDto : IEntityDto<TKey>
|
||||
where TGetListOutputDto : IEntityDto<TKey>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TGetOutputDto : IEntityDto<TKey>
|
||||
where TGetListOutputDto : IEntityDto<TKey>
|
||||
{
|
||||
protected 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();
|
||||
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
||||
/// <summary>
|
||||
/// 获取缓存键
|
||||
/// </summary>
|
||||
protected virtual string GenerateCacheKey(TKey id) =>
|
||||
$"{typeof(TEntity).Name}:{CurrentTenant.Id ?? Guid.Empty}:{id}";
|
||||
|
||||
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
|
||||
: base(repository)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
||||
/// <summary>
|
||||
/// 更新实体并清除缓存
|
||||
/// </summary>
|
||||
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
||||
{
|
||||
var output = await base.UpdateAsync(id, input);
|
||||
await Cache.RemoveAsync(GetCacheKey(id));
|
||||
return output;
|
||||
var result = await base.UpdateAsync(id, input);
|
||||
await EntityCache.RemoveAsync(GenerateCacheKey(id));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
||||
/// <summary>
|
||||
/// 获取实体列表(需要继承实现具体的缓存策略)
|
||||
/// </summary>
|
||||
public override Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
||||
{
|
||||
//两种方式:
|
||||
//1:全表缓存,使用缓存直接查询
|
||||
//2:非全部缓存,查询到的数据直接添加到缓存
|
||||
|
||||
//判断是否该实体为全表缓存
|
||||
throw new NotImplementedException();
|
||||
|
||||
//IDistributedCache 有局限性,条件查询无法进行缓存了
|
||||
//if (true)
|
||||
//{
|
||||
// return await GetListByCacheAsync(input);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// return await GetListByDbAsync(input);
|
||||
//}
|
||||
|
||||
// 建议实现两种缓存策略:
|
||||
// 1. 全表缓存: 适用于数据量小且变动不频繁的场景
|
||||
// 2. 按需缓存: 仅缓存常用数据,适用于大数据量场景
|
||||
throw new NotImplementedException("请实现具体的缓存查询策略");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存获取实体列表
|
||||
/// </summary>
|
||||
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromCacheAsync(
|
||||
TGetListInput input)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个实体(优先从缓存获取)
|
||||
/// </summary>
|
||||
protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
|
||||
{
|
||||
var output = await Cache.GetOrAddAsync(GetCacheKey(id), async () => await base.GetEntityByIdAsync(id));
|
||||
return output!;
|
||||
return (await EntityCache.GetOrAddAsync(
|
||||
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);
|
||||
foreach (var itemId in id)
|
||||
{
|
||||
await Cache.RemoveAsync(GetCacheKey(itemId));
|
||||
}
|
||||
|
||||
await base.DeleteAsync(ids);
|
||||
|
||||
// 批量清除缓存
|
||||
var tasks = ids.Select(id =>
|
||||
EntityCache.RemoveAsync(GenerateCacheKey(id)));
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,142 +8,260 @@ using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Yi.Framework.Ddd.Application
|
||||
{
|
||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TEntityDto : IEntityDto<TKey>
|
||||
/// <summary>
|
||||
/// CRUD应用服务基类 - 基础版本
|
||||
/// </summary>
|
||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey>
|
||||
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TEntityDto : IEntityDto<TKey>
|
||||
{
|
||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||
: base(repository)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CRUD应用服务基类 - 支持自定义查询输入
|
||||
/// </summary>
|
||||
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
|
||||
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
|
||||
where TEntity : class, IEntity<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>
|
||||
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
|
||||
where TEntity : class, IEntity<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>
|
||||
: YiCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||
where TEntity : class, IEntity<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, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TGetOutputDto : IEntityDto<TKey>
|
||||
where TGetListOutputDto : IEntityDto<TKey>
|
||||
where TEntity : class, IEntity<TKey>
|
||||
where TGetOutputDto : IEntityDto<TKey>
|
||||
where TGetListOutputDto : IEntityDto<TKey>
|
||||
{
|
||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
|
||||
/// <summary>
|
||||
/// 临时文件存储路径
|
||||
/// </summary>
|
||||
private const string TempFilePath = "/wwwroot/temp";
|
||||
|
||||
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
|
||||
: base(repository)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多查
|
||||
/// 更新实体
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="id">实体ID</param>
|
||||
/// <param name="input">更新输入</param>
|
||||
/// <returns>更新后的实体DTO</returns>
|
||||
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
|
||||
{
|
||||
// 检查更新权限
|
||||
await CheckUpdatePolicyAsync();
|
||||
|
||||
// 获取并验证实体
|
||||
var entity = await GetEntityByIdAsync(id);
|
||||
|
||||
// 检查更新输入
|
||||
await CheckUpdateInputDtoAsync(entity, input);
|
||||
|
||||
// 映射并更新实体
|
||||
await MapToEntityAsync(input, entity);
|
||||
await Repository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查更新输入数据的有效性
|
||||
/// </summary>
|
||||
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity, TUpdateInput input)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建实体
|
||||
/// </summary>
|
||||
/// <param name="input">创建输入</param>
|
||||
/// <returns>创建后的实体DTO</returns>
|
||||
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
|
||||
{
|
||||
// 检查创建权限
|
||||
await CheckCreatePolicyAsync();
|
||||
|
||||
// 检查创建输入
|
||||
await CheckCreateInputDtoAsync(input);
|
||||
|
||||
// 映射到实体
|
||||
var entity = await MapToEntityAsync(input);
|
||||
|
||||
// 设置租户ID
|
||||
TryToSetTenantId(entity);
|
||||
|
||||
// 插入实体
|
||||
await Repository.InsertAsync(entity, autoSave: true);
|
||||
|
||||
return await MapToGetOutputDtoAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查创建输入数据的有效性
|
||||
/// </summary>
|
||||
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实体列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入</param>
|
||||
/// <returns>分页结果</returns>
|
||||
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
|
||||
{
|
||||
List<TEntity>? entites = null;
|
||||
//区分多查还是批量查
|
||||
List<TEntity> entities;
|
||||
|
||||
// 根据输入类型决定查询方式
|
||||
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
|
||||
{
|
||||
entites = await Repository.GetListAsync();
|
||||
// 查询全部
|
||||
entities = await Repository.GetListAsync();
|
||||
}
|
||||
var total = await Repository.GetCountAsync();
|
||||
var output = await MapToGetListOutputDtosAsync(entites);
|
||||
return new PagedResultDto<TGetListOutputDto>(total, output);
|
||||
//throw new NotImplementedException($"【{typeof(TEntity)}】实体的CrudAppService,查询为具体业务,通用查询几乎无实际场景,请重写实现!");
|
||||
|
||||
// 获取总数并映射结果
|
||||
var totalCount = await Repository.GetCountAsync();
|
||||
var dtos = await MapToGetListOutputDtosAsync(entities);
|
||||
|
||||
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多删
|
||||
/// 获取实体动态下拉框列表,子类重写该方法,通过 keywords 进行筛选
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="keywords">查询关键字</param>
|
||||
/// <returns></returns>
|
||||
[RemoteService(isEnabled: true)]
|
||||
public virtual async Task DeleteAsync(IEnumerable<TKey> id)
|
||||
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetSelectDataListAsync(string? keywords = null)
|
||||
{
|
||||
await Repository.DeleteManyAsync(id);
|
||||
List<TEntity> entities = await Repository.GetListAsync();
|
||||
|
||||
// 获取总数并映射结果
|
||||
var totalCount = entities.Count;
|
||||
var dtos = await MapToGetListOutputDtosAsync(entities);
|
||||
|
||||
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 偷梁换柱
|
||||
/// 批量删除实体
|
||||
/// </summary>
|
||||
/// <param name="ids">实体ID集合</param>
|
||||
[RemoteService(isEnabled: true)]
|
||||
public virtual async Task DeleteAsync(IEnumerable<TKey> ids)
|
||||
{
|
||||
await Repository.DeleteManyAsync(ids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个删除实体(禁用远程访问)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[RemoteService(isEnabled: false)]
|
||||
public override Task DeleteAsync(TKey id)
|
||||
{
|
||||
return base.DeleteAsync(id);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导出excel
|
||||
/// 导出Excel
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>Excel文件</returns>
|
||||
public virtual async Task<IActionResult> GetExportExcelAsync(TGetListInput input)
|
||||
{
|
||||
// 重置分页参数以获取全部数据
|
||||
if (input is IPagedResultRequest paged)
|
||||
{
|
||||
paged.SkipCount = 0;
|
||||
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(dirPath))
|
||||
// 确保临时目录存在
|
||||
if (!Directory.Exists(TempFilePath))
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入excle
|
||||
/// 生成Excel文件名
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task PostImportExcelAsync(List<TCreateInput> input)
|
||||
private string GenerateExcelFileName()
|
||||
{
|
||||
var entities = input.Select(x => MapToEntity(x)).ToList();
|
||||
//安全起见,该接口需要自己实现
|
||||
throw new NotImplementedException();
|
||||
//await Repository.DeleteManyAsync(entities.Select(x => x.Id));
|
||||
//await Repository.InsertManyAsync(entities);
|
||||
return $"{typeof(TEntity).Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Guid.NewGuid()}.xlsx";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入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
|
||||
{
|
||||
[DependsOn(typeof(AbpDddApplicationModule),
|
||||
typeof(YiFrameworkDddApplicationContractsModule))]
|
||||
/// <summary>
|
||||
/// Yi框架DDD应用层模块
|
||||
/// </summary>
|
||||
[DependsOn(
|
||||
typeof(AbpDddApplicationModule),
|
||||
typeof(YiFrameworkDddApplicationContractsModule)
|
||||
)]
|
||||
public class YiFrameworkDddApplicationModule : AbpModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序初始化配置
|
||||
/// </summary>
|
||||
/// <param name="context">应用程序初始化上下文</param>
|
||||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
//分页限制
|
||||
// 配置分页查询的默认值和最大值限制
|
||||
ConfigureDefaultPagingSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置默认分页设置
|
||||
/// </summary>
|
||||
private void ConfigureDefaultPagingSettings()
|
||||
{
|
||||
// 设置默认每页显示记录数
|
||||
LimitedResultRequestDto.DefaultMaxResultCount = 10;
|
||||
|
||||
// 设置最大允许的每页记录数
|
||||
LimitedResultRequestDto.MaxMaxResultCount = 10000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,37 @@ using Volo.Abp.ObjectMapping;
|
||||
|
||||
namespace Yi.Framework.Mapster
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapster自动对象映射提供程序
|
||||
/// 实现IAutoObjectMappingProvider接口,提供对象间的自动映射功能
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
var sss = typeof(TDestination).Name;
|
||||
// 使用Mapster的Adapt方法进行对象映射
|
||||
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)
|
||||
{
|
||||
return source.Adapt<TSource, TDestination>(destination);
|
||||
// 使用Mapster的Adapt方法进行对象映射,保留目标对象的实例
|
||||
return source.Adapt(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,51 @@ using Volo.Abp.ObjectMapping;
|
||||
|
||||
namespace Yi.Framework.Mapster
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapster对象映射器
|
||||
/// 实现IObjectMapper接口,提供对象映射功能
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return AutoObjectMappingProvider.Map(source, destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
using MapsterMapper;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.ObjectMapping;
|
||||
using Yi.Framework.Core;
|
||||
|
||||
namespace Yi.Framework.Mapster
|
||||
{
|
||||
[DependsOn(typeof(YiFrameworkCoreModule),
|
||||
|
||||
/// <summary>
|
||||
/// Yi框架Mapster模块
|
||||
/// 用于配置和注册Mapster相关服务
|
||||
/// </summary>
|
||||
[DependsOn(
|
||||
typeof(YiFrameworkCoreModule),
|
||||
typeof(AbpObjectMappingModule)
|
||||
)]
|
||||
)]
|
||||
public class YiFrameworkMapsterModule : AbpModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置服务
|
||||
/// </summary>
|
||||
/// <param name="context">服务配置上下文</param>
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
context.Services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
|
||||
var services = context.Services;
|
||||
|
||||
// 注册Mapster相关服务
|
||||
services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
|
||||
services.AddTransient<IObjectMapper, MapsterObjectMapper>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\common.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Yi.Framework.Core\Yi.Framework.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.57.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Yi.Framework.Core.Options;
|
||||
|
||||
namespace Yi.Framework.SemanticKernel;
|
||||
|
||||
public class YiFrameworkSemanticKernelModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
var services = context.Services;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
using SqlSugar;
|
||||
using ArgumentException = System.ArgumentException;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库连接配置选项
|
||||
/// </summary>
|
||||
public class DbConnOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接字符串(如果开启多租户,也就是默认库了),必填
|
||||
/// 主数据库连接字符串
|
||||
/// 如果开启多租户,此为默认租户数据库
|
||||
/// </summary>
|
||||
public string? Url { get; set; }
|
||||
|
||||
@@ -15,41 +20,48 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
public DbType? DbType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启种子数据
|
||||
/// 是否启用种子数据初始化
|
||||
/// </summary>
|
||||
public bool EnabledDbSeed { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用驼峰命名转下划线命名
|
||||
/// </summary>
|
||||
public bool EnableUnderLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启codefirst
|
||||
/// 是否启用Code First模式
|
||||
/// </summary>
|
||||
public bool EnabledCodeFirst { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启sql日志
|
||||
/// 是否启用SQL日志记录
|
||||
/// </summary>
|
||||
public bool EnabledSqlLog { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 实体程序集
|
||||
/// 实体类所在程序集名称列表
|
||||
/// </summary>
|
||||
public List<string>? EntityAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启读写分离
|
||||
/// 是否启用读写分离
|
||||
/// </summary>
|
||||
public bool EnabledReadWrite { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 读写分离
|
||||
/// 只读数据库连接字符串列表
|
||||
/// </summary>
|
||||
public List<string>? ReadUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启Saas多租户
|
||||
/// 是否启用SaaS多租户
|
||||
/// </summary>
|
||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 并发乐观锁异常,否则不处理
|
||||
/// </summary>
|
||||
public bool EnabledConcurrencyException { get; set; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ using System.Linq;
|
||||
using System.Text;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar数据库上下文接口
|
||||
/// </summary>
|
||||
public interface ISqlSugarDbContext
|
||||
{
|
||||
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||
ISqlSugarClient SqlSugarClient { get; }
|
||||
DbConnOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库备份
|
||||
/// 获取SqlSugar客户端实例
|
||||
/// </summary>
|
||||
ISqlSugarClient SqlSugarClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行数据库备份
|
||||
/// </summary>
|
||||
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 Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
|
||||
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity> where TEntity : class, IEntity,new ()
|
||||
/// <summary>
|
||||
/// 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; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询构造器
|
||||
/// </summary>
|
||||
ISugarQueryable<TEntity> _DbQueryable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取数据库上下文
|
||||
/// </summary>
|
||||
Task<ISqlSugarClient> GetDbContextAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取删除操作构造器
|
||||
/// </summary>
|
||||
Task<IDeleteable<TEntity>> AsDeleteable();
|
||||
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> insertObjs);
|
||||
Task<IInsertable<TEntity>> AsInsertable(TEntity insertObj);
|
||||
Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs);
|
||||
|
||||
/// <summary>
|
||||
/// 获取插入操作构造器
|
||||
/// </summary>
|
||||
Task<IInsertable<TEntity>> AsInsertable(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// 获取批量插入操作构造器
|
||||
/// </summary>
|
||||
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> entities);
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询构造器
|
||||
/// </summary>
|
||||
Task<ISugarQueryable<TEntity>> AsQueryable();
|
||||
|
||||
/// <summary>
|
||||
/// 获取SqlSugar客户端
|
||||
/// </summary>
|
||||
Task<ISqlSugarClient> AsSugarClient();
|
||||
|
||||
/// <summary>
|
||||
/// 获取租户操作接口
|
||||
/// </summary>
|
||||
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(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> 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(Expression<Func<TEntity, bool>> whereExpression);
|
||||
|
||||
/// <summary>
|
||||
/// 获取满足条件的所有实体
|
||||
/// </summary>
|
||||
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate);
|
||||
|
||||
#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
|
||||
|
||||
#region 插入
|
||||
//插入
|
||||
Task<bool> InsertAsync(TEntity insertObj);
|
||||
Task<bool> InsertOrUpdateAsync(TEntity data);
|
||||
Task<bool> InsertOrUpdateAsync(List<TEntity> datas);
|
||||
Task<int> InsertReturnIdentityAsync(TEntity insertObj);
|
||||
Task<long> InsertReturnBigIdentityAsync(TEntity insertObj);
|
||||
Task<long> InsertReturnSnowflakeIdAsync(TEntity insertObj);
|
||||
Task<TEntity> InsertReturnEntityAsync(TEntity insertObj);
|
||||
Task<bool> InsertRangeAsync(List<TEntity> insertObjs);
|
||||
#region 插入操作
|
||||
|
||||
/// <summary>
|
||||
/// 插入实体
|
||||
/// </summary>
|
||||
Task<bool> InsertAsync(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// 插入或更新实体
|
||||
/// </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
|
||||
|
||||
#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
|
||||
|
||||
#region 删除
|
||||
//删除
|
||||
Task<bool> DeleteAsync(TEntity deleteObj);
|
||||
Task<bool> DeleteAsync(List<TEntity> deleteObjs);
|
||||
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> whereExpression);
|
||||
#region 删除操作
|
||||
|
||||
/// <summary>
|
||||
/// 删除实体
|
||||
/// </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> DeleteByIdsAsync(dynamic[] ids);
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 根据主键批量删除
|
||||
/// </summary>
|
||||
Task<bool> DeleteByIdsAsync(dynamic[] ids);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public interface ISqlSugarRepository<TEntity, TKey> : ISqlSugarRepository<TEntity>,IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar数据库上下文提供者接口
|
||||
/// </summary>
|
||||
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
|
||||
public interface ISugarDbContextProvider<TDbContext>
|
||||
where TDbContext : ISqlSugarDbContext
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取数据库上下文实例
|
||||
/// </summary>
|
||||
/// <returns>数据库上下文实例</returns>
|
||||
Task<TDbContext> GetDbContextAsync();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 忽略CodeFirst特性
|
||||
/// 标记此特性的实体类将不会被CodeFirst功能扫描
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class IgnoreCodeFirstAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlSugarCoreNoDrive" Version="$(SqlSugarVersion)" />
|
||||
<!-- <PackageReference Include="SqlSugarCoreNoDrive" Version="$(SqlSugarVersion)" />-->
|
||||
|
||||
<PackageReference Include="SqlSugarCore" Version="$(SqlSugarVersion)" />
|
||||
|
||||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -3,9 +3,13 @@ using Yi.Framework.Core;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar Core抽象层模块
|
||||
/// 提供SqlSugar ORM的基础抽象接口和类型定义
|
||||
/// </summary>
|
||||
[DependsOn(typeof(YiFrameworkCoreModule))]
|
||||
public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule
|
||||
{
|
||||
|
||||
// 模块配置方法可在此添加
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,34 @@
|
||||
|
||||
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();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当前数据库上下文
|
||||
/// </summary>
|
||||
public ISqlSugarDbContext? Current
|
||||
{
|
||||
get => _currentScope.Value;
|
||||
set => _currentScope.Value = value;
|
||||
}
|
||||
public AsyncLocalDbContextAccessor()
|
||||
|
||||
/// <summary>
|
||||
/// 初始化异步本地数据库上下文访问器
|
||||
/// </summary>
|
||||
private AsyncLocalDbContextAccessor()
|
||||
{
|
||||
_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,8 +1,14 @@
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Nito.AsyncEx;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Linq;
|
||||
@@ -11,62 +17,73 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
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()
|
||||
{
|
||||
public ISqlSugarClient _Db => GetDbContextAsync().Result;
|
||||
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
|
||||
|
||||
public ISugarQueryable<TEntity> _DbQueryable => GetDbContextAsync().Result.Queryable<TEntity>();
|
||||
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
|
||||
|
||||
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
|
||||
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
|
||||
|
||||
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
/// <summary>
|
||||
/// 异步查询执行器
|
||||
/// </summary>
|
||||
public IAsyncQueryableExecuter AsyncExecuter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用变更追踪
|
||||
/// </summary>
|
||||
public bool? IsChangeTrackingEnabled => false;
|
||||
|
||||
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider)
|
||||
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider)
|
||||
{
|
||||
_sugarDbContextProvider = sugarDbContextProvider;
|
||||
_dbContextProvider = dbContextProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取DB
|
||||
/// 获取数据库上下文
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
|
||||
{
|
||||
|
||||
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
|
||||
//await Console.Out.WriteLineAsync("获取的id:" + db.ContextID);
|
||||
return db;
|
||||
var dbContext = await _dbContextProvider.GetDbContextAsync();
|
||||
return dbContext.SqlSugarClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取简单Db
|
||||
/// 获取简单数据库客户端
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync()
|
||||
{
|
||||
var db = await GetDbContextAsync();
|
||||
return new SimpleClient<TEntity>(db);
|
||||
var dbContext = await GetDbContextAsync();
|
||||
return new SimpleClient<TEntity>(dbContext);
|
||||
}
|
||||
|
||||
#region Abp模块
|
||||
|
||||
public virtual async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate,
|
||||
bool includeDetails = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetFirstAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate,
|
||||
bool includeDetails = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetFirstAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await this.DeleteAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
public virtual async Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await this.DeleteAsync(predicate);
|
||||
}
|
||||
@@ -96,60 +113,71 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate,
|
||||
bool includeDetails = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetListAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await InsertReturnEntityAsync(entity);
|
||||
}
|
||||
|
||||
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await InsertRangeAsync(entities.ToList());
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await UpdateAsync(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await UpdateRangeAsync(entities.ToList());
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task DeleteAsync(TEntity entity, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DeleteAsync(entity);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DeleteAsync(entities.ToList());
|
||||
}
|
||||
|
||||
public virtual async Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<List<TEntity>> GetListAsync(bool includeDetails = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetListAsync();
|
||||
}
|
||||
|
||||
public virtual async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await this.CountAsync(_=>true);
|
||||
return await this.CountAsync(_ => true);
|
||||
}
|
||||
|
||||
public virtual async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting,
|
||||
bool includeDetails = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetPageListAsync(_ => true, skipCount, maxResultCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 内置DB快捷操作
|
||||
|
||||
public virtual async Task<IDeleteable<TEntity>> AsDeleteable()
|
||||
{
|
||||
return (await GetDbSimpleClientAsync()).AsDeleteable();
|
||||
@@ -204,9 +232,11 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return (await GetDbSimpleClientAsync()).AsUpdateable(updateObjs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SimpleClient模块
|
||||
|
||||
public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).CountAsync(whereExpression);
|
||||
@@ -223,7 +253,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).DeleteAsync(deleteObj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual async Task<bool> DeleteAsync(List<TEntity> deleteObjs)
|
||||
@@ -243,13 +272,13 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete.IsDeleted), true).Where(whereExpression).ExecuteCommandAsync() > 0;
|
||||
return await (await GetDbSimpleClientAsync()).AsUpdateable()
|
||||
.SetColumns(nameof(ISoftDelete.IsDeleted), true).Where(whereExpression).ExecuteCommandAsync() > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).DeleteAsync(whereExpression);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual async Task<bool> DeleteByIdAsync(dynamic id)
|
||||
@@ -257,6 +286,11 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//反射赋值
|
||||
ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
|
||||
return await UpdateAsync(entity);
|
||||
@@ -277,6 +311,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//反射赋值
|
||||
entities.ForEach(e => ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, e));
|
||||
return await UpdateRangeAsync(entities);
|
||||
@@ -285,7 +320,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).DeleteByIdAsync(ids);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> GetByIdAsync(dynamic id)
|
||||
@@ -294,7 +328,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
}
|
||||
|
||||
|
||||
|
||||
public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).GetFirstAsync(whereExpression);
|
||||
@@ -310,14 +343,19 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
return await (await GetDbSimpleClientAsync()).GetListAsync(whereExpression);
|
||||
}
|
||||
|
||||
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 GetDbSimpleClientAsync()).GetPageListAsync(whereExpression,
|
||||
new PageModel() { PageIndex = pageNum, PageSize = 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 GetDbSimpleClientAsync()).GetPageListAsync(whereExpression,
|
||||
new PageModel { PageIndex = pageNum, PageSize = pageSize }, orderByExpression, orderByType);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
@@ -372,16 +410,40 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
|
||||
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
|
||||
{
|
||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
|
||||
{
|
||||
if (Options is not null && Options.EnabledConcurrencyException)
|
||||
{
|
||||
try
|
||||
{
|
||||
int num = await (await GetDbSimpleClientAsync())
|
||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||
return num > 0;
|
||||
}
|
||||
catch (VersionExceptions ex)
|
||||
{
|
||||
throw new AbpDbConcurrencyException(
|
||||
$"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = await (await GetDbSimpleClientAsync())
|
||||
.Context.Updateable(updateObj).ExecuteCommandAsync();
|
||||
return num > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
||||
}
|
||||
|
||||
public virtual async Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression)
|
||||
public virtual async Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns,
|
||||
Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).UpdateAsync(columns, whereExpression);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public virtual async Task<bool> UpdateRangeAsync(List<TEntity> updateObjs)
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).UpdateRangeAsync(updateObjs);
|
||||
@@ -390,30 +452,36 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>, IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
|
||||
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>,
|
||||
IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
|
||||
{
|
||||
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(sugarDbContextProvider)
|
||||
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider) : base(
|
||||
dbContextProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task DeleteAsync(TKey id, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DeleteByIdAsync(id);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
|
||||
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Select(x => (object)x).ToArray());
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity?> FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<TEntity?> FindAsync(TKey id, bool includeDetails = true,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetByIdAsync(id);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> GetAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<TEntity> GetAsync(TKey id, bool includeDetails = true,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetByIdAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,114 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
|
||||
public class SqlSugarDbConnectionCreator: ISqlSugarDbConnectionCreator,ITransientDependency
|
||||
{
|
||||
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
|
||||
{
|
||||
Options = options.Value;
|
||||
}
|
||||
public DbConnOptions Options { get; }
|
||||
|
||||
public void SetDbAop(ISqlSugarClient currentDb)
|
||||
{
|
||||
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
|
||||
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
|
||||
currentDb.Aop.DataExecuting = this.DataExecuting;
|
||||
currentDb.Aop.DataExecuted = this.DataExecuted;
|
||||
OnSqlSugarClientConfig(currentDb);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ConnectionConfig Build(Action<ConnectionConfig>? action=null)
|
||||
{
|
||||
var dbConnOptions = Options;
|
||||
#region 组装options
|
||||
if (dbConnOptions.DbType is null)
|
||||
{
|
||||
throw new ArgumentException("DbType配置为空");
|
||||
}
|
||||
var slavaConFig = new List<SlaveConnectionConfig>();
|
||||
if (dbConnOptions.EnabledReadWrite)
|
||||
{
|
||||
if (dbConnOptions.ReadUrl is null)
|
||||
{
|
||||
throw new ArgumentException("读写分离为空");
|
||||
}
|
||||
|
||||
var readCon = dbConnOptions.ReadUrl;
|
||||
|
||||
readCon.ForEach(s =>
|
||||
{
|
||||
//如果是动态saas分库,这里的连接串都不能写死,需要动态添加,这里只配置共享库的连接
|
||||
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 组装连接config
|
||||
var connectionConfig = new ConnectionConfig()
|
||||
{
|
||||
ConfigId= ConnectionStrings.DefaultConnectionStringName,
|
||||
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
|
||||
ConnectionString = dbConnOptions.Url,
|
||||
IsAutoCloseConnection = true,
|
||||
SlaveConnectionConfigs = slavaConFig,
|
||||
//设置codefirst非空值判断
|
||||
ConfigureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityService = (c, p) =>
|
||||
{
|
||||
if (new NullabilityInfoContext()
|
||||
.Create(c).WriteState is NullabilityState.Nullable)
|
||||
{
|
||||
p.IsNullable = true;
|
||||
}
|
||||
|
||||
EntityService(c, p);
|
||||
}
|
||||
},
|
||||
//这里多租户有个坑,无效的
|
||||
AopEvents = new AopEvents
|
||||
{
|
||||
DataExecuted = DataExecuted,
|
||||
DataExecuting = DataExecuting,
|
||||
OnLogExecuted = OnLogExecuted,
|
||||
OnLogExecuting = OnLogExecuting
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (action is not null)
|
||||
{
|
||||
action.Invoke(connectionConfig);
|
||||
}
|
||||
#endregion
|
||||
return connectionConfig;
|
||||
}
|
||||
[DisablePropertyInjection]
|
||||
public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<object, DataAfterModel> DataExecuted { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<object, DataFilterModel> DataExecuting { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
|
||||
|
||||
[DisablePropertyInjection]
|
||||
public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,376 +1,83 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Security.Policy;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Reflection;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Entities.Events;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
/// <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>
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
|
||||
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
|
||||
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
|
||||
public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
|
||||
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
|
||||
|
||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||
|
||||
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
public AbpDbConnectionOptions ConnectionOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbConnectionOptions>>().Value;
|
||||
|
||||
public ISerializeService SerializeService=> LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
|
||||
|
||||
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
SqlSugarClient = sqlSugarClient;
|
||||
}
|
||||
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
|
||||
_dbConnectionCreator = connectionCreator;
|
||||
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
|
||||
connectionCreator.EntityService = EntityService;
|
||||
connectionCreator.DataExecuting = DataExecuting;
|
||||
connectionCreator.DataExecuted = DataExecuted;
|
||||
connectionCreator.OnLogExecuting = OnLogExecuting;
|
||||
connectionCreator.OnLogExecuted = OnLogExecuted;
|
||||
SqlSugarClient = new SqlSugarClient(connectionCreator.Build(action: options =>
|
||||
{
|
||||
options.ConnectionString = GetCurrentConnectionString();
|
||||
options.DbType = GetCurrentDbType();
|
||||
}));
|
||||
connectionCreator.SetDbAop(SqlSugarClient);
|
||||
//替换默认序列化器
|
||||
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// db切换多库支持
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string GetCurrentConnectionString()
|
||||
{
|
||||
var defautlUrl = Options.Url ?? ConnectionOptions.GetConnectionStringOrNull(ConnectionStrings.DefaultConnectionStringName);
|
||||
//如果未开启多租户,返回db url 或者 默认连接字符串
|
||||
if (!Options.EnabledSaasMultiTenancy)
|
||||
{
|
||||
return defautlUrl;
|
||||
}
|
||||
|
||||
//开启了多租户
|
||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
||||
var connectionString = connectionStringResolver.ResolveAsync().GetAwaiter().GetResult();
|
||||
|
||||
|
||||
//没有检测到使用多租户功能,默认使用默认库即可
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
|
||||
connectionString = defautlUrl;
|
||||
}
|
||||
return connectionString!;
|
||||
}
|
||||
|
||||
protected virtual DbType GetCurrentDbType()
|
||||
{
|
||||
if (CurrentTenant.Name is not null)
|
||||
{
|
||||
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
|
||||
if (dbTypeFromTenantName is not null)
|
||||
{
|
||||
return dbTypeFromTenantName.Value;
|
||||
}
|
||||
}
|
||||
Volo.Abp.Check.NotNull(Options.DbType, "默认DbType未配置!");
|
||||
return Options.DbType!.Value;
|
||||
}
|
||||
|
||||
//根据租户name进行匹配db类型: Test_Sqlite,[来自AI]
|
||||
private DbType? GetDbTypeFromTenantName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找下划线的位置
|
||||
int underscoreIndex = name.LastIndexOf('_');
|
||||
|
||||
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取 枚举 部分
|
||||
string enumString = name.Substring(underscoreIndex + 1);
|
||||
|
||||
// 尝试将 尾缀 转换为枚举
|
||||
if (Enum.TryParse<DbType>(enumString, out DbType result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 条件不满足时返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 上下文对象扩展
|
||||
/// </summary>
|
||||
/// <param name="sqlSugarClient"></param>
|
||||
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
//需自定义扩展
|
||||
if (IsSoftDeleteFilterEnabled)
|
||||
{
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
|
||||
}
|
||||
if (IsMultiTenantFilterEnabled)
|
||||
{
|
||||
//表达式不能放方法
|
||||
Guid? tenantId = CurrentTenant?.Id;
|
||||
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == tenantId);
|
||||
}
|
||||
CustomDataFilter(sqlSugarClient);
|
||||
}
|
||||
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
|
||||
}
|
||||
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
/// <param name="oldValue"></param>
|
||||
/// <param name="entityInfo"></param>
|
||||
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
//审计日志
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.UpdateByObject:
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
|
||||
{
|
||||
if (!DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
|
||||
{
|
||||
//主键为空或者为默认最小值
|
||||
if (Guid.Empty.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(GuidGenerator.Create());
|
||||
}
|
||||
}
|
||||
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
|
||||
{
|
||||
//为空或者为默认最小值
|
||||
if (oldValue is null || DateTime.MinValue.Equals(oldValue))
|
||||
{
|
||||
entityInfo.SetValue(DateTime.Now);
|
||||
}
|
||||
}
|
||||
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
|
||||
{
|
||||
if (CurrentUser.Id != null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentUser.Id);
|
||||
}
|
||||
}
|
||||
|
||||
//插入时,需要租户id,先预留
|
||||
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
|
||||
{
|
||||
if (CurrentTenant is not null)
|
||||
{
|
||||
entityInfo.SetValue(CurrentTenant.Id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//领域事件
|
||||
switch (entityInfo.OperationType)
|
||||
{
|
||||
case DataFilterType.InsertByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
break;
|
||||
case DataFilterType.UpdateByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//软删除,发布的是删除事件
|
||||
if (entityInfo.EntityValue is ISoftDelete softDelete)
|
||||
{
|
||||
if (softDelete.IsDeleted == true)
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case DataFilterType.DeleteByObject:
|
||||
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
|
||||
{
|
||||
//这里sqlsugar有个特殊,删除会返回批量的结果
|
||||
if (entityInfo.EntityValue is IEnumerable entityValues)
|
||||
{
|
||||
foreach (var entityValue in entityValues)
|
||||
{
|
||||
|
||||
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="pars"></param>
|
||||
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==========Yi-SQL执行:==========");
|
||||
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
|
||||
sb.AppendLine("===============================");
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="pars"></param>
|
||||
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
if (Options.EnabledSqlLog)
|
||||
{
|
||||
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
|
||||
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体配置
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="column"></param>
|
||||
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
|
||||
{
|
||||
if (property.Name == "ConcurrencyStamp")
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
if (property.PropertyType == typeof(ExtraPropertyDictionary))
|
||||
{
|
||||
column.IsIgnore = true;
|
||||
}
|
||||
if (property.Name == nameof(Entity<object>.Id))
|
||||
{
|
||||
column.IsPrimarykey = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void BackupDataBase()
|
||||
{
|
||||
string directoryName = "database_backup";
|
||||
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
|
||||
if (!Directory.Exists(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
switch (Options.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
//MySql
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.sql");//mysql 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.Sqlite:
|
||||
//Sqlite
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
|
||||
break;
|
||||
|
||||
|
||||
case DbType.SqlServer:
|
||||
//SqlServer
|
||||
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, $"{Path.Combine(directoryName, fileName)}.bak"/*服务器路径*/);//第一个参数库名
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("其他数据库备份未实现");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置SqlSugar客户端
|
||||
/// </summary>
|
||||
public virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
SqlSugarClient = sqlSugarClient;
|
||||
CustomDataFilter(sqlSugarClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义数据过滤器
|
||||
/// </summary>
|
||||
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据执行后事件
|
||||
/// </summary>
|
||||
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据执行前事件
|
||||
/// </summary>
|
||||
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SQL执行前事件
|
||||
/// </summary>
|
||||
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SQL执行后事件
|
||||
/// </summary>
|
||||
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体服务配置
|
||||
/// </summary>
|
||||
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,26 +4,52 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore;
|
||||
|
||||
/// <summary>
|
||||
/// SqlSugar数据库上下文创建上下文
|
||||
/// </summary>
|
||||
public class SqlSugarDbContextCreationContext
|
||||
{
|
||||
public static SqlSugarDbContextCreationContext Current => _current.Value;
|
||||
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> _current = new AsyncLocal<SqlSugarDbContextCreationContext>();
|
||||
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> CurrentContextHolder =
|
||||
new AsyncLocal<SqlSugarDbContextCreationContext>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前上下文
|
||||
/// </summary>
|
||||
public static SqlSugarDbContextCreationContext Current => CurrentContextHolder.Value!;
|
||||
|
||||
/// <summary>
|
||||
/// 连接字符串名称
|
||||
/// </summary>
|
||||
public string ConnectionStringName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接字符串
|
||||
/// </summary>
|
||||
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;
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的上下文
|
||||
/// </summary>
|
||||
public static IDisposable Use(SqlSugarDbContextCreationContext context)
|
||||
{
|
||||
var previousValue = Current;
|
||||
_current.Value = context;
|
||||
return new DisposeAction(() => _current.Value = previousValue);
|
||||
var previousContext = Current;
|
||||
CurrentContextHolder.Value = context;
|
||||
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 方法序列化对象
|
||||
T json = (T)methods.MakeGenericMethod(typeof(T))
|
||||
.Invoke(null, new object[] { value, null });
|
||||
return json;
|
||||
.Invoke(null, new object[] { value, null! });
|
||||
return json!;
|
||||
}
|
||||
var jSetting = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect
|
||||
};
|
||||
return JsonConvert.DeserializeObject<T>(value, jSetting);
|
||||
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,13 +8,23 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Uow
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar数据库API实现
|
||||
/// </summary>
|
||||
public class SqlSugarDatabaseApi : IDatabaseApi
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库上下文
|
||||
/// </summary>
|
||||
public ISqlSugarDbContext DbContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化SqlSugar数据库API
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文</param>
|
||||
public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext)
|
||||
{
|
||||
DbContext = dbContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,34 +3,48 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Uow
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar事务API实现
|
||||
/// </summary>
|
||||
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()
|
||||
{
|
||||
|
||||
return _sqlsugarDbContext;
|
||||
return _dbContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交事务
|
||||
/// </summary>
|
||||
public async Task CommitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sqlsugarDbContext.SqlSugarClient.Ado.Dispose();
|
||||
await _dbContext.SqlSugarClient.Ado.CommitTranAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回滚事务
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||
|
||||
/// <summary>
|
||||
/// 日志记录器
|
||||
/// </summary>
|
||||
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务提供者
|
||||
/// </summary>
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库上下文访问器实例
|
||||
/// </summary>
|
||||
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
|
||||
protected readonly IUnitOfWorkManager UnitOfWorkManager;
|
||||
protected readonly IConnectionStringResolver ConnectionStringResolver;
|
||||
protected readonly ICancellationTokenProvider CancellationTokenProvider;
|
||||
protected readonly ICurrentTenant CurrentTenant;
|
||||
|
||||
private readonly TenantConfigurationWrapper _tenantConfigurationWrapper;
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
private readonly IConnectionStringResolver _connectionStringResolver;
|
||||
private readonly ICancellationTokenProvider _cancellationTokenProvider;
|
||||
private readonly ICurrentTenant _currentTenant;
|
||||
|
||||
public UnitOfWorkSqlsugarDbContextProvider(
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
IConnectionStringResolver connectionStringResolver,
|
||||
ICancellationTokenProvider cancellationTokenProvider,
|
||||
ICurrentTenant currentTenant,
|
||||
ISqlSugarDbConnectionCreator dbConnectionCreator
|
||||
)
|
||||
ICurrentTenant currentTenant,
|
||||
TenantConfigurationWrapper tenantConfigurationWrapper)
|
||||
{
|
||||
UnitOfWorkManager = unitOfWorkManager;
|
||||
ConnectionStringResolver = connectionStringResolver;
|
||||
CancellationTokenProvider = cancellationTokenProvider;
|
||||
CurrentTenant = currentTenant;
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
_connectionStringResolver = connectionStringResolver;
|
||||
_cancellationTokenProvider = cancellationTokenProvider;
|
||||
_currentTenant = currentTenant;
|
||||
_tenantConfigurationWrapper = tenantConfigurationWrapper;
|
||||
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
|
||||
_dbConnectionCreator = dbConnectionCreator;
|
||||
}
|
||||
|
||||
//private static object _databaseApiLock = new object();
|
||||
/// <summary>
|
||||
/// 获取数据库上下文
|
||||
/// </summary>
|
||||
public virtual async Task<TDbContext> GetDbContextAsync()
|
||||
{
|
||||
// 获取当前租户配置
|
||||
var tenantConfiguration = await _tenantConfigurationWrapper.GetAsync();
|
||||
|
||||
// 获取连接字符串信息
|
||||
var connectionStringName = tenantConfiguration.GetCurrentConnectionName();
|
||||
var connectionString = tenantConfiguration.GetCurrentConnectionString();
|
||||
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
|
||||
|
||||
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
|
||||
|
||||
//获取当前连接字符串,未多租户时,默认为空
|
||||
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
|
||||
var dbContextKey = $"{this.GetType().FullName}_{connectionString}";
|
||||
|
||||
|
||||
var unitOfWork = UnitOfWorkManager.Current;
|
||||
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/)
|
||||
var unitOfWork = _unitOfWorkManager.Current;
|
||||
if (unitOfWork == null)
|
||||
{
|
||||
var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
|
||||
//提高体验,取消工作单元强制性
|
||||
//throw new AbpException("A DbContext can only be created inside a unit of work!");
|
||||
//如果不启用工作单元,创建一个新的db,不开启事务即可
|
||||
return dbContext;
|
||||
throw new AbpException(
|
||||
"DbContext 只能在工作单元内工作,当前DbContext没有工作单元,如需创建新线程并发操作,请手动创建工作单元");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//尝试当前工作单元获取db
|
||||
// 尝试从当前工作单元获取数据库API
|
||||
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
||||
|
||||
//当前没有db创建一个新的db
|
||||
// 当前没有数据库API则创建新的
|
||||
if (databaseApi == null)
|
||||
{
|
||||
//db根据连接字符串来创建
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
|
||||
/// <summary>
|
||||
/// 创建数据库上下文
|
||||
/// </summary>
|
||||
protected virtual async Task<TDbContext> CreateDbContextAsync(
|
||||
IUnitOfWork unitOfWork,
|
||||
string connectionStringName,
|
||||
string connectionString)
|
||||
{
|
||||
var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString);
|
||||
//将连接key进行传值
|
||||
using (SqlSugarDbContextCreationContext.Use(creationContext))
|
||||
{
|
||||
var dbContext = await CreateDbContextAsync(unitOfWork);
|
||||
return dbContext;
|
||||
return await CreateDbContextAsync(unitOfWork);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据工作单元创建数据库上下文
|
||||
/// </summary>
|
||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
|
||||
{
|
||||
return unitOfWork.Options.IsTransactional
|
||||
? await CreateDbContextWithTransactionAsync(unitOfWork)
|
||||
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建带事务的数据库上下文
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
//该db还没有进行开启事务
|
||||
if (activeTransaction == null)
|
||||
{
|
||||
//获取到db添加事务即可
|
||||
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
var transaction = new SqlSugarTransactionApi(
|
||||
dbContext
|
||||
);
|
||||
var transaction = new SqlSugarTransactionApi(dbContext);
|
||||
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
||||
|
||||
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
||||
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,106 +6,190 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SqlSugar;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.Domain;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.MultiTenancy.ConfigurationStore;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
using Yi.Framework.SqlSugarCore.Repositories;
|
||||
using Yi.Framework.SqlSugarCore.Uow;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar Core模块
|
||||
/// </summary>
|
||||
[DependsOn(typeof(AbpDddDomainModule))]
|
||||
public class YiFrameworkSqlSugarCoreModule : AbpModule
|
||||
{
|
||||
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
|
||||
{
|
||||
var service = context.Services;
|
||||
var configuration = service.GetConfiguration();
|
||||
Configure<DbConnOptions>(configuration.GetSection("DbConnOptions"));
|
||||
var services = context.Services;
|
||||
var configuration = services.GetConfiguration();
|
||||
|
||||
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
|
||||
// 配置数据库连接选项
|
||||
ConfigureDbOptions(services, configuration);
|
||||
|
||||
//不开放sqlsugarClient
|
||||
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
|
||||
// 配置GUID生成器
|
||||
ConfigureGuidGenerator(services);
|
||||
|
||||
// 注册仓储和服务
|
||||
RegisterRepositories(services);
|
||||
|
||||
service.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
|
||||
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> ();
|
||||
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)
|
||||
{
|
||||
//进行CodeFirst
|
||||
var service = context.ServiceProvider;
|
||||
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
var _logger= service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
|
||||
var serviceProvider = context.ServiceProvider;
|
||||
var options = serviceProvider.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
var logger = serviceProvider.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)
|
||||
{
|
||||
CodeFirst(service);
|
||||
await InitializeDatabase(serviceProvider);
|
||||
}
|
||||
|
||||
// 初始化种子数据
|
||||
if (options.EnabledDbSeed)
|
||||
{
|
||||
await DataSeedAsync(service);
|
||||
await InitializeSeedData(serviceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
private void CodeFirst(IServiceProvider service)
|
||||
private void LogConfiguration(ILogger logger, DbConnOptions options)
|
||||
{
|
||||
var logMessage = new StringBuilder()
|
||||
.AppendLine()
|
||||
.AppendLine("==========Yi-SQL配置:==========")
|
||||
.AppendLine($"数据库连接字符串:{options.Url}")
|
||||
.AppendLine($"数据库类型:{options.DbType}")
|
||||
.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}")
|
||||
.AppendLine($"是否开启CodeFirst:{options.EnabledCodeFirst}")
|
||||
.AppendLine($"是否开启Saas多租户:{options.EnabledSaasMultiTenancy}")
|
||||
.AppendLine("===============================")
|
||||
.ToString();
|
||||
|
||||
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
||||
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
||||
logger.LogInformation(logMessage);
|
||||
}
|
||||
|
||||
//尝试创建数据库
|
||||
private async Task InitializeDatabase(IServiceProvider serviceProvider)
|
||||
{
|
||||
var moduleContainer = serviceProvider.GetRequiredService<IModuleContainer>();
|
||||
var db = serviceProvider.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
|
||||
|
||||
// 创建数据库
|
||||
db.DbMaintenance.CreateDatabase();
|
||||
|
||||
List<Type> types = new List<Type>();
|
||||
foreach (var module in moduleContainer.Modules)
|
||||
{
|
||||
types.AddRange(module.Assembly.GetTypes()
|
||||
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
|
||||
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
|
||||
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
||||
}
|
||||
if (types.Count > 0)
|
||||
{
|
||||
db.CopyNew().CodeFirst.InitTables(types.ToArray());
|
||||
}
|
||||
// 获取需要创建表的实体类型
|
||||
var entityTypes = moduleContainer.Modules
|
||||
.SelectMany(m => m.Assembly.GetTypes())
|
||||
.Where(t => t.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null
|
||||
&& t.GetCustomAttribute<SugarTable>() != null
|
||||
&& t.GetCustomAttribute<SplitTableAttribute>() == null)
|
||||
.ToList();
|
||||
|
||||
if (entityTypes.Any())
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user