mirror of
https://github.com/flipped-aurora/gin-vue-admin.git
synced 2026-09-25 05:50:19 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ccdc6295 | ||
|
|
d143b326b4 | ||
|
|
556b4431a2 | ||
|
|
9fea067931 | ||
|
|
fa9957c43c | ||
|
|
00224efd24 | ||
|
|
506c9ea7a6 | ||
|
|
05872354b8 | ||
|
|
b71cc443c0 | ||
|
|
1317e9504e | ||
|
|
670aff79cc | ||
|
|
52cd5e72ab | ||
|
|
68d6d3660b | ||
|
|
876017115c | ||
|
|
755373468c | ||
|
|
d2e64e618e | ||
|
|
fd2a6f45fd | ||
|
|
2242f5d6e1 | ||
|
|
567a8eb9bb | ||
|
|
2ebeec8765 | ||
|
|
70ee13e10c | ||
|
|
ed65e31cd8 | ||
|
|
f693f29326 | ||
|
|
2fda961643 | ||
|
|
fd9883e035 | ||
|
|
f41f068b8b | ||
|
|
799f411c73 | ||
|
|
97e7be9d3c | ||
|
|
5cfe04e5f5 | ||
|
|
5f0d59a02b | ||
|
|
805f319578 | ||
|
|
f23df92c18 | ||
|
|
41d9453110 | ||
|
|
48d0cae58b | ||
|
|
49af9439dc | ||
|
|
1994497ad4 | ||
|
|
4ce191cdfa | ||
|
|
ee8d8d7e04 | ||
|
|
cf022f492c | ||
|
|
96019e8a9b | ||
|
|
9ff1d4ca77 | ||
|
|
8bc631ece9 | ||
|
|
0edb567ded | ||
|
|
d404fcf746 | ||
|
|
8af78e3af7 | ||
|
|
3722a8e7f3 | ||
|
|
b86f7412d1 | ||
|
|
ad52418f99 | ||
|
|
08ae44158f | ||
|
|
a1f18e1386 | ||
|
|
c028ea3d15 | ||
|
|
f357c676bb | ||
|
|
8022c662e0 | ||
|
|
aa84605c4d | ||
|
|
287cb6ec9c | ||
|
|
b9788e9b2a | ||
|
|
a469ea23f4 | ||
|
|
c5f81d64c8 | ||
|
|
78d135c6bf | ||
|
|
ff06aca146 | ||
|
|
849d5760da | ||
|
|
8cce9edbde | ||
|
|
6bea7bc524 | ||
|
|
b6c0138dd2 | ||
|
|
7658ff46c3 | ||
|
|
bd1aa1f559 | ||
|
|
27fb85f630 | ||
|
|
0be88bb442 | ||
|
|
a8642f4582 | ||
|
|
5d67fc95d2 | ||
|
|
057ad2eeeb | ||
|
|
ca0a1250cd | ||
|
|
6c82b34e8b | ||
|
|
fdc5e3cbf6 | ||
|
|
a0643d8ded | ||
|
|
83fb5d5921 | ||
|
|
a1070a64e7 | ||
|
|
709cdef050 | ||
|
|
c95ece3fe2 | ||
|
|
d5befa1900 | ||
|
|
89ea284cc6 | ||
|
|
ffe1844ba6 | ||
|
|
d36eeb5134 | ||
|
|
b9009bbd48 | ||
|
|
5be32930f8 | ||
|
|
296c71290c | ||
|
|
2378971c8c | ||
|
|
5a30df558c | ||
|
|
2c21f3cd75 | ||
|
|
7d3e7b5b7a | ||
|
|
b01b27f1eb | ||
|
|
d40b1ba823 | ||
|
|
a1dfc360fc | ||
|
|
6da17433f2 | ||
|
|
32f21d8555 | ||
|
|
c59a0504ad | ||
|
|
182af9a1b6 | ||
|
|
424c5b23b4 | ||
|
|
86b420ce67 | ||
|
|
ea5709e5b6 | ||
|
|
4e9e3570ed | ||
|
|
78cdc287c5 | ||
|
|
58b123d316 | ||
|
|
fb199aeedc | ||
|
|
364dddb70a | ||
|
|
6bf848d53f | ||
|
|
3e429f4091 | ||
|
|
daabca4768 | ||
|
|
9e688c65d3 | ||
|
|
2e8f5870d8 | ||
|
|
8b0a00b98a | ||
|
|
1ce3e11d52 | ||
|
|
11aa8c5cc2 | ||
|
|
d8e6f2f5f4 | ||
|
|
9d5c93b4c3 | ||
|
|
2f7f3e5ac6 | ||
|
|
cb97d3832d | ||
|
|
dd34d6e3be | ||
|
|
1d700ad5b6 | ||
|
|
3648df9522 | ||
|
|
eb2925829f | ||
|
|
1e3c968277 | ||
|
|
339db778c3 | ||
|
|
8b8ae7b692 | ||
|
|
52be26eb43 | ||
|
|
9f958addc8 | ||
|
|
7a69b88885 | ||
|
|
76fe71a8dd | ||
|
|
3f21d7f8dd | ||
|
|
1fce169616 | ||
|
|
0d900293eb | ||
|
|
b71ad7b6f4 | ||
|
|
2139e38147 | ||
|
|
a033ab9abe | ||
|
|
311e1ddb9f | ||
|
|
31e6316916 | ||
|
|
9260111312 | ||
|
|
877f8f4fcf | ||
|
|
273d7bd50e | ||
|
|
39abc7b632 | ||
|
|
e19ddf6600 | ||
|
|
9db3bcfac5 | ||
|
|
a6b6b5d4aa | ||
|
|
23f9227da1 | ||
|
|
857ba194e5 | ||
|
|
6e27e3a114 | ||
|
|
d5b4bbfa84 | ||
|
|
72ee8fb6fc | ||
|
|
2ec2e9a66e | ||
|
|
bf095c38e9 | ||
|
|
cee3b54b4c | ||
|
|
ff7e002049 | ||
|
|
a1c8f6bc55 | ||
|
|
ca24945af6 | ||
|
|
ea7455e092 | ||
|
|
a5e1170af0 | ||
|
|
44751357b4 | ||
|
|
e58d40ab83 | ||
|
|
f7c3d76d37 | ||
|
|
55bf69c810 | ||
|
|
b4232a8f27 | ||
|
|
8d6dcc6b07 | ||
|
|
e3a57bdc69 | ||
|
|
0eee6a3cc2 | ||
|
|
174721bc55 | ||
|
|
ba63bf2371 | ||
|
|
2f07e9271f | ||
|
|
3e9602ff7e | ||
|
|
30763b48de | ||
|
|
c3b75933b8 | ||
|
|
ff08d45f25 | ||
|
|
be0e4e97f7 | ||
|
|
b916c7951b | ||
|
|
0ebb3d4195 | ||
|
|
e7680c43ac | ||
|
|
2f80eaaf9c | ||
|
|
2dfe9d7f4b | ||
|
|
cbc504cc0b | ||
|
|
99550cbc37 | ||
|
|
4a53864738 | ||
|
|
b731b9df78 | ||
|
|
1f3a8ea2eb | ||
|
|
2bd82ead97 | ||
|
|
9f1f72fce5 | ||
|
|
38ed8bb399 | ||
|
|
2d98fdf321 | ||
|
|
214e108591 | ||
|
|
b197a84ef7 | ||
|
|
09603c436e | ||
|
|
d4c883535b | ||
|
|
6323688fca | ||
|
|
7a7c811311 | ||
|
|
81e178b267 | ||
|
|
70c9a715d4 | ||
|
|
28bd6fef7e | ||
|
|
35e3f820c0 | ||
|
|
e847aa9e75 | ||
|
|
fcfa55b39a | ||
|
|
8be390b2cf | ||
|
|
3ad83cbcd6 | ||
|
|
41826f5e5c | ||
|
|
0ccd6dc750 | ||
|
|
8c2ce8587d | ||
|
|
5e35fcea58 | ||
|
|
54595d46e5 | ||
|
|
09cb29da17 | ||
|
|
ecf1b5bc56 | ||
|
|
da71231be9 | ||
|
|
63feecf4aa | ||
|
|
a6255557ee | ||
|
|
84a3c3ba7c | ||
|
|
90c8a240a1 | ||
|
|
07c694e24d | ||
|
|
7c1c392710 | ||
|
|
379f5fe841 | ||
|
|
0ab15cc5e2 | ||
|
|
8f7a8a9e6d | ||
|
|
d5dc42c71e | ||
|
|
6ae5b2384e | ||
|
|
07c9e97bf5 | ||
|
|
5a53da62e3 | ||
|
|
11748da7a9 | ||
|
|
b0e15b86f5 | ||
|
|
1d1ad23bc0 | ||
|
|
ac54446769 | ||
|
|
5fe5caaf2b | ||
|
|
e111b01f16 | ||
|
|
fd2ac3f7f1 | ||
|
|
4e57b75640 | ||
|
|
e439bff2bc | ||
|
|
e210002bff | ||
|
|
4e735beb2f | ||
|
|
d747edc1f3 | ||
|
|
8bec94336b | ||
|
|
83eb193f1b | ||
|
|
9806f72ca6 | ||
|
|
7e7ec52290 | ||
|
|
98746fbca5 | ||
|
|
c0f65386bd | ||
|
|
cef6fce2c9 | ||
|
|
4964518486 | ||
|
|
06ec433b4e | ||
|
|
85681af8b8 | ||
|
|
4b12ecdf4b | ||
|
|
e8eacb02d6 | ||
|
|
e9dd55f4f9 | ||
|
|
a79df727c9 | ||
|
|
ca47b1c4f5 | ||
|
|
ab75bc7f59 | ||
|
|
f776a061ce | ||
|
|
1456f95b2c | ||
|
|
c5767f6ad5 | ||
|
|
3f8dedba80 | ||
|
|
121d62f20f | ||
|
|
2c08265210 | ||
|
|
82e397f136 | ||
|
|
730ee0205e | ||
|
|
a4716d5db0 | ||
|
|
06f166b70a | ||
|
|
f2ea36ac0d | ||
|
|
a8e706a395 | ||
|
|
f608bfa191 | ||
|
|
e494e22c07 | ||
|
|
d40c815760 | ||
|
|
ea39d83907 | ||
|
|
dae38d9b18 | ||
|
|
1e7cf9e65d | ||
|
|
831bd61e02 | ||
|
|
fe2b846dbc | ||
|
|
8c47d2b179 | ||
|
|
afe6b234cf | ||
|
|
1743c763d2 | ||
|
|
21237f8be1 | ||
|
|
d19e446416 | ||
|
|
8b93ede959 | ||
|
|
cb23254152 | ||
|
|
601aa6e245 | ||
|
|
532d282509 | ||
|
|
93c0525ea5 | ||
|
|
2bf423b42f | ||
|
|
f6c2ce45c1 | ||
|
|
d24eeefc86 | ||
|
|
876d4187d9 | ||
|
|
2412e7151f | ||
|
|
07c21caf8d | ||
|
|
69ddb2ddff | ||
|
|
283143e1bf | ||
|
|
80c54ad890 | ||
|
|
3272f3c8cc | ||
|
|
6bfbfc4762 | ||
|
|
8d2735f6e9 | ||
|
|
d72984be7e | ||
|
|
39212666c5 | ||
|
|
c909d32c56 | ||
|
|
8073e989f5 | ||
|
|
4671013ea6 | ||
|
|
b954457ae4 | ||
|
|
f7889b9709 | ||
|
|
a9894acd51 | ||
|
|
51e1c62ee6 | ||
|
|
fbd0f4b9c6 | ||
|
|
58204373aa | ||
|
|
710257da65 | ||
|
|
f63803529a | ||
|
|
7d9af64f6d | ||
|
|
87a4ce17e1 | ||
|
|
fae6f413a5 | ||
|
|
d8e36ab0f8 | ||
|
|
35fd5c8d65 | ||
|
|
5604424e6f | ||
|
|
c4c5f01ee5 | ||
|
|
e6d203d567 | ||
|
|
e75fa0bd84 | ||
|
|
b2cfbb95b6 | ||
|
|
0d84ea9523 | ||
|
|
e99fe70164 | ||
|
|
8bd337e08f | ||
|
|
12124eb1aa | ||
|
|
e036395d18 | ||
|
|
41ba7ad6a5 | ||
|
|
3452eafe4f | ||
|
|
e515ad3074 | ||
|
|
255f138bdb | ||
|
|
08496c2c61 | ||
|
|
25a996fe51 | ||
|
|
9ba39f2eda | ||
|
|
4a70b38a99 | ||
|
|
0dc66adc0d | ||
|
|
c2c11b29a7 | ||
|
|
071f48c3ca | ||
|
|
65cef0b40f | ||
|
|
f30912e02f | ||
|
|
1b74dc8afd | ||
|
|
1840b343b2 | ||
|
|
4f8030ecae | ||
|
|
bb6c0be9de | ||
|
|
a2d4c43047 | ||
|
|
60184f12bd | ||
|
|
4ffa30e31a | ||
|
|
7fff2833c3 | ||
|
|
d1da92d040 | ||
|
|
6bb8f8a030 | ||
|
|
dcd0eb20e1 | ||
|
|
7d6270079a | ||
|
|
05242c0ad7 | ||
|
|
30fb3a97d7 | ||
|
|
4dec8cfb17 | ||
|
|
bb8210b88e | ||
|
|
b69858b6f7 | ||
|
|
f191601ab4 | ||
|
|
7d62a55a92 | ||
|
|
eadcda4e30 | ||
|
|
45382d1446 | ||
|
|
0fd4f0bd7c | ||
|
|
c3fd7b9077 | ||
|
|
0f6aa3df83 | ||
|
|
48333c2ad2 | ||
|
|
9786886c3f | ||
|
|
b0eedfd6f1 | ||
|
|
1753bf96a8 | ||
|
|
67315bf88e | ||
|
|
4a3dcf24e1 | ||
|
|
c1b18fe478 | ||
|
|
e918af0a0c | ||
|
|
a847ad292f | ||
|
|
efac72ecbb | ||
|
|
ed5611810f | ||
|
|
b8d5e79c03 | ||
|
|
3e72ea0650 | ||
|
|
8b8a0b61af | ||
|
|
f237ac981e | ||
|
|
0ce5a63c1d | ||
|
|
a483ac9174 | ||
|
|
91e4c89962 | ||
|
|
c34e3b4282 | ||
|
|
6e38eb1d70 | ||
|
|
5d227d8d72 | ||
|
|
ccdf725957 | ||
|
|
86cffba4aa | ||
|
|
2086a73557 | ||
|
|
a5e8980b23 | ||
|
|
360af93d8d | ||
|
|
4c46bdaa7f | ||
|
|
b98094ceee | ||
|
|
03330537fc | ||
|
|
2c7809b134 | ||
|
|
af287dcdce | ||
|
|
ed840a96e6 | ||
|
|
04cfd0eb38 | ||
|
|
6386034c65 | ||
|
|
9b26d6c504 | ||
|
|
3529297fa4 | ||
|
|
ad699fa23c | ||
|
|
0fd9e951b9 | ||
|
|
66bcd32ae7 | ||
|
|
f0d91b0058 | ||
|
|
3d58213f0b | ||
|
|
528d7411e1 | ||
|
|
a6651ffe48 | ||
|
|
a8e6dc3a3a | ||
|
|
8733b2463c | ||
|
|
37b0ef4599 | ||
|
|
c72ed970fb | ||
|
|
e9e42be139 | ||
|
|
b55ecd2808 | ||
|
|
f2a8829d1d | ||
|
|
c20ea7e030 | ||
|
|
df3fc0a9ef | ||
|
|
3c7a05b572 | ||
|
|
a155bcd11a | ||
|
|
57f13f59b1 | ||
|
|
894cf8e6c3 | ||
|
|
fa3b952af3 | ||
|
|
b26db1910b | ||
|
|
aab865be96 | ||
|
|
866fa5643e | ||
|
|
87ced16d63 | ||
|
|
ed5463360e | ||
|
|
27edfac161 | ||
|
|
7f70e69c8e | ||
|
|
c70fedfb43 | ||
|
|
85d7b5fd5d | ||
|
|
cbace6f97e | ||
|
|
bec5dc4476 | ||
|
|
f7ddd8bbbb | ||
|
|
43d0c291ed | ||
|
|
0956edd0d0 | ||
|
|
c9477d37fa | ||
|
|
3b5c96d7cb | ||
|
|
c3c049b225 | ||
|
|
6fedb9773b | ||
|
|
332a8de2e9 | ||
|
|
7121968081 | ||
|
|
a44133d0c9 | ||
|
|
3c3d9487d0 | ||
|
|
15b5083d17 | ||
|
|
e25cafe2b4 | ||
|
|
b5c44e47ff | ||
|
|
5d04b8deea | ||
|
|
1207291249 | ||
|
|
06bf641e7a | ||
|
|
ce764082b8 | ||
|
|
aebc301b05 | ||
|
|
3b93a073c3 | ||
|
|
a4a7b44da9 | ||
|
|
e65be6ee36 | ||
|
|
2d0b9ecac0 | ||
|
|
5a5b86aeec | ||
|
|
ff41fe0577 | ||
|
|
c97b60b2da | ||
|
|
2645dcdddf | ||
|
|
6debb09b0f | ||
|
|
62b84d454a | ||
|
|
26dc3f32d9 | ||
|
|
84f8b6e1da | ||
|
|
70e3d40c3e | ||
|
|
432b2f04d7 | ||
|
|
63c82cf705 | ||
|
|
5c7d121e67 | ||
|
|
055198ccd5 | ||
|
|
0993e81e00 | ||
|
|
15d22b7726 | ||
|
|
bb3a54a312 | ||
|
|
7ad6b9c32a | ||
|
|
4814f21017 | ||
|
|
1f962f21b3 | ||
|
|
397e1e2bf1 | ||
|
|
b10c7297fd | ||
|
|
7f9194f475 | ||
|
|
0fc1edf895 | ||
|
|
c973c0cce5 | ||
|
|
48f3bdc80c | ||
|
|
0fc43fcf42 | ||
|
|
e593ec604e | ||
|
|
1494bc9db3 | ||
|
|
4512aa7ff8 | ||
|
|
de782e629f | ||
|
|
b7c6a7ffd9 | ||
|
|
ca4118cc8e | ||
|
|
a561e7dc2b | ||
|
|
b3a0f187d7 | ||
|
|
39be2d0351 | ||
|
|
5492e0db50 | ||
|
|
db038c29e8 | ||
|
|
4fe1f5c225 | ||
|
|
ac374bab22 | ||
|
|
01f6814a25 | ||
|
|
6e4dc10c49 | ||
|
|
6b3a9024d3 | ||
|
|
b4841843f7 | ||
|
|
35efc07b2e | ||
|
|
954160ebd9 | ||
|
|
ea1633ad52 | ||
|
|
fb5d5ea1be | ||
|
|
02f5d6fea2 | ||
|
|
227755b892 | ||
|
|
eebd8771bd | ||
|
|
36fed0cc6f | ||
|
|
08f3b64243 | ||
|
|
59cfe96fe3 | ||
|
|
128e810f77 | ||
|
|
bbe9322f75 | ||
|
|
35186b41ff | ||
|
|
0c846f0fea | ||
|
|
d497e96407 | ||
|
|
a4f139f855 | ||
|
|
e386b28ba4 | ||
|
|
aa8ef48246 | ||
|
|
6bac462359 | ||
|
|
24e4d05157 | ||
|
|
737c051466 | ||
|
|
4ab50882a4 | ||
|
|
06a07865f3 | ||
|
|
5607702be9 | ||
|
|
e9be009f49 | ||
|
|
bb4e938bd2 | ||
|
|
45f723c07f | ||
|
|
640f44fd10 | ||
|
|
821dd02aa4 | ||
|
|
6e2140258e | ||
|
|
b628bd7769 | ||
|
|
f23cd22110 | ||
|
|
8ef4f408bf | ||
|
|
01139a2e6b | ||
|
|
08606f740d | ||
|
|
42c6bc11cc | ||
|
|
d6703e82e2 | ||
|
|
8f1692e394 | ||
|
|
3e55234a24 | ||
|
|
7df246c25a | ||
|
|
fbffbce090 | ||
|
|
18e9b6d84c | ||
|
|
03e5bc515f | ||
|
|
b336529d42 | ||
|
|
bbf716b28e | ||
|
|
725f6fe3ec | ||
|
|
f773a02ce0 | ||
|
|
21266be1ed | ||
|
|
53d0338218 | ||
|
|
2f67c23c5c | ||
|
|
e64be755f9 | ||
|
|
23952c5f5f | ||
|
|
02fc5036d7 | ||
|
|
a6a65ed9f4 | ||
|
|
369f657aa6 | ||
|
|
1801e5b30f | ||
|
|
b10d5103f3 | ||
|
|
9964bfef8b | ||
|
|
9945a19f2c | ||
|
|
a5d43e6f14 | ||
|
|
b61b9a06dd | ||
|
|
f28570fa98 | ||
|
|
fb53d639be | ||
|
|
42de9257c3 | ||
|
|
0fba597a61 | ||
|
|
d0393c4e9f | ||
|
|
a21d2a591e | ||
|
|
de54688c56 | ||
|
|
3c486d9117 | ||
|
|
e8128250f4 | ||
|
|
58a1b72910 | ||
|
|
ee00d29afd | ||
|
|
5cfcc538a8 | ||
|
|
e7c8a9d420 | ||
|
|
6b07ffe8f7 | ||
|
|
0d17340445 | ||
|
|
2a0b92713b | ||
|
|
fb22eb4178 | ||
|
|
136fb2f394 | ||
|
|
c515805f0a | ||
|
|
613ba9ba20 | ||
|
|
f6314d3581 | ||
|
|
043f5c78c2 | ||
|
|
0f9e519b0e | ||
|
|
0d17a2351f | ||
|
|
1b388664cb | ||
|
|
1edb0c529f | ||
|
|
f07a58bff4 | ||
|
|
c054840aec | ||
|
|
e259418398 | ||
|
|
df1d68a724 | ||
|
|
9d58390e11 | ||
|
|
db4c7a8bda | ||
|
|
ab163fc0ef | ||
|
|
0265801830 | ||
|
|
5fa7bbbc0f | ||
|
|
e15351e1c5 | ||
|
|
5eb0133b5c | ||
|
|
ff8344d671 | ||
|
|
d9a43ea1d6 | ||
|
|
6c62d73afb | ||
|
|
d23d6c342a | ||
|
|
90549a09e1 | ||
|
|
17d3639443 | ||
|
|
e8a096b8fe | ||
|
|
fea136e513 | ||
|
|
f3a4095462 | ||
|
|
d7d25cf69b | ||
|
|
3b33fe83e6 | ||
|
|
56f4d223ab | ||
|
|
c07317a619 | ||
|
|
4798f7688b | ||
|
|
24db0eb5aa | ||
|
|
1ece13c43d | ||
|
|
48268309b9 | ||
|
|
4074a52b1b | ||
|
|
61dd524c26 | ||
|
|
61a678a318 | ||
|
|
8ecec6a14f | ||
|
|
e3f8b8a394 | ||
|
|
eebddbafcc | ||
|
|
50cbce0f7a | ||
|
|
f9974a8971 | ||
|
|
baa48a1679 | ||
|
|
032910f87a | ||
|
|
773ef95b70 | ||
|
|
18c2f84771 | ||
|
|
ea721a71b5 | ||
|
|
b1b7427c6e | ||
|
|
7bc7c30510 | ||
|
|
d1e055430d | ||
|
|
736534379a | ||
|
|
f6b71e157c | ||
|
|
53e9f931f2 | ||
|
|
c226a9476f | ||
|
|
8ca5600286 | ||
|
|
9729bc00a0 | ||
|
|
f9c9af8261 | ||
|
|
c9e64373fb | ||
|
|
262febf486 | ||
|
|
eca9c4d7f8 | ||
|
|
a20083bb9c | ||
|
|
d31b01415b | ||
|
|
26cdb472f0 | ||
|
|
3c2595aaf9 | ||
|
|
ad323a0dff | ||
|
|
9a0417b1a7 | ||
|
|
3324a816ee | ||
|
|
cc4135a2d7 | ||
|
|
2d884c83cc | ||
|
|
619c5b5e1d | ||
|
|
33afa9adfc | ||
|
|
0c141cc6f7 | ||
|
|
efdff2ab0c | ||
|
|
ebefb0eba4 | ||
|
|
d4b9c2c97b | ||
|
|
d4db16b501 | ||
|
|
a5cbd7387b | ||
|
|
99d6b4a361 | ||
|
|
18365689e6 | ||
|
|
8ea827412a | ||
|
|
4d8979c4ad | ||
|
|
236357baa8 | ||
|
|
5ef4b21858 | ||
|
|
3b3441851c | ||
|
|
5517987e14 | ||
|
|
7e98f61bec | ||
|
|
e2d360e8b1 | ||
|
|
57cfedfd7c | ||
|
|
50b1d21d13 | ||
|
|
7b79ec6c66 | ||
|
|
7a5b31530f | ||
|
|
e9100a9542 | ||
|
|
421304d4a3 | ||
|
|
10d61bbbb3 | ||
|
|
095709fb19 | ||
|
|
725f4150fc | ||
|
|
93ac2e70d2 | ||
|
|
f4c2b50ca5 | ||
|
|
3354edbb1c | ||
|
|
d3600499af | ||
|
|
35e2c2cfe8 | ||
|
|
1e4970469f | ||
|
|
4cbfd7c9b1 | ||
|
|
deb32fa0fa | ||
|
|
f17a738780 | ||
|
|
2f53d0efae | ||
|
|
3e43c2e6e6 | ||
|
|
96919a9f79 | ||
|
|
a23fb627cd | ||
|
|
aae915b092 | ||
|
|
160c8e814b | ||
|
|
88f48baba1 | ||
|
|
7835efbf37 | ||
|
|
455fbb505e | ||
|
|
df3f7090e8 | ||
|
|
632b3c200c | ||
|
|
5b203326ae | ||
|
|
fd87aca57d | ||
|
|
b9b7e556e3 | ||
|
|
5b9a7e6a2a | ||
|
|
d278e19367 | ||
|
|
342030ca47 | ||
|
|
f90e241cc9 | ||
|
|
ff6bcc437c | ||
|
|
e32dbb5125 | ||
|
|
06701cc7d1 | ||
|
|
ccb31bc1b4 | ||
|
|
7e2e29e2e8 | ||
|
|
0c76995f6d | ||
|
|
17cab8fcb7 | ||
|
|
d64753859d | ||
|
|
48b1c24c95 | ||
|
|
7e5e12b139 | ||
|
|
59ba4752c0 | ||
|
|
189219c673 | ||
|
|
4b54262edd | ||
|
|
458b035a7e | ||
|
|
abe85cf6e7 | ||
|
|
379b63275a | ||
|
|
07cde689b1 | ||
|
|
26606f138d | ||
|
|
24c4774f7e | ||
|
|
6bbb4f577e | ||
|
|
4890de5052 | ||
|
|
746af37899 | ||
|
|
0136ce7034 | ||
|
|
5ca47a91a6 | ||
|
|
d849df41be | ||
|
|
a2269bcdd5 | ||
|
|
d41394c70a | ||
|
|
756f73bbd2 | ||
|
|
95eb17b6d1 | ||
|
|
773fd1f221 | ||
|
|
c95b436680 | ||
|
|
3f47151678 | ||
|
|
dbf1e7136c | ||
|
|
84b44eef91 | ||
|
|
686ada1e49 | ||
|
|
d9f6bc058a | ||
|
|
6f531793f0 | ||
|
|
f8794bef5f | ||
|
|
9daf103669 | ||
|
|
26fa047fe4 | ||
|
|
1a04bb535d | ||
|
|
ad41b7b434 | ||
|
|
6791b1dbdc | ||
|
|
dc1b84130c | ||
|
|
e70a933090 | ||
|
|
08617f2fd4 | ||
|
|
8aefb5db8e | ||
|
|
e1d214b91a | ||
|
|
0285de50e5 | ||
|
|
5f7caa0535 | ||
|
|
df9d2bc472 | ||
|
|
a923b6beca | ||
|
|
760ef77593 | ||
|
|
64f703856e | ||
|
|
36eba36bf2 | ||
|
|
332176a619 | ||
|
|
cd5b60c8cc | ||
|
|
5ea8947fc9 | ||
|
|
474a5c8e42 | ||
|
|
952062d5be | ||
|
|
c071564de4 | ||
|
|
b2fa7563d3 | ||
|
|
22f68fedcb | ||
|
|
0a4cda26cb | ||
|
|
54eb84e690 | ||
|
|
5cd33814fc | ||
|
|
90f19679e7 | ||
|
|
38ade9e498 | ||
|
|
4c77831001 | ||
|
|
722fe556e3 | ||
|
|
98b83c0078 | ||
|
|
85646b5ffa | ||
|
|
d3f5ed7d60 | ||
|
|
e0a8e93704 | ||
|
|
3aab5e9eb7 | ||
|
|
2307ac88cb | ||
|
|
0f2018d913 | ||
|
|
8b9a75ba5a | ||
|
|
7872e0ce38 | ||
|
|
d28ac73987 | ||
|
|
2c8c7a8b51 | ||
|
|
f98fc6bf3c | ||
|
|
d42958734f | ||
|
|
6f2c785713 | ||
|
|
05a1ce51f0 | ||
|
|
58b063770d | ||
|
|
6f9a83709d | ||
|
|
bd2a3ddfba | ||
|
|
4433b6623e | ||
|
|
e1ca17637f | ||
|
|
5ef0e43312 | ||
|
|
aa6ca95f13 | ||
|
|
2523467c97 | ||
|
|
b38eb6709e | ||
|
|
f6dacdb046 | ||
|
|
cc0903f74d | ||
|
|
72936fc0d1 | ||
|
|
4b7f552885 | ||
|
|
9b2fc60d79 | ||
|
|
3e5a26b95a | ||
|
|
a5f3139784 | ||
|
|
9ca9fcdb09 | ||
|
|
913db35b23 | ||
|
|
8eba3ba4f7 | ||
|
|
5bd0f24a84 | ||
|
|
e8fb3180db | ||
|
|
232aaad090 |
@@ -0,0 +1,902 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 以上三个字段返回给前端并未做驼峰处理,json内依然是 `ID`, `CreatedAt`, `UpdatedAt`
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **插件注册**: **必须**调用 ```
|
||||
func init() {
|
||||
interfaces.Register(Plugin)
|
||||
}
|
||||
```
|
||||
方法,让插件自动注册到本体中
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 插件默认注册功能
|
||||
|
||||
`plugin/register.go` 文件下用 ` _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/插件"
|
||||
` 的方式匿名引用用于激活插件本体的init
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
- **必须**优先使用UnoCSS原子化类名进行样式设计
|
||||
- **必须**优先el-drawer组件进行编辑,新增,步骤等操作
|
||||
- **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性,确保组件销毁,避免内存泄漏和状态污染
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **⚠️ 前端工具库使用规范(强制)**
|
||||
|
||||
> **核心原则:在开发任何前端功能时,必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数,严禁重复造轮子。**
|
||||
|
||||
`src/utils/` 目录提供了项目级别的通用工具集,涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明:
|
||||
|
||||
### **工具文件清单**
|
||||
|
||||
#### `request.js` — HTTP 请求封装(核心)
|
||||
- 基于 Axios 封装的统一 HTTP 请求实例,内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截
|
||||
- **所有 API 请求必须且只能通过此模块发送,禁止直接使用 axios**
|
||||
- 用法:`import service from '@/utils/request'`
|
||||
|
||||
#### `date.js` — 日期格式化
|
||||
- 扩展了 `Date.prototype.Format` 方法,支持自定义格式如 `yyyy-MM-dd hh:mm:ss`
|
||||
- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串
|
||||
- **需要格式化日期时,优先使用此工具,禁止自行手写日期格式化逻辑**
|
||||
- 用法:`import { formatTimeToStr } from '@/utils/date'`
|
||||
|
||||
#### `format.js` — 数据展示格式化(综合工具)
|
||||
- `formatBoolean(bool)` — 将布尔值转为 "是"/"否" 中文展示
|
||||
- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串
|
||||
- `filterDict(value, options)` — 在字典选项数组(支持多级树形)中根据 value 查找对应的 label
|
||||
- `filterDataSource(dataSource, value)` — 在数据源(支持多级树形)中根据 value 查找 label,支持数组批量查找
|
||||
- `getDictFunc(type)` — 异步获取指定类型的字典数据
|
||||
- `ReturnArrImg(arr)` — 将图片路径(单个或数组)转为完整 URL,自动补全服务器前缀
|
||||
- `onDownloadFile(url)` — 触发文件下载
|
||||
- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量(支持亮/暗模式)
|
||||
- `CreateUUID()` — 生成 UUID v4 字符串
|
||||
- `getBaseUrl()` — 获取当前环境的 API BaseURL
|
||||
- **以上所有格式化场景优先使用此文件中的工具函数**
|
||||
- 用法:`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`
|
||||
|
||||
#### `dictionary.js` — 字典数据获取
|
||||
- `getDict(type, options)` — 异步获取字典数据,支持 `depth`(深度)和 `value`(指定节点)参数,内置 Pinia store 缓存,避免重复请求
|
||||
- **凡是需要字典下拉数据、字典树形数据的场景,必须使用此工具**
|
||||
- 用法:`import { getDict } from '@/utils/dictionary'`
|
||||
|
||||
#### `stringFun.js` — 字符串处理
|
||||
- `toUpperCase(str)` — 首字母转大写
|
||||
- `toLowerCase(str)` — 首字母转小写
|
||||
- `toSQLLine(str)` — 驼峰命名转下划线(snake_case),如 `userName` → `user_name`
|
||||
- `toHump(name)` — 下划线命名转驼峰,如 `user_name` → `userName`
|
||||
- **进行命名格式转换时必须使用此工具,禁止使用正则手写**
|
||||
- 用法:`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`
|
||||
|
||||
#### `params.js` — 系统参数获取
|
||||
- `getParams(key)` — 异步从 Pinia store 中获取系统参数,内置缓存
|
||||
- **获取系统配置参数时,优先使用此工具**
|
||||
- 用法:`import { getParams } from '@/utils/params'`
|
||||
|
||||
#### `bus.js` — 全局事件总线
|
||||
- 基于 `mitt` 封装的全局事件总线实例 `emitter`,用于跨组件通信
|
||||
- **跨层级组件通信优先使用此事件总线,避免滥用 Pinia**
|
||||
- 用法:`import { emitter } from '@/utils/bus'`
|
||||
|
||||
#### `closeThisPage.js` — 关闭当前标签页
|
||||
- `closeThisPage()` — 触发关闭当前多标签页的操作(通过事件总线发送 `closeThisPage` 事件)
|
||||
- **在需要程序化关闭当前页面时,必须使用此工具**
|
||||
- 用法:`import { closeThisPage } from '@/utils/closeThisPage'`
|
||||
|
||||
#### `downloadImg.js` — 图片下载
|
||||
- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载,支持跨域
|
||||
- **需要下载图片时,优先使用此工具**
|
||||
- 用法:`import { downloadImage } from '@/utils/downloadImg'`
|
||||
|
||||
#### `image.js` — 图片压缩
|
||||
- 导出 `ImageCompress` 类,支持图片等比压缩至指定最大宽高,并可限制文件大小
|
||||
- **上传图片前需要做压缩处理时,使用此工具**
|
||||
- 用法:`import ImageCompress from '@/utils/image'`
|
||||
|
||||
#### `event.js` — DOM 事件监听管理
|
||||
- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听
|
||||
- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听
|
||||
- **手动操作 DOM 事件时,使用此工具以确保安全性**
|
||||
- 用法:`import { addEventListen, removeEventListen } from '@/utils/event'`
|
||||
|
||||
#### `env.js` — 环境判断
|
||||
- `isDev` — 是否为开发环境(Boolean)
|
||||
- `isProd` — 是否为生产环境(Boolean)
|
||||
- **需要区分运行环境时,使用此工具,禁止直接读取 `import.meta.env`**
|
||||
- 用法:`import { isDev, isProd } from '@/utils/env'`
|
||||
|
||||
#### `doc.js` — 外部文档跳转
|
||||
- `toDoc(url)` — 在新标签页打开指定 URL
|
||||
- 用法:`import { toDoc } from '@/utils/doc'`
|
||||
|
||||
#### `fmtRouterTitle.js` — 路由标题格式化
|
||||
- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值(如 `${id}` 替换为路由 params/query 值)
|
||||
- 用法:`import { fmtTitle } from '@/utils/fmtRouterTitle'`
|
||||
|
||||
#### `page.js` — 页面标题生成
|
||||
- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题(格式:`页面名 - 应用名`)
|
||||
- 用法:`import getPageTitle from '@/utils/page'`
|
||||
|
||||
#### `asyncRouter.js` — 异步路由处理
|
||||
- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置(字符串 component 路径)动态转换为 Vue 组件的 import 函数,支持 `view/` 和 `plugin/` 目录
|
||||
- **动态路由相关逻辑已由此工具处理,不需要也不应该手动实现**
|
||||
- 用法:`import { asyncRouterHandle } from '@/utils/asyncRouter'`
|
||||
|
||||
#### `btnAuth.js` — 按钮权限
|
||||
- `useBtnAuth()` — Composition API Hook,返回当前路由挂载的按钮权限对象(来自 `route.meta.btns`),用于控制操作按钮的显示
|
||||
- **实现按钮级别权限控制时,必须使用此 Hook**
|
||||
- 用法:`import { useBtnAuth } from '@/utils/btnAuth'`
|
||||
|
||||
### **使用强制要求**
|
||||
|
||||
| 场景 | 必须使用的工具 |
|
||||
|------|----------------|
|
||||
| 发送 HTTP 请求 | `@/utils/request` |
|
||||
| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |
|
||||
| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |
|
||||
| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |
|
||||
| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |
|
||||
| 驼峰/下划线命名转换 | `@/utils/stringFun` |
|
||||
| 获取系统参数 | `@/utils/params` 中的 `getParams` |
|
||||
| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |
|
||||
| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |
|
||||
| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |
|
||||
| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |
|
||||
| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -0,0 +1,902 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 以上三个字段返回给前端并未做驼峰处理,json内依然是 `ID`, `CreatedAt`, `UpdatedAt`
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **插件注册**: **必须**调用 ```
|
||||
func init() {
|
||||
interfaces.Register(Plugin)
|
||||
}
|
||||
```
|
||||
方法,让插件自动注册到本体中
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 插件默认注册功能
|
||||
|
||||
`plugin/register.go` 文件下用 ` _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/插件"
|
||||
` 的方式匿名引用用于激活插件本体的init
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
- **必须**优先使用UnoCSS原子化类名进行样式设计
|
||||
- **必须**优先el-drawer组件进行编辑,新增,步骤等操作
|
||||
- **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性,确保组件销毁,避免内存泄漏和状态污染
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **⚠️ 前端工具库使用规范(强制)**
|
||||
|
||||
> **核心原则:在开发任何前端功能时,必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数,严禁重复造轮子。**
|
||||
|
||||
`src/utils/` 目录提供了项目级别的通用工具集,涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明:
|
||||
|
||||
### **工具文件清单**
|
||||
|
||||
#### `request.js` — HTTP 请求封装(核心)
|
||||
- 基于 Axios 封装的统一 HTTP 请求实例,内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截
|
||||
- **所有 API 请求必须且只能通过此模块发送,禁止直接使用 axios**
|
||||
- 用法:`import service from '@/utils/request'`
|
||||
|
||||
#### `date.js` — 日期格式化
|
||||
- 扩展了 `Date.prototype.Format` 方法,支持自定义格式如 `yyyy-MM-dd hh:mm:ss`
|
||||
- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串
|
||||
- **需要格式化日期时,优先使用此工具,禁止自行手写日期格式化逻辑**
|
||||
- 用法:`import { formatTimeToStr } from '@/utils/date'`
|
||||
|
||||
#### `format.js` — 数据展示格式化(综合工具)
|
||||
- `formatBoolean(bool)` — 将布尔值转为 "是"/"否" 中文展示
|
||||
- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串
|
||||
- `filterDict(value, options)` — 在字典选项数组(支持多级树形)中根据 value 查找对应的 label
|
||||
- `filterDataSource(dataSource, value)` — 在数据源(支持多级树形)中根据 value 查找 label,支持数组批量查找
|
||||
- `getDictFunc(type)` — 异步获取指定类型的字典数据
|
||||
- `ReturnArrImg(arr)` — 将图片路径(单个或数组)转为完整 URL,自动补全服务器前缀
|
||||
- `onDownloadFile(url)` — 触发文件下载
|
||||
- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量(支持亮/暗模式)
|
||||
- `CreateUUID()` — 生成 UUID v4 字符串
|
||||
- `getBaseUrl()` — 获取当前环境的 API BaseURL
|
||||
- **以上所有格式化场景优先使用此文件中的工具函数**
|
||||
- 用法:`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`
|
||||
|
||||
#### `dictionary.js` — 字典数据获取
|
||||
- `getDict(type, options)` — 异步获取字典数据,支持 `depth`(深度)和 `value`(指定节点)参数,内置 Pinia store 缓存,避免重复请求
|
||||
- **凡是需要字典下拉数据、字典树形数据的场景,必须使用此工具**
|
||||
- 用法:`import { getDict } from '@/utils/dictionary'`
|
||||
|
||||
#### `stringFun.js` — 字符串处理
|
||||
- `toUpperCase(str)` — 首字母转大写
|
||||
- `toLowerCase(str)` — 首字母转小写
|
||||
- `toSQLLine(str)` — 驼峰命名转下划线(snake_case),如 `userName` → `user_name`
|
||||
- `toHump(name)` — 下划线命名转驼峰,如 `user_name` → `userName`
|
||||
- **进行命名格式转换时必须使用此工具,禁止使用正则手写**
|
||||
- 用法:`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`
|
||||
|
||||
#### `params.js` — 系统参数获取
|
||||
- `getParams(key)` — 异步从 Pinia store 中获取系统参数,内置缓存
|
||||
- **获取系统配置参数时,优先使用此工具**
|
||||
- 用法:`import { getParams } from '@/utils/params'`
|
||||
|
||||
#### `bus.js` — 全局事件总线
|
||||
- 基于 `mitt` 封装的全局事件总线实例 `emitter`,用于跨组件通信
|
||||
- **跨层级组件通信优先使用此事件总线,避免滥用 Pinia**
|
||||
- 用法:`import { emitter } from '@/utils/bus'`
|
||||
|
||||
#### `closeThisPage.js` — 关闭当前标签页
|
||||
- `closeThisPage()` — 触发关闭当前多标签页的操作(通过事件总线发送 `closeThisPage` 事件)
|
||||
- **在需要程序化关闭当前页面时,必须使用此工具**
|
||||
- 用法:`import { closeThisPage } from '@/utils/closeThisPage'`
|
||||
|
||||
#### `downloadImg.js` — 图片下载
|
||||
- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载,支持跨域
|
||||
- **需要下载图片时,优先使用此工具**
|
||||
- 用法:`import { downloadImage } from '@/utils/downloadImg'`
|
||||
|
||||
#### `image.js` — 图片压缩
|
||||
- 导出 `ImageCompress` 类,支持图片等比压缩至指定最大宽高,并可限制文件大小
|
||||
- **上传图片前需要做压缩处理时,使用此工具**
|
||||
- 用法:`import ImageCompress from '@/utils/image'`
|
||||
|
||||
#### `event.js` — DOM 事件监听管理
|
||||
- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听
|
||||
- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听
|
||||
- **手动操作 DOM 事件时,使用此工具以确保安全性**
|
||||
- 用法:`import { addEventListen, removeEventListen } from '@/utils/event'`
|
||||
|
||||
#### `env.js` — 环境判断
|
||||
- `isDev` — 是否为开发环境(Boolean)
|
||||
- `isProd` — 是否为生产环境(Boolean)
|
||||
- **需要区分运行环境时,使用此工具,禁止直接读取 `import.meta.env`**
|
||||
- 用法:`import { isDev, isProd } from '@/utils/env'`
|
||||
|
||||
#### `doc.js` — 外部文档跳转
|
||||
- `toDoc(url)` — 在新标签页打开指定 URL
|
||||
- 用法:`import { toDoc } from '@/utils/doc'`
|
||||
|
||||
#### `fmtRouterTitle.js` — 路由标题格式化
|
||||
- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值(如 `${id}` 替换为路由 params/query 值)
|
||||
- 用法:`import { fmtTitle } from '@/utils/fmtRouterTitle'`
|
||||
|
||||
#### `page.js` — 页面标题生成
|
||||
- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题(格式:`页面名 - 应用名`)
|
||||
- 用法:`import getPageTitle from '@/utils/page'`
|
||||
|
||||
#### `asyncRouter.js` — 异步路由处理
|
||||
- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置(字符串 component 路径)动态转换为 Vue 组件的 import 函数,支持 `view/` 和 `plugin/` 目录
|
||||
- **动态路由相关逻辑已由此工具处理,不需要也不应该手动实现**
|
||||
- 用法:`import { asyncRouterHandle } from '@/utils/asyncRouter'`
|
||||
|
||||
#### `btnAuth.js` — 按钮权限
|
||||
- `useBtnAuth()` — Composition API Hook,返回当前路由挂载的按钮权限对象(来自 `route.meta.btns`),用于控制操作按钮的显示
|
||||
- **实现按钮级别权限控制时,必须使用此 Hook**
|
||||
- 用法:`import { useBtnAuth } from '@/utils/btnAuth'`
|
||||
|
||||
### **使用强制要求**
|
||||
|
||||
| 场景 | 必须使用的工具 |
|
||||
|------|----------------|
|
||||
| 发送 HTTP 请求 | `@/utils/request` |
|
||||
| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |
|
||||
| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |
|
||||
| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |
|
||||
| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |
|
||||
| 驼峰/下划线命名转换 | `@/utils/stringFun` |
|
||||
| 获取系统参数 | `@/utils/params` 中的 `getParams` |
|
||||
| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |
|
||||
| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |
|
||||
| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |
|
||||
| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |
|
||||
| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -0,0 +1,902 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 以上三个字段返回给前端并未做驼峰处理,json内依然是 `ID`, `CreatedAt`, `UpdatedAt`
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **插件注册**: **必须**调用 ```
|
||||
func init() {
|
||||
interfaces.Register(Plugin)
|
||||
}
|
||||
```
|
||||
方法,让插件自动注册到本体中
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 插件默认注册功能
|
||||
|
||||
`plugin/register.go` 文件下用 ` _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/插件"
|
||||
` 的方式匿名引用用于激活插件本体的init
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
- **必须**优先使用UnoCSS原子化类名进行样式设计
|
||||
- **必须**优先el-drawer组件进行编辑,新增,步骤等操作
|
||||
- **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性,确保组件销毁,避免内存泄漏和状态污染
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **⚠️ 前端工具库使用规范(强制)**
|
||||
|
||||
> **核心原则:在开发任何前端功能时,必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数,严禁重复造轮子。**
|
||||
|
||||
`src/utils/` 目录提供了项目级别的通用工具集,涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明:
|
||||
|
||||
### **工具文件清单**
|
||||
|
||||
#### `request.js` — HTTP 请求封装(核心)
|
||||
- 基于 Axios 封装的统一 HTTP 请求实例,内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截
|
||||
- **所有 API 请求必须且只能通过此模块发送,禁止直接使用 axios**
|
||||
- 用法:`import service from '@/utils/request'`
|
||||
|
||||
#### `date.js` — 日期格式化
|
||||
- 扩展了 `Date.prototype.Format` 方法,支持自定义格式如 `yyyy-MM-dd hh:mm:ss`
|
||||
- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串
|
||||
- **需要格式化日期时,优先使用此工具,禁止自行手写日期格式化逻辑**
|
||||
- 用法:`import { formatTimeToStr } from '@/utils/date'`
|
||||
|
||||
#### `format.js` — 数据展示格式化(综合工具)
|
||||
- `formatBoolean(bool)` — 将布尔值转为 "是"/"否" 中文展示
|
||||
- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串
|
||||
- `filterDict(value, options)` — 在字典选项数组(支持多级树形)中根据 value 查找对应的 label
|
||||
- `filterDataSource(dataSource, value)` — 在数据源(支持多级树形)中根据 value 查找 label,支持数组批量查找
|
||||
- `getDictFunc(type)` — 异步获取指定类型的字典数据
|
||||
- `ReturnArrImg(arr)` — 将图片路径(单个或数组)转为完整 URL,自动补全服务器前缀
|
||||
- `onDownloadFile(url)` — 触发文件下载
|
||||
- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量(支持亮/暗模式)
|
||||
- `CreateUUID()` — 生成 UUID v4 字符串
|
||||
- `getBaseUrl()` — 获取当前环境的 API BaseURL
|
||||
- **以上所有格式化场景优先使用此文件中的工具函数**
|
||||
- 用法:`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`
|
||||
|
||||
#### `dictionary.js` — 字典数据获取
|
||||
- `getDict(type, options)` — 异步获取字典数据,支持 `depth`(深度)和 `value`(指定节点)参数,内置 Pinia store 缓存,避免重复请求
|
||||
- **凡是需要字典下拉数据、字典树形数据的场景,必须使用此工具**
|
||||
- 用法:`import { getDict } from '@/utils/dictionary'`
|
||||
|
||||
#### `stringFun.js` — 字符串处理
|
||||
- `toUpperCase(str)` — 首字母转大写
|
||||
- `toLowerCase(str)` — 首字母转小写
|
||||
- `toSQLLine(str)` — 驼峰命名转下划线(snake_case),如 `userName` → `user_name`
|
||||
- `toHump(name)` — 下划线命名转驼峰,如 `user_name` → `userName`
|
||||
- **进行命名格式转换时必须使用此工具,禁止使用正则手写**
|
||||
- 用法:`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`
|
||||
|
||||
#### `params.js` — 系统参数获取
|
||||
- `getParams(key)` — 异步从 Pinia store 中获取系统参数,内置缓存
|
||||
- **获取系统配置参数时,优先使用此工具**
|
||||
- 用法:`import { getParams } from '@/utils/params'`
|
||||
|
||||
#### `bus.js` — 全局事件总线
|
||||
- 基于 `mitt` 封装的全局事件总线实例 `emitter`,用于跨组件通信
|
||||
- **跨层级组件通信优先使用此事件总线,避免滥用 Pinia**
|
||||
- 用法:`import { emitter } from '@/utils/bus'`
|
||||
|
||||
#### `closeThisPage.js` — 关闭当前标签页
|
||||
- `closeThisPage()` — 触发关闭当前多标签页的操作(通过事件总线发送 `closeThisPage` 事件)
|
||||
- **在需要程序化关闭当前页面时,必须使用此工具**
|
||||
- 用法:`import { closeThisPage } from '@/utils/closeThisPage'`
|
||||
|
||||
#### `downloadImg.js` — 图片下载
|
||||
- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载,支持跨域
|
||||
- **需要下载图片时,优先使用此工具**
|
||||
- 用法:`import { downloadImage } from '@/utils/downloadImg'`
|
||||
|
||||
#### `image.js` — 图片压缩
|
||||
- 导出 `ImageCompress` 类,支持图片等比压缩至指定最大宽高,并可限制文件大小
|
||||
- **上传图片前需要做压缩处理时,使用此工具**
|
||||
- 用法:`import ImageCompress from '@/utils/image'`
|
||||
|
||||
#### `event.js` — DOM 事件监听管理
|
||||
- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听
|
||||
- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听
|
||||
- **手动操作 DOM 事件时,使用此工具以确保安全性**
|
||||
- 用法:`import { addEventListen, removeEventListen } from '@/utils/event'`
|
||||
|
||||
#### `env.js` — 环境判断
|
||||
- `isDev` — 是否为开发环境(Boolean)
|
||||
- `isProd` — 是否为生产环境(Boolean)
|
||||
- **需要区分运行环境时,使用此工具,禁止直接读取 `import.meta.env`**
|
||||
- 用法:`import { isDev, isProd } from '@/utils/env'`
|
||||
|
||||
#### `doc.js` — 外部文档跳转
|
||||
- `toDoc(url)` — 在新标签页打开指定 URL
|
||||
- 用法:`import { toDoc } from '@/utils/doc'`
|
||||
|
||||
#### `fmtRouterTitle.js` — 路由标题格式化
|
||||
- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值(如 `${id}` 替换为路由 params/query 值)
|
||||
- 用法:`import { fmtTitle } from '@/utils/fmtRouterTitle'`
|
||||
|
||||
#### `page.js` — 页面标题生成
|
||||
- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题(格式:`页面名 - 应用名`)
|
||||
- 用法:`import getPageTitle from '@/utils/page'`
|
||||
|
||||
#### `asyncRouter.js` — 异步路由处理
|
||||
- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置(字符串 component 路径)动态转换为 Vue 组件的 import 函数,支持 `view/` 和 `plugin/` 目录
|
||||
- **动态路由相关逻辑已由此工具处理,不需要也不应该手动实现**
|
||||
- 用法:`import { asyncRouterHandle } from '@/utils/asyncRouter'`
|
||||
|
||||
#### `btnAuth.js` — 按钮权限
|
||||
- `useBtnAuth()` — Composition API Hook,返回当前路由挂载的按钮权限对象(来自 `route.meta.btns`),用于控制操作按钮的显示
|
||||
- **实现按钮级别权限控制时,必须使用此 Hook**
|
||||
- 用法:`import { useBtnAuth } from '@/utils/btnAuth'`
|
||||
|
||||
### **使用强制要求**
|
||||
|
||||
| 场景 | 必须使用的工具 |
|
||||
|------|----------------|
|
||||
| 发送 HTTP 请求 | `@/utils/request` |
|
||||
| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |
|
||||
| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |
|
||||
| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |
|
||||
| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |
|
||||
| 驼峰/下划线命名转换 | `@/utils/stringFun` |
|
||||
| 获取系统参数 | `@/utils/params` 中的 `getParams` |
|
||||
| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |
|
||||
| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |
|
||||
| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |
|
||||
| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |
|
||||
| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -0,0 +1,902 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 以上三个字段返回给前端并未做驼峰处理,json内依然是 `ID`, `CreatedAt`, `UpdatedAt`
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **插件注册**: **必须**调用 ```
|
||||
func init() {
|
||||
interfaces.Register(Plugin)
|
||||
}
|
||||
```
|
||||
方法,让插件自动注册到本体中
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 插件默认注册功能
|
||||
|
||||
`plugin/register.go` 文件下用 ` _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/插件"
|
||||
` 的方式匿名引用用于激活插件本体的init
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
- **必须**优先使用UnoCSS原子化类名进行样式设计
|
||||
- **必须**优先el-drawer组件进行编辑,新增,步骤等操作
|
||||
- **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性,确保组件销毁,避免内存泄漏和状态污染
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **⚠️ 前端工具库使用规范(强制)**
|
||||
|
||||
> **核心原则:在开发任何前端功能时,必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数,严禁重复造轮子。**
|
||||
|
||||
`src/utils/` 目录提供了项目级别的通用工具集,涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明:
|
||||
|
||||
### **工具文件清单**
|
||||
|
||||
#### `request.js` — HTTP 请求封装(核心)
|
||||
- 基于 Axios 封装的统一 HTTP 请求实例,内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截
|
||||
- **所有 API 请求必须且只能通过此模块发送,禁止直接使用 axios**
|
||||
- 用法:`import service from '@/utils/request'`
|
||||
|
||||
#### `date.js` — 日期格式化
|
||||
- 扩展了 `Date.prototype.Format` 方法,支持自定义格式如 `yyyy-MM-dd hh:mm:ss`
|
||||
- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串
|
||||
- **需要格式化日期时,优先使用此工具,禁止自行手写日期格式化逻辑**
|
||||
- 用法:`import { formatTimeToStr } from '@/utils/date'`
|
||||
|
||||
#### `format.js` — 数据展示格式化(综合工具)
|
||||
- `formatBoolean(bool)` — 将布尔值转为 "是"/"否" 中文展示
|
||||
- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串
|
||||
- `filterDict(value, options)` — 在字典选项数组(支持多级树形)中根据 value 查找对应的 label
|
||||
- `filterDataSource(dataSource, value)` — 在数据源(支持多级树形)中根据 value 查找 label,支持数组批量查找
|
||||
- `getDictFunc(type)` — 异步获取指定类型的字典数据
|
||||
- `ReturnArrImg(arr)` — 将图片路径(单个或数组)转为完整 URL,自动补全服务器前缀
|
||||
- `onDownloadFile(url)` — 触发文件下载
|
||||
- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量(支持亮/暗模式)
|
||||
- `CreateUUID()` — 生成 UUID v4 字符串
|
||||
- `getBaseUrl()` — 获取当前环境的 API BaseURL
|
||||
- **以上所有格式化场景优先使用此文件中的工具函数**
|
||||
- 用法:`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`
|
||||
|
||||
#### `dictionary.js` — 字典数据获取
|
||||
- `getDict(type, options)` — 异步获取字典数据,支持 `depth`(深度)和 `value`(指定节点)参数,内置 Pinia store 缓存,避免重复请求
|
||||
- **凡是需要字典下拉数据、字典树形数据的场景,必须使用此工具**
|
||||
- 用法:`import { getDict } from '@/utils/dictionary'`
|
||||
|
||||
#### `stringFun.js` — 字符串处理
|
||||
- `toUpperCase(str)` — 首字母转大写
|
||||
- `toLowerCase(str)` — 首字母转小写
|
||||
- `toSQLLine(str)` — 驼峰命名转下划线(snake_case),如 `userName` → `user_name`
|
||||
- `toHump(name)` — 下划线命名转驼峰,如 `user_name` → `userName`
|
||||
- **进行命名格式转换时必须使用此工具,禁止使用正则手写**
|
||||
- 用法:`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`
|
||||
|
||||
#### `params.js` — 系统参数获取
|
||||
- `getParams(key)` — 异步从 Pinia store 中获取系统参数,内置缓存
|
||||
- **获取系统配置参数时,优先使用此工具**
|
||||
- 用法:`import { getParams } from '@/utils/params'`
|
||||
|
||||
#### `bus.js` — 全局事件总线
|
||||
- 基于 `mitt` 封装的全局事件总线实例 `emitter`,用于跨组件通信
|
||||
- **跨层级组件通信优先使用此事件总线,避免滥用 Pinia**
|
||||
- 用法:`import { emitter } from '@/utils/bus'`
|
||||
|
||||
#### `closeThisPage.js` — 关闭当前标签页
|
||||
- `closeThisPage()` — 触发关闭当前多标签页的操作(通过事件总线发送 `closeThisPage` 事件)
|
||||
- **在需要程序化关闭当前页面时,必须使用此工具**
|
||||
- 用法:`import { closeThisPage } from '@/utils/closeThisPage'`
|
||||
|
||||
#### `downloadImg.js` — 图片下载
|
||||
- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载,支持跨域
|
||||
- **需要下载图片时,优先使用此工具**
|
||||
- 用法:`import { downloadImage } from '@/utils/downloadImg'`
|
||||
|
||||
#### `image.js` — 图片压缩
|
||||
- 导出 `ImageCompress` 类,支持图片等比压缩至指定最大宽高,并可限制文件大小
|
||||
- **上传图片前需要做压缩处理时,使用此工具**
|
||||
- 用法:`import ImageCompress from '@/utils/image'`
|
||||
|
||||
#### `event.js` — DOM 事件监听管理
|
||||
- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听
|
||||
- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听
|
||||
- **手动操作 DOM 事件时,使用此工具以确保安全性**
|
||||
- 用法:`import { addEventListen, removeEventListen } from '@/utils/event'`
|
||||
|
||||
#### `env.js` — 环境判断
|
||||
- `isDev` — 是否为开发环境(Boolean)
|
||||
- `isProd` — 是否为生产环境(Boolean)
|
||||
- **需要区分运行环境时,使用此工具,禁止直接读取 `import.meta.env`**
|
||||
- 用法:`import { isDev, isProd } from '@/utils/env'`
|
||||
|
||||
#### `doc.js` — 外部文档跳转
|
||||
- `toDoc(url)` — 在新标签页打开指定 URL
|
||||
- 用法:`import { toDoc } from '@/utils/doc'`
|
||||
|
||||
#### `fmtRouterTitle.js` — 路由标题格式化
|
||||
- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值(如 `${id}` 替换为路由 params/query 值)
|
||||
- 用法:`import { fmtTitle } from '@/utils/fmtRouterTitle'`
|
||||
|
||||
#### `page.js` — 页面标题生成
|
||||
- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题(格式:`页面名 - 应用名`)
|
||||
- 用法:`import getPageTitle from '@/utils/page'`
|
||||
|
||||
#### `asyncRouter.js` — 异步路由处理
|
||||
- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置(字符串 component 路径)动态转换为 Vue 组件的 import 函数,支持 `view/` 和 `plugin/` 目录
|
||||
- **动态路由相关逻辑已由此工具处理,不需要也不应该手动实现**
|
||||
- 用法:`import { asyncRouterHandle } from '@/utils/asyncRouter'`
|
||||
|
||||
#### `btnAuth.js` — 按钮权限
|
||||
- `useBtnAuth()` — Composition API Hook,返回当前路由挂载的按钮权限对象(来自 `route.meta.btns`),用于控制操作按钮的显示
|
||||
- **实现按钮级别权限控制时,必须使用此 Hook**
|
||||
- 用法:`import { useBtnAuth } from '@/utils/btnAuth'`
|
||||
|
||||
### **使用强制要求**
|
||||
|
||||
| 场景 | 必须使用的工具 |
|
||||
|------|----------------|
|
||||
| 发送 HTTP 请求 | `@/utils/request` |
|
||||
| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |
|
||||
| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |
|
||||
| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |
|
||||
| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |
|
||||
| 驼峰/下划线命名转换 | `@/utils/stringFun` |
|
||||
| 获取系统参数 | `@/utils/params` 中的 `getParams` |
|
||||
| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |
|
||||
| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |
|
||||
| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |
|
||||
| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |
|
||||
| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -3,7 +3,7 @@ description: Report a bug to help us improve Gin-Vue-Admin
|
||||
title: "[Bug]: "
|
||||
labels: [bug]
|
||||
assignees:
|
||||
- piexlmax
|
||||
- pixelmaxQm
|
||||
- songzhibin97
|
||||
- SliverHorn
|
||||
- bypanghu
|
||||
|
||||
@@ -3,7 +3,7 @@ description: Suggest an idea for Gin-Vue-Admin
|
||||
title: "[Feature]: "
|
||||
labels: [feature]
|
||||
assignees:
|
||||
- piexlmax
|
||||
- pixelmaxQm
|
||||
body:
|
||||
- type: textarea
|
||||
id: desc
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.8.0]
|
||||
node-version: [18.16.0]
|
||||
steps:
|
||||
- name: Check out branch
|
||||
uses: actions/checkout@v2
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.18]
|
||||
go-version: [1.22]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v1
|
||||
@@ -83,8 +83,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.8.0]
|
||||
go-version: [1.18]
|
||||
node-version: [18.16.0]
|
||||
go-version: [1.22]
|
||||
steps:
|
||||
- name: Check out branch
|
||||
uses: actions/checkout@v2
|
||||
@@ -194,8 +194,8 @@ jobs:
|
||||
name: devops-prod
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: ['16.x']
|
||||
go-version: ['1.18']
|
||||
node-version: ['18.x']
|
||||
go-version: ['1.22']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: tag major and minor versions
|
||||
|
||||
+13
-1
@@ -1,4 +1,3 @@
|
||||
.idea/
|
||||
/web/node_modules
|
||||
/web/dist
|
||||
|
||||
@@ -22,10 +21,23 @@ yarn-error.log*
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
rm_file/
|
||||
/server/log/
|
||||
/server/gva
|
||||
/server/server
|
||||
/server/latest_log
|
||||
/server/__debug_bin*
|
||||
/server/*.local.yaml
|
||||
server/uploads/
|
||||
|
||||
*.iml
|
||||
web/.pnpm-debug.log
|
||||
web/pnpm-lock.yaml
|
||||
|
||||
# binary files
|
||||
*.exe
|
||||
|
||||
# SQLite database files
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
@@ -0,0 +1,902 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 以上三个字段返回给前端并未做驼峰处理,json内依然是 `ID`, `CreatedAt`, `UpdatedAt`
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **插件注册**: **必须**调用 ```
|
||||
func init() {
|
||||
interfaces.Register(Plugin)
|
||||
}
|
||||
```
|
||||
方法,让插件自动注册到本体中
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 插件默认注册功能
|
||||
|
||||
`plugin/register.go` 文件下用 ` _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/插件"
|
||||
` 的方式匿名引用用于激活插件本体的init
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
- **必须**优先使用UnoCSS原子化类名进行样式设计
|
||||
- **必须**优先el-drawer组件进行编辑,新增,步骤等操作
|
||||
- **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性,确保组件销毁,避免内存泄漏和状态污染
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **⚠️ 前端工具库使用规范(强制)**
|
||||
|
||||
> **核心原则:在开发任何前端功能时,必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数,严禁重复造轮子。**
|
||||
|
||||
`src/utils/` 目录提供了项目级别的通用工具集,涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明:
|
||||
|
||||
### **工具文件清单**
|
||||
|
||||
#### `request.js` — HTTP 请求封装(核心)
|
||||
- 基于 Axios 封装的统一 HTTP 请求实例,内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截
|
||||
- **所有 API 请求必须且只能通过此模块发送,禁止直接使用 axios**
|
||||
- 用法:`import service from '@/utils/request'`
|
||||
|
||||
#### `date.js` — 日期格式化
|
||||
- 扩展了 `Date.prototype.Format` 方法,支持自定义格式如 `yyyy-MM-dd hh:mm:ss`
|
||||
- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串
|
||||
- **需要格式化日期时,优先使用此工具,禁止自行手写日期格式化逻辑**
|
||||
- 用法:`import { formatTimeToStr } from '@/utils/date'`
|
||||
|
||||
#### `format.js` — 数据展示格式化(综合工具)
|
||||
- `formatBoolean(bool)` — 将布尔值转为 "是"/"否" 中文展示
|
||||
- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串
|
||||
- `filterDict(value, options)` — 在字典选项数组(支持多级树形)中根据 value 查找对应的 label
|
||||
- `filterDataSource(dataSource, value)` — 在数据源(支持多级树形)中根据 value 查找 label,支持数组批量查找
|
||||
- `getDictFunc(type)` — 异步获取指定类型的字典数据
|
||||
- `ReturnArrImg(arr)` — 将图片路径(单个或数组)转为完整 URL,自动补全服务器前缀
|
||||
- `onDownloadFile(url)` — 触发文件下载
|
||||
- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量(支持亮/暗模式)
|
||||
- `CreateUUID()` — 生成 UUID v4 字符串
|
||||
- `getBaseUrl()` — 获取当前环境的 API BaseURL
|
||||
- **以上所有格式化场景优先使用此文件中的工具函数**
|
||||
- 用法:`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`
|
||||
|
||||
#### `dictionary.js` — 字典数据获取
|
||||
- `getDict(type, options)` — 异步获取字典数据,支持 `depth`(深度)和 `value`(指定节点)参数,内置 Pinia store 缓存,避免重复请求
|
||||
- **凡是需要字典下拉数据、字典树形数据的场景,必须使用此工具**
|
||||
- 用法:`import { getDict } from '@/utils/dictionary'`
|
||||
|
||||
#### `stringFun.js` — 字符串处理
|
||||
- `toUpperCase(str)` — 首字母转大写
|
||||
- `toLowerCase(str)` — 首字母转小写
|
||||
- `toSQLLine(str)` — 驼峰命名转下划线(snake_case),如 `userName` → `user_name`
|
||||
- `toHump(name)` — 下划线命名转驼峰,如 `user_name` → `userName`
|
||||
- **进行命名格式转换时必须使用此工具,禁止使用正则手写**
|
||||
- 用法:`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`
|
||||
|
||||
#### `params.js` — 系统参数获取
|
||||
- `getParams(key)` — 异步从 Pinia store 中获取系统参数,内置缓存
|
||||
- **获取系统配置参数时,优先使用此工具**
|
||||
- 用法:`import { getParams } from '@/utils/params'`
|
||||
|
||||
#### `bus.js` — 全局事件总线
|
||||
- 基于 `mitt` 封装的全局事件总线实例 `emitter`,用于跨组件通信
|
||||
- **跨层级组件通信优先使用此事件总线,避免滥用 Pinia**
|
||||
- 用法:`import { emitter } from '@/utils/bus'`
|
||||
|
||||
#### `closeThisPage.js` — 关闭当前标签页
|
||||
- `closeThisPage()` — 触发关闭当前多标签页的操作(通过事件总线发送 `closeThisPage` 事件)
|
||||
- **在需要程序化关闭当前页面时,必须使用此工具**
|
||||
- 用法:`import { closeThisPage } from '@/utils/closeThisPage'`
|
||||
|
||||
#### `downloadImg.js` — 图片下载
|
||||
- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载,支持跨域
|
||||
- **需要下载图片时,优先使用此工具**
|
||||
- 用法:`import { downloadImage } from '@/utils/downloadImg'`
|
||||
|
||||
#### `image.js` — 图片压缩
|
||||
- 导出 `ImageCompress` 类,支持图片等比压缩至指定最大宽高,并可限制文件大小
|
||||
- **上传图片前需要做压缩处理时,使用此工具**
|
||||
- 用法:`import ImageCompress from '@/utils/image'`
|
||||
|
||||
#### `event.js` — DOM 事件监听管理
|
||||
- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听
|
||||
- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听
|
||||
- **手动操作 DOM 事件时,使用此工具以确保安全性**
|
||||
- 用法:`import { addEventListen, removeEventListen } from '@/utils/event'`
|
||||
|
||||
#### `env.js` — 环境判断
|
||||
- `isDev` — 是否为开发环境(Boolean)
|
||||
- `isProd` — 是否为生产环境(Boolean)
|
||||
- **需要区分运行环境时,使用此工具,禁止直接读取 `import.meta.env`**
|
||||
- 用法:`import { isDev, isProd } from '@/utils/env'`
|
||||
|
||||
#### `doc.js` — 外部文档跳转
|
||||
- `toDoc(url)` — 在新标签页打开指定 URL
|
||||
- 用法:`import { toDoc } from '@/utils/doc'`
|
||||
|
||||
#### `fmtRouterTitle.js` — 路由标题格式化
|
||||
- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值(如 `${id}` 替换为路由 params/query 值)
|
||||
- 用法:`import { fmtTitle } from '@/utils/fmtRouterTitle'`
|
||||
|
||||
#### `page.js` — 页面标题生成
|
||||
- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题(格式:`页面名 - 应用名`)
|
||||
- 用法:`import getPageTitle from '@/utils/page'`
|
||||
|
||||
#### `asyncRouter.js` — 异步路由处理
|
||||
- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置(字符串 component 路径)动态转换为 Vue 组件的 import 函数,支持 `view/` 和 `plugin/` 目录
|
||||
- **动态路由相关逻辑已由此工具处理,不需要也不应该手动实现**
|
||||
- 用法:`import { asyncRouterHandle } from '@/utils/asyncRouter'`
|
||||
|
||||
#### `btnAuth.js` — 按钮权限
|
||||
- `useBtnAuth()` — Composition API Hook,返回当前路由挂载的按钮权限对象(来自 `route.meta.btns`),用于控制操作按钮的显示
|
||||
- **实现按钮级别权限控制时,必须使用此 Hook**
|
||||
- 用法:`import { useBtnAuth } from '@/utils/btnAuth'`
|
||||
|
||||
### **使用强制要求**
|
||||
|
||||
| 场景 | 必须使用的工具 |
|
||||
|------|----------------|
|
||||
| 发送 HTTP 请求 | `@/utils/request` |
|
||||
| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |
|
||||
| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |
|
||||
| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |
|
||||
| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |
|
||||
| 驼峰/下划线命名转换 | `@/utils/stringFun` |
|
||||
| 获取系统参数 | `@/utils/params` 中的 `getParams` |
|
||||
| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |
|
||||
| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |
|
||||
| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |
|
||||
| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |
|
||||
| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 JiZhao Jiang
|
||||
Copyright 2019 北京翻转极光科技有限责任公司
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -2,9 +2,9 @@ SHELL = /bin/bash
|
||||
|
||||
#SCRIPT_DIR = $(shell pwd)/etc/script
|
||||
#请选择golang版本
|
||||
BUILD_IMAGE_SERVER = golang:1.18
|
||||
BUILD_IMAGE_SERVER = golang:1.22
|
||||
#请选择node版本
|
||||
BUILD_IMAGE_WEB = node:16
|
||||
BUILD_IMAGE_WEB = node:20
|
||||
#项目名称
|
||||
PROJECT_NAME = github.com/flipped-aurora/gin-vue-admin/server
|
||||
#配置文件目录
|
||||
@@ -13,16 +13,9 @@ CONFIG_FILE = config.yaml
|
||||
IMAGE_NAME = gva
|
||||
#镜像地址
|
||||
REPOSITORY = registry.cn-hangzhou.aliyuncs.com/${IMAGE_NAME}
|
||||
|
||||
ifeq ($(TAGS_OPT),)
|
||||
TAGS_OPT = latest
|
||||
else
|
||||
endif
|
||||
|
||||
ifeq ($(PLUGIN),)
|
||||
PLUGIN = email
|
||||
else
|
||||
endif
|
||||
#镜像版本
|
||||
TAGS_OPT ?= latest
|
||||
PLUGIN ?= email
|
||||
|
||||
#容器环境前后端共同打包
|
||||
build: build-web build-server
|
||||
@@ -48,7 +41,7 @@ build-image-server:
|
||||
build-local:
|
||||
if [ -d "build" ];then rm -rf build; else echo "OK!"; fi \
|
||||
&& if [ -f "/.dockerenv" ];then echo "OK!"; else make build-web-local && make build-server-local; fi \
|
||||
&& mkdir build && cp -r web/dist build/ && cp server/server build/ && cp -r server/resource build/resource
|
||||
&& mkdir build && cp -r web/dist build/ && cp server/server build/ && cp -r server/resource build/resource
|
||||
|
||||
#本地环境打包前端
|
||||
build-web-local:
|
||||
@@ -63,13 +56,17 @@ build-server-local:
|
||||
&& go build -ldflags "-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') -X main.Version=${TAGS_OPT}" -v
|
||||
|
||||
#打包前后端二合一镜像
|
||||
image: build
|
||||
image: build
|
||||
docker build -t ${REPOSITORY}/gin-vue-admin:${TAGS_OPT} -f deploy/docker/Dockerfile .
|
||||
|
||||
#尝鲜版
|
||||
images: build build-image-web build-image-server
|
||||
docker build -t ${REPOSITORY}/all:${TAGS_OPT} -f deploy/docker/Dockerfile .
|
||||
|
||||
|
||||
#swagger 文档生成
|
||||
doc:
|
||||
@cd server && swag init
|
||||
|
||||
#插件快捷打包: make plugin PLUGIN="这里是插件文件夹名称,默认为email"
|
||||
plugin:
|
||||
if [ -d ".plugin" ];then rm -rf .plugin ; else echo "OK!"; fi && mkdir -p .plugin/${PLUGIN}/{server/plugin,web/plugin} \
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
|
||||
<div align=center>
|
||||
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width=300" height="300" />
|
||||
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width="300" height="300" />
|
||||
</div>
|
||||
<div align=center>
|
||||
<img src="https://img.shields.io/badge/golang-1.18-blue"/>
|
||||
@@ -16,10 +16,6 @@ English | [简体中文](./README.md)
|
||||
|
||||
[github](https://github.com/flipped-aurora/gin-vue-admin): https://github.com/flipped-aurora/gin-vue-admin
|
||||
|
||||
[Vue3 version branch address](https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop): https://github.com/flipped-aurora/gin-vue-admin/tree/vue3Develop
|
||||
|
||||
[Approval flow branch](https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow): https://github.com/flipped-aurora/gin-vue-admin/tree/gva_workflow
|
||||
|
||||
# Project Guidelines
|
||||
[Online Documentation](https://www.gin-vue-admin.com/) : https://www.gin-vue-admin.com/
|
||||
|
||||
@@ -152,7 +148,7 @@ go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
|
||||
cd server
|
||||
go generate -run "go env -w .*?"
|
||||
|
||||
# 使用如下命令下载swag
|
||||
# Use the following command to download swag
|
||||
go get -u github.com/swaggo/swag/cmd/swag
|
||||
````
|
||||
|
||||
@@ -303,6 +299,10 @@ swag init
|
||||
|
||||
Thank you for considering your contribution to gin-vue-admin!
|
||||
|
||||
<a href="https://openomy.app/github/flipped-aurora/gin-vue-admin" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=flipped-aurora/gin-vue-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/flipped-aurora/gin-vue-admin/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=flipped-aurora/gin-vue-admin" />
|
||||
</a>
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
|
||||
<div align=center>
|
||||
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width=300" height="300" />
|
||||
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width="300" height="300" />
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
<img src="https://img.shields.io/badge/golang-1.18-blue"/>
|
||||
<img src="https://img.shields.io/badge/golang-1.20-blue"/>
|
||||
<img src="https://img.shields.io/badge/gin-1.9.1-lightBlue"/>
|
||||
<img src="https://img.shields.io/badge/vue-3.3.4-brightgreen"/>
|
||||
<img src="https://img.shields.io/badge/element--plus-2.3.8-green"/>
|
||||
<img src="https://img.shields.io/badge/gorm-1.25.2-red"/>
|
||||
<img src="https://gitcode.com/flipped-aurora/gin-vue-admin/star/badge.svg"/>
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
<a href="https://trendshift.io/repositories/3250" target="_blank"><img src="https://trendshift.io/api/badge/repositories/3250" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
</div>
|
||||
|
||||
[English](./README-en.md) | 简体中文
|
||||
|
||||
## 不同版本
|
||||
## ✨一分钟生成前后端基础代码
|
||||
|
||||
我们会对以下四个版本持续维护,请选择适合自己的版本使用。最新技术栈为组合式api版本,已支持多语言(I18N)
|
||||
<table>
|
||||
<tr>
|
||||
<td width="250">
|
||||
<p>⭐️ <a href="https://www.bilibili.com/video/BV1B3htzqEf1/?spm_id_from=333.1387.homepage.video_card.click" target="__blank"> 高度适配AI编辑器的MCP </a></p>
|
||||
<p>📄 创建基础模板</p>
|
||||
<p>🤖 AI生成结构</p>
|
||||
<p>⏰ 生成代码</p>
|
||||
<p>🏷️ 分配权限</p>
|
||||
<p>🎉 基础CURD生成完成</p>
|
||||
</td>
|
||||
<td>
|
||||
<video src="https://private-user-images.githubusercontent.com/165128580/384700666-4d039215-af29-4f86-bb4f-60dbab38f58e.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzEyNTIxNDYsIm5iZiI6MTczMTI1MTg0NiwicGF0aCI6Ii8xNjUxMjg1ODAvMzg0NzAwNjY2LTRkMDM5MjE1LWFmMjktNGY4Ni1iYjRmLTYwZGJhYjM4ZjU4ZS5tcDQ_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQxMTEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MTExMFQxNTE3MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT00NjJkMDcwZjJkMjAyMmU1N2I2MzQxY2RhODFlNzgzNGRiMDFhMmY2NTYyM2ZmODdhNDVmMWE1NzlhMDdlOTI5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZJbswpLzF2RHjemcGirKOP0L1fvpl3FUqIiQ_-yjeUo" data-canonical-src="https://private-user-images.githubusercontent.com/165128580/384700666-4d039215-af29-4f86-bb4f-60dbab38f58e.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzEyNTIxNDYsIm5iZiI6MTczMTI1MTg0NiwicGF0aCI6Ii8xNjUxMjg1ODAvMzg0NzAwNjY2LTRkMDM5MjE1LWFmMjktNGY4Ni1iYjRmLTYwZGJhYjM4ZjU4ZS5tcDQ_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQxMTEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MTExMFQxNTE3MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT00NjJkMDcwZjJkMjAyMmU1N2I2MzQxY2RhODFlNzgzNGRiMDFhMmY2NTYyM2ZmODdhNDVmMWE1NzlhMDdlOTI5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZJbswpLzF2RHjemcGirKOP0L1fvpl3FUqIiQ_-yjeUo" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; min-height: 200px">
|
||||
</video>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[组合式API版(主)](https://github.com/flipped-aurora/gin-vue-admin) |
|
||||
[组合式API多语言(i18n)版](https://github.com/flipped-aurora/gin-vue-admin/tree/i18n-dev-new) |
|
||||
[声明式API版](https://github.com/flipped-aurora/gin-vue-admin/tree/v2.4.x) |
|
||||
[声明式API多语言(i18n)版](https://github.com/flipped-aurora/gin-vue-admin/tree/i18n-dev)
|
||||
|
||||
# 项目文档
|
||||
[在线文档](https://www.gin-vue-admin.com) : https://www.gin-vue-admin.com
|
||||
@@ -34,6 +51,8 @@
|
||||
|
||||
[插件市场](https://plugin.gin-vue-admin.com/)
|
||||
|
||||
[软件著作权证书](https://www.gin-vue-admin.com/copyright.pdf)
|
||||
|
||||
# 重要提示
|
||||
|
||||
1.本项目从起步到开发到部署均有文档和详细视频教程
|
||||
@@ -42,9 +61,11 @@
|
||||
|
||||
3.您完全可以通过我们的教程和文档完成一切操作,因此我们不再提供免费的技术服务,如需服务请进行[付费支持](https://www.gin-vue-admin.com/coffee/payment.html)
|
||||
|
||||
4.如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。您需保留如下版权声明信息,其余信息功能不做任何限制。如需剔除请[购买授权](https://www.gin-vue-admin.com/empower/index.html)
|
||||
4.如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。您需保留如下版权声明信息,以及日志和代码中所包含的版权声明信息。所需保留信息均为文案性质,不会影响任何业务内容,如决定商用【产生收益的商业行为均在商用行列】或者必须剔除请[购买授权](https://plugin.gin-vue-admin.com/licenseindex.html)
|
||||
\
|
||||
<img src="https://qmplusimg.henrongyi.top/openSource/login.jpg" width="1000">
|
||||
|
||||
<img src="https://qmplusimg.henrongyi.top/%E6%8E%88%E6%9D%83.png" width="1000">
|
||||
<img src="https://qmplusimg.henrongyi.top/openSource/dashboard.jpg" width="1000">
|
||||
|
||||
## 1. 基本介绍
|
||||
|
||||
@@ -82,8 +103,8 @@ Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-adm
|
||||
## 2. 使用说明
|
||||
|
||||
```
|
||||
- node版本 > v16.8.3
|
||||
- golang版本 >= v1.16
|
||||
- node版本 > v18.16.0
|
||||
- golang版本 >= v1.22
|
||||
- IDE推荐:Goland
|
||||
```
|
||||
|
||||
@@ -101,11 +122,9 @@ cd server
|
||||
# 使用 go mod 并安装go依赖包
|
||||
go generate
|
||||
|
||||
# 编译
|
||||
go build -o server main.go (windows编译命令为go build -o server.exe main.go )
|
||||
# 运行
|
||||
go run .
|
||||
|
||||
# 运行二进制
|
||||
./server (windows运行命令为 server.exe)
|
||||
```
|
||||
|
||||
### 2.2 web项目
|
||||
@@ -125,29 +144,8 @@ npm run serve
|
||||
|
||||
#### 2.3.1 安装 swagger
|
||||
|
||||
##### (1)可以访问外国网站
|
||||
|
||||
````
|
||||
go get -u github.com/swaggo/swag/cmd/swag
|
||||
````
|
||||
|
||||
##### (2)无法访问外国网站
|
||||
|
||||
由于国内没法安装 go.org/x 包下面的东西,推荐使用 [goproxy.cn](https://goproxy.cn) 或者 [goproxy.io](https://goproxy.io/zh/)
|
||||
|
||||
```bash
|
||||
# 如果您使用的 Go 版本是 1.13 - 1.15 需要手动设置GO111MODULE=on, 开启方式如下命令, 如果你的 Go 版本 是 1.16 ~ 最新版 可以忽略以下步骤一
|
||||
# 步骤一、启用 Go Modules 功能
|
||||
go env -w GO111MODULE=on
|
||||
# 步骤二、配置 GOPROXY 环境变量
|
||||
go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
|
||||
|
||||
# 如果嫌弃麻烦,可以使用go generate 编译前自动执行代码, 不过这个不能使用 `Goland` 或者 `Vscode` 的 命令行终端
|
||||
cd server
|
||||
go generate -run "go env -w .*?"
|
||||
|
||||
# 使用如下命令下载swag
|
||||
go get -u github.com/swaggo/swag/cmd/swag
|
||||
``` shell
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
```
|
||||
|
||||
#### 2.3.2 生成API文档
|
||||
@@ -352,17 +350,20 @@ swag init
|
||||
|
||||
### 7.1 技术群
|
||||
|
||||
### QQ交流群:622360840
|
||||
| QQ 群 |
|
||||
| :---: |
|
||||
| <img src="http://qmplusimg.henrongyi.top/qq.jpg" width="180"/> |
|
||||
### QQ交流群:971857775
|
||||
|
||||
### 微信交流群
|
||||
| 微信 |
|
||||
| :---: |
|
||||
| <img width="150" src="http://qmplusimg.henrongyi.top/qrjjz.png">
|
||||
|
||||
添加微信,备注"加入gin-vue-admin交流群"
|
||||
防止广告进群,添加微信,输入以下代码执行结果(请勿转码为string)
|
||||
|
||||
```
|
||||
str := "5Yqg5YWlR1ZB5Lqk5rWB576k"
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(str)
|
||||
fmt.Println(decodeBytes, err)
|
||||
```
|
||||
|
||||
### [关于我们](https://www.gin-vue-admin.com/about/join.html)
|
||||
|
||||
@@ -370,14 +371,15 @@ swag init
|
||||
|
||||
感谢您对gin-vue-admin的贡献!
|
||||
|
||||
<a href="https://github.com/flipped-aurora/gin-vue-admin/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=flipped-aurora/gin-vue-admin" />
|
||||
</a>
|
||||
<a href="https://openomy.app/github/flipped-aurora/gin-vue-admin" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=flipped-aurora/gin-vue-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
## 9. 捐赠
|
||||
|
||||
如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: [点我](https://www.gin-vue-admin.com/coffee/index.html)
|
||||
|
||||
## 10. 商用注意事项
|
||||
## 10. 注意事项
|
||||
|
||||
如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。
|
||||
请严格遵守Apache 2.0协议并保留作品声明,去除版权信息请务必[获取授权](https://plugin.gin-vue-admin.com/license)
|
||||
未授权去除版权信息将依法追究法律责任
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
# 声明一个名为network的networks,subnet为network的子网地址,默认网关是177.7.0.1
|
||||
networks:
|
||||
network:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: '177.7.0.0/16'
|
||||
|
||||
# 设置mysql,redis持久化保存
|
||||
volumes:
|
||||
mysql:
|
||||
redis:
|
||||
|
||||
services:
|
||||
web:
|
||||
image: node:16
|
||||
container_name: gva-web
|
||||
hostname: gva-web #可以通过容器名访问
|
||||
restart: always
|
||||
ports:
|
||||
- '8080:8080'
|
||||
depends_on:
|
||||
- server
|
||||
working_dir: /web # 如果docker 设置了workdir 则此处不需要设置
|
||||
#若网络不太好,请自行换源,如下
|
||||
#command: bash -c "yarn config set registry https://registry.npm.taobao.org --global && yarn install && yarn serve"
|
||||
command: bash -c "yarn install && yarn serve"
|
||||
volumes:
|
||||
- ../../web:/web
|
||||
networks:
|
||||
network:
|
||||
ipv4_address: 177.7.0.11
|
||||
|
||||
server:
|
||||
image: golang:1.18
|
||||
container_name: gva-server
|
||||
hostname: gva-server
|
||||
restart: always
|
||||
ports:
|
||||
- '8888:8888'
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
volumes:
|
||||
- ../../server:/server
|
||||
working_dir: /server # 如果docker 设置了workdir 则此处不需要设置
|
||||
command: bash -c "go env -w GOPROXY=https://goproxy.cn,direct && go mod tidy && go run main.go"
|
||||
links:
|
||||
- mysql
|
||||
- redis
|
||||
networks:
|
||||
network:
|
||||
ipv4_address: 177.7.0.12
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.21 # 如果您是 arm64 架构:如 MacOS 的 M1,请修改镜像为 image: mysql/mysql-server:8.0.21
|
||||
container_name: gva-mysql
|
||||
hostname: gva-mysql
|
||||
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #设置utf8字符集
|
||||
restart: always
|
||||
ports:
|
||||
- "13306:3306" # host物理直接映射端口为13306
|
||||
environment:
|
||||
#MYSQL_ROOT_PASSWORD: 'Aa@6447985' # root管理员用户密码
|
||||
MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
|
||||
MYSQL_USER: 'gva'
|
||||
MYSQL_PASSWORD: 'Aa@6447985'
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
networks:
|
||||
network:
|
||||
ipv4_address: 177.7.0.13
|
||||
|
||||
redis:
|
||||
image: redis:6.0.6
|
||||
container_name: gva-redis # 容器名
|
||||
hostname: gva-redis
|
||||
restart: always
|
||||
ports:
|
||||
- '16379:6379'
|
||||
volumes:
|
||||
- redis:/data
|
||||
networks:
|
||||
network:
|
||||
ipv4_address: 177.7.0.14
|
||||
@@ -38,8 +38,10 @@ services:
|
||||
ports:
|
||||
- '8888:8888'
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
links:
|
||||
- mysql
|
||||
- redis
|
||||
@@ -59,6 +61,11 @@ services:
|
||||
MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
|
||||
MYSQL_USER: 'gva'
|
||||
MYSQL_PASSWORD: 'Aa@6447985'
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "gva", "-pAa@6447985"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
networks:
|
||||
@@ -71,6 +78,11 @@ services:
|
||||
restart: always
|
||||
ports:
|
||||
- '16379:6379'
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping | grep PONG || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
volumes:
|
||||
- redis:/data
|
||||
networks:
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN set -ex \
|
||||
&& echo "LANG=en_US.utf8" > /etc/locale.conf \
|
||||
&& echo "net.core.somaxconn = 1024" >> /etc/sysctl.conf \
|
||||
&& echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf \
|
||||
&& yum -y install yum -y install *epel* \
|
||||
&& yum -y install epel-release \
|
||||
&& yum -y localinstall http://mirrors.ustc.edu.cn/mysql-repo/mysql57-community-release-el7.rpm \
|
||||
&& yum -y install mysql-community-server git redis nginx go npm --nogpgcheck && chmod +x ./entrypoint.sh \
|
||||
&& npm install -g yarn && go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct \
|
||||
|
||||
@@ -13,6 +13,10 @@ RUN go env -w GO111MODULE=on \
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com"
|
||||
# 设置时区
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN apk update && apk add --no-cache tzdata openntpd \
|
||||
&& ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
WORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
@@ -20,5 +24,8 @@ COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/server ./
|
||||
COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/resource ./resource/
|
||||
COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/config.docker.yaml ./
|
||||
|
||||
# 挂载目录:如果使用了sqlite数据库,容器命令示例:docker run -d -v /宿主机路径/gva.db:/go/src/github.com/flipped-aurora/gin-vue-admin/server/gva.db -p 8888:8888 --name gva-server-v1 gva-server:1.0
|
||||
# VOLUME ["/go/src/github.com/flipped-aurora/gin-vue-admin/server"]
|
||||
|
||||
EXPOSE 8888
|
||||
ENTRYPOINT ./server -c config.docker.yaml
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/api/v1/system"
|
||||
)
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
|
||||
type ApiGroup struct {
|
||||
SystemApiGroup system.ApiGroup
|
||||
ExampleApiGroup example.ApiGroup
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
|
||||
@@ -5,9 +5,11 @@ import "github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
type ApiGroup struct {
|
||||
CustomerApi
|
||||
FileUploadAndDownloadApi
|
||||
AttachmentCategoryApi
|
||||
}
|
||||
|
||||
var (
|
||||
customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService
|
||||
fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
|
||||
attachmentCategoryService = service.ServiceGroupApp.ExampleServiceGroup.AttachmentCategoryService
|
||||
)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package example
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AttachmentCategoryApi struct{}
|
||||
|
||||
// GetCategoryList
|
||||
// @Tags GetCategoryList
|
||||
// @Summary 媒体库分类列表
|
||||
// @Security AttachmentCategory
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=example.ExaAttachmentCategory,msg=string} "媒体库分类列表"
|
||||
// @Router /attachmentCategory/getCategoryList [get]
|
||||
func (a *AttachmentCategoryApi) GetCategoryList(c *gin.Context) {
|
||||
res, err := attachmentCategoryService.GetCategoryList()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取分类列表失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取分类列表失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(res, c)
|
||||
}
|
||||
|
||||
// AddCategory
|
||||
// @Tags AddCategory
|
||||
// @Summary 添加媒体库分类
|
||||
// @Security AttachmentCategory
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body example.ExaAttachmentCategory true "媒体库分类数据"// @Success 200 {object} response.Response{msg=string} "添加媒体库分类"
|
||||
// @Router /attachmentCategory/addCategory [post]
|
||||
func (a *AttachmentCategoryApi) AddCategory(c *gin.Context) {
|
||||
var req example.ExaAttachmentCategory
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
global.GVA_LOG.Error("参数错误!", zap.Error(err))
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := attachmentCategoryService.AddCategory(&req); err != nil {
|
||||
global.GVA_LOG.Error("创建/更新失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建/更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建/更新成功", c)
|
||||
}
|
||||
|
||||
// DeleteCategory
|
||||
// @Tags DeleteCategory
|
||||
// @Summary 删除分类
|
||||
// @Security AttachmentCategory
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body common.GetById true "分类id"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除分类"
|
||||
// @Router /attachmentCategory/deleteCategory [post]
|
||||
func (a *AttachmentCategoryApi) DeleteCategory(c *gin.Context) {
|
||||
var req common.GetById
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if req.ID == 0 {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := attachmentCategoryService.DeleteCategory(&req.ID); err != nil {
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
|
||||
|
||||
@@ -84,7 +85,7 @@ func (b *FileUploadAndDownloadApi) BreakpointContinue(c *gin.Context) {
|
||||
// @Produce application/json
|
||||
// @Param file formData file true "Find the file, 查找文件"
|
||||
// @Success 200 {object} response.Response{data=exampleRes.FileResponse,msg=string} "查找文件,返回包括文件详情"
|
||||
// @Router /fileUploadAndDownload/findFile [post]
|
||||
// @Router /fileUploadAndDownload/findFile [get]
|
||||
func (b *FileUploadAndDownloadApi) FindFile(c *gin.Context) {
|
||||
fileMd5 := c.Query("fileMd5")
|
||||
fileName := c.Query("fileName")
|
||||
@@ -135,6 +136,11 @@ func (b *FileUploadAndDownloadApi) RemoveChunk(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 路径穿越拦截
|
||||
if strings.Contains(file.FilePath, "..") || strings.Contains(file.FilePath, "../") || strings.Contains(file.FilePath, "./") || strings.Contains(file.FilePath, ".\\") {
|
||||
response.FailWithMessage("非法路径,禁止删除", c)
|
||||
return
|
||||
}
|
||||
err = utils.RemoveChunk(file.FileMd5)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("缓存切片删除失败!", zap.Error(err))
|
||||
|
||||
@@ -2,12 +2,13 @@ package example
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example/request"
|
||||
exampleRes "github.com/flipped-aurora/gin-vue-admin/server/model/example/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type FileUploadAndDownloadApi struct{}
|
||||
@@ -25,15 +26,16 @@ func (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) {
|
||||
var file example.ExaFileUploadAndDownload
|
||||
noSave := c.DefaultQuery("noSave", "0")
|
||||
_, header, err := c.Request.FormFile("file")
|
||||
classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("接收文件失败!", zap.Error(err))
|
||||
response.FailWithMessage("接收文件失败", c)
|
||||
return
|
||||
}
|
||||
file, err = fileUploadAndDownloadService.UploadFile(header, noSave) // 文件上传后拿到文件路径
|
||||
file, err = fileUploadAndDownloadService.UploadFile(header, noSave, classId) // 文件上传后拿到文件路径
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("修改数据库链接失败!", zap.Error(err))
|
||||
response.FailWithMessage("修改数据库链接失败", c)
|
||||
global.GVA_LOG.Error("上传文件失败!", zap.Error(err))
|
||||
response.FailWithMessage("上传文件失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(exampleRes.ExaFileResponse{File: file}, "上传成功", c)
|
||||
@@ -85,11 +87,11 @@ func (b *FileUploadAndDownloadApi) DeleteFile(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Param data body request.ExaAttachmentCategorySearch true "页码, 每页大小, 分类id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /fileUploadAndDownload/getFileList [post]
|
||||
func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) {
|
||||
var pageInfo request.PageInfo
|
||||
var pageInfo request.ExaAttachmentCategorySearch
|
||||
err := c.ShouldBindJSON(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
@@ -108,3 +110,26 @@ func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) {
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// ImportURL
|
||||
// @Tags ExaFileUploadAndDownload
|
||||
// @Summary 导入URL
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param data body example.ExaFileUploadAndDownload true "对象"
|
||||
// @Success 200 {object} response.Response{msg=string} "导入URL"
|
||||
// @Router /fileUploadAndDownload/importURL [post]
|
||||
func (b *FileUploadAndDownloadApi) ImportURL(c *gin.Context) {
|
||||
var file []example.ExaFileUploadAndDownload
|
||||
err := c.ShouldBindJSON(&file)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := fileUploadAndDownloadService.ImportURL(&file); err != nil {
|
||||
global.GVA_LOG.Error("导入URL失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入URL失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("导入URL成功", c)
|
||||
}
|
||||
|
||||
+15
-15
@@ -2,9 +2,9 @@ package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
request "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -21,13 +21,13 @@ type AutoCodeHistoryApi struct{}
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取meta信息"
|
||||
// @Router /autoCode/getMeta [post]
|
||||
func (a *AutoCodeHistoryApi) First(c *gin.Context) {
|
||||
var info request.GetById
|
||||
var info common.GetById
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
data, err := autoCodeHistoryService.First(&info)
|
||||
data, err := autoCodeHistoryService.First(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
@@ -45,13 +45,13 @@ func (a *AutoCodeHistoryApi) First(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{msg=string} "删除回滚记录"
|
||||
// @Router /autoCode/delSysHistory [post]
|
||||
func (a *AutoCodeHistoryApi) Delete(c *gin.Context) {
|
||||
var info request.GetById
|
||||
var info common.GetById
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = autoCodeHistoryService.Delete(&info)
|
||||
err = autoCodeHistoryService.Delete(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
@@ -66,17 +66,17 @@ func (a *AutoCodeHistoryApi) Delete(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.RollBack true "请求参数"
|
||||
// @Param data body request.SysAutoHistoryRollBack true "请求参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "回滚自动生成代码"
|
||||
// @Router /autoCode/rollback [post]
|
||||
func (a *AutoCodeHistoryApi) RollBack(c *gin.Context) {
|
||||
var info systemReq.RollBack
|
||||
var info request.SysAutoHistoryRollBack
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = autoCodeHistoryService.RollBack(&info)
|
||||
err = autoCodeHistoryService.RollBack(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
@@ -90,17 +90,17 @@ func (a *AutoCodeHistoryApi) RollBack(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.SysAutoHistory true "请求参数"
|
||||
// @Param data body common.PageInfo true "请求参数"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "查询回滚记录,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /autoCode/getSysHistory [post]
|
||||
func (a *AutoCodeHistoryApi) GetList(c *gin.Context) {
|
||||
var search systemReq.SysAutoHistory
|
||||
err := c.ShouldBindJSON(&search)
|
||||
var info common.PageInfo
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := autoCodeHistoryService.GetList(search.PageInfo)
|
||||
list, total, err := autoCodeHistoryService.GetList(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
@@ -109,7 +109,7 @@ func (a *AutoCodeHistoryApi) GetList(c *gin.Context) {
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: search.Page,
|
||||
PageSize: search.PageSize,
|
||||
Page: info.Page,
|
||||
PageSize: info.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/mcp/client"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
// Create
|
||||
// @Tags mcp
|
||||
// @Summary 自动McpTool
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/mcp [post]
|
||||
func (a *AutoCodeTemplateApi) MCP(c *gin.Context) {
|
||||
var info request.AutoMcpTool
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
toolFilePath, err := autoCodeTemplateService.CreateMcp(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建失败", c)
|
||||
global.GVA_LOG.Error(err.Error())
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功,MCP Tool路径:"+toolFilePath, c)
|
||||
}
|
||||
|
||||
// Create
|
||||
// @Tags mcp
|
||||
// @Summary 自动McpTool
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/mcpList [post]
|
||||
func (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {
|
||||
|
||||
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||
|
||||
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||
defer testClient.Close()
|
||||
toolsRequest := mcp.ListToolsRequest{}
|
||||
|
||||
list, err := testClient.ListTools(c.Request.Context(), toolsRequest)
|
||||
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建失败", c)
|
||||
global.GVA_LOG.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
mcpServerConfig := map[string]interface{}{
|
||||
"mcpServers": map[string]interface{}{
|
||||
global.GVA_CONFIG.MCP.Name: map[string]string{
|
||||
"url": baseUrl,
|
||||
},
|
||||
},
|
||||
}
|
||||
response.OkWithData(gin.H{
|
||||
"mcpServerConfig": mcpServerConfig,
|
||||
"list": list,
|
||||
}, c)
|
||||
}
|
||||
|
||||
// Create
|
||||
// @Tags mcp
|
||||
// @Summary 测试McpTool
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body object true "调用MCP Tool的参数"
|
||||
// @Success 200 {object} response.Response "{"success":true,"data":{},"msg":"测试成功"}"
|
||||
// @Router /autoCode/mcpTest [post]
|
||||
func (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {
|
||||
// 定义接口请求结构
|
||||
var testRequest struct {
|
||||
Name string `json:"name" binding:"required"` // 工具名称
|
||||
Arguments map[string]interface{} `json:"arguments" binding:"required"` // 工具参数
|
||||
}
|
||||
|
||||
// 绑定JSON请求体
|
||||
if err := c.ShouldBindJSON(&testRequest); err != nil {
|
||||
response.FailWithMessage("参数解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建MCP客户端
|
||||
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建MCP客户端失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
defer testClient.Close()
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 初始化MCP连接
|
||||
initRequest := mcp.InitializeRequest{}
|
||||
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
|
||||
initRequest.Params.ClientInfo = mcp.Implementation{
|
||||
Name: "testClient",
|
||||
Version: "v1.0.0",
|
||||
}
|
||||
|
||||
_, err = testClient.Initialize(ctx, initRequest)
|
||||
if err != nil {
|
||||
response.FailWithMessage("初始化MCP连接失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建工具调用请求
|
||||
request := mcp.CallToolRequest{}
|
||||
request.Params.Name = testRequest.Name
|
||||
request.Params.Arguments = testRequest.Arguments
|
||||
|
||||
// 调用工具
|
||||
result, err := testClient.CallTool(ctx, request)
|
||||
if err != nil {
|
||||
response.FailWithMessage("工具调用失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理响应结果
|
||||
if len(result.Content) == 0 {
|
||||
response.FailWithMessage("工具未返回任何内容", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
response.OkWithData(result.Content, c)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AutoCodePackageApi struct{}
|
||||
|
||||
// Create
|
||||
// @Tags AutoCodePackage
|
||||
// @Summary 创建package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.SysAutoCodePackageCreate true "创建package"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||
// @Router /autoCode/createPackage [post]
|
||||
func (a *AutoCodePackageApi) Create(c *gin.Context) {
|
||||
var info request.SysAutoCodePackageCreate
|
||||
_ = c.ShouldBindJSON(&info)
|
||||
if err := utils.Verify(info, utils.AutoPackageVerify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if strings.Contains(info.PackageName, "\\") || strings.Contains(info.PackageName, "/") || strings.Contains(info.PackageName, "..") {
|
||||
response.FailWithMessage("包名不合法", c)
|
||||
return
|
||||
} // PackageName可能导致路径穿越的问题 / 和 \ 都要防止
|
||||
err := autoCodePackageService.Create(c.Request.Context(), &info)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Tags AutoCode
|
||||
// @Summary 删除package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body common.GetById true "创建package"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "删除package成功"
|
||||
// @Router /autoCode/delPackage [post]
|
||||
func (a *AutoCodePackageApi) Delete(c *gin.Context) {
|
||||
var info common.GetById
|
||||
_ = c.ShouldBindJSON(&info)
|
||||
err := autoCodePackageService.Delete(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// All
|
||||
// @Tags AutoCodePackage
|
||||
// @Summary 获取package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||
// @Router /autoCode/getPackage [post]
|
||||
func (a *AutoCodePackageApi) All(c *gin.Context) {
|
||||
data, err := autoCodePackageService.All(c.Request.Context())
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"pkgs": data}, "获取成功", c)
|
||||
}
|
||||
|
||||
// Templates
|
||||
// @Tags AutoCodePackage
|
||||
// @Summary 获取package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||
// @Router /autoCode/getTemplates [get]
|
||||
func (a *AutoCodePackageApi) Templates(c *gin.Context) {
|
||||
data, err := autoCodePackageService.Templates(c.Request.Context())
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(data, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AutoCodePluginApi struct{}
|
||||
|
||||
// Install
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 安装插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept multipart/form-data
|
||||
// @Produce application/json
|
||||
// @Param plug formData file true "this is a test file"
|
||||
// @Success 200 {object} response.Response{data=[]interface{},msg=string} "安装插件成功"
|
||||
// @Router /autoCode/installPlugin [post]
|
||||
func (a *AutoCodePluginApi) Install(c *gin.Context) {
|
||||
header, err := c.FormFile("plug")
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
web, server, err := autoCodePluginService.Install(header)
|
||||
webStr := "web插件安装成功"
|
||||
serverStr := "server插件安装成功"
|
||||
if web == -1 {
|
||||
webStr = "web端插件未成功安装,请按照文档自行解压安装,如果为纯后端插件请忽略此条提示"
|
||||
}
|
||||
if server == -1 {
|
||||
serverStr = "server端插件未成功安装,请按照文档自行解压安装,如果为纯前端插件请忽略此条提示"
|
||||
}
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData([]interface{}{
|
||||
gin.H{
|
||||
"code": web,
|
||||
"msg": webStr,
|
||||
},
|
||||
gin.H{
|
||||
"code": server,
|
||||
"msg": serverStr,
|
||||
}}, c)
|
||||
}
|
||||
|
||||
// Packaged
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 打包插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param plugName query string true "插件名称"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||
// @Router /autoCode/pubPlug [post]
|
||||
func (a *AutoCodePluginApi) Packaged(c *gin.Context) {
|
||||
plugName := c.Query("plugName")
|
||||
zipPath, err := autoCodePluginService.PubPlug(plugName)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("打包失败!", zap.Error(err))
|
||||
response.FailWithMessage("打包失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage(fmt.Sprintf("打包成功,文件路径为:%s", zipPath), c)
|
||||
}
|
||||
|
||||
// InitMenu
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 打包插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||
// @Router /autoCode/initMenu [post]
|
||||
func (a *AutoCodePluginApi) InitMenu(c *gin.Context) {
|
||||
var menuInfo request.InitMenu
|
||||
err := c.ShouldBindJSON(&menuInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = autoCodePluginService.InitMenu(menuInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建初始化Menu失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建初始化Menu失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("文件变更成功", c)
|
||||
}
|
||||
|
||||
// InitAPI
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 打包插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||
// @Router /autoCode/initAPI [post]
|
||||
func (a *AutoCodePluginApi) InitAPI(c *gin.Context) {
|
||||
var apiInfo request.InitApi
|
||||
err := c.ShouldBindJSON(&apiInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = autoCodePluginService.InitAPI(apiInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建初始化API失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建初始化API失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("文件变更成功", c)
|
||||
}
|
||||
|
||||
// InitDictionary
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 打包插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||
// @Router /autoCode/initDictionary [post]
|
||||
func (a *AutoCodePluginApi) InitDictionary(c *gin.Context) {
|
||||
var dictInfo request.InitDictionary
|
||||
err := c.ShouldBindJSON(&dictInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = autoCodePluginService.InitDictionary(dictInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建初始化Dictionary失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建初始化Dictionary失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("文件变更成功", c)
|
||||
}
|
||||
|
||||
// GetPluginList
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 获取插件列表
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=[]systemRes.PluginInfo} "获取插件列表成功"
|
||||
// @Router /autoCode/getPluginList [get]
|
||||
func (a *AutoCodePluginApi) GetPluginList(c *gin.Context) {
|
||||
serverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin")
|
||||
webDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin")
|
||||
|
||||
serverEntries, _ := os.ReadDir(serverDir)
|
||||
webEntries, _ := os.ReadDir(webDir)
|
||||
|
||||
configMap := make(map[string]string)
|
||||
|
||||
for _, entry := range serverEntries {
|
||||
if entry.IsDir() {
|
||||
configMap[entry.Name()] = "server"
|
||||
}
|
||||
}
|
||||
|
||||
for _, entry := range webEntries {
|
||||
if entry.IsDir() {
|
||||
if val, ok := configMap[entry.Name()]; ok {
|
||||
if val == "server" {
|
||||
configMap[entry.Name()] = "full"
|
||||
}
|
||||
} else {
|
||||
configMap[entry.Name()] = "web"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var list []systemRes.PluginInfo
|
||||
for k, v := range configMap {
|
||||
apis, menus, dicts := utils.GetPluginData(k)
|
||||
list = append(list, systemRes.PluginInfo{
|
||||
PluginName: k,
|
||||
PluginType: v,
|
||||
Apis: apis,
|
||||
Menus: menus,
|
||||
Dictionaries: dicts,
|
||||
})
|
||||
}
|
||||
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
||||
|
||||
// Remove
|
||||
// @Tags AutoCodePlugin
|
||||
// @Summary 删除插件
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param pluginName query string true "插件名称"
|
||||
// @Param pluginType query string true "插件类型"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除插件成功"
|
||||
// @Router /autoCode/removePlugin [post]
|
||||
func (a *AutoCodePluginApi) Remove(c *gin.Context) {
|
||||
pluginName := c.Query("pluginName")
|
||||
pluginType := c.Query("pluginType")
|
||||
err := autoCodePluginService.Remove(pluginName, pluginType)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AutoCodeTemplateApi struct{}
|
||||
|
||||
// Preview
|
||||
// @Tags AutoCodeTemplate
|
||||
// @Summary 预览创建后的代码
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoCode true "预览创建代码"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "预览创建后的代码"
|
||||
// @Router /autoCode/preview [post]
|
||||
func (a *AutoCodeTemplateApi) Preview(c *gin.Context) {
|
||||
var info request.AutoCode
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = utils.Verify(info, utils.AutoCodeVerify)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = info.Pretreatment()
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
info.PackageT = utils.FirstUpper(info.Package)
|
||||
autoCode, err := autoCodeTemplateService.Preview(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error(err.Error(), zap.Error(err))
|
||||
response.FailWithMessage("预览失败:"+err.Error(), c)
|
||||
} else {
|
||||
response.OkWithDetailed(gin.H{"autoCode": autoCode}, "预览成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// Create
|
||||
// @Tags AutoCodeTemplate
|
||||
// @Summary 自动代码模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoCode true "创建自动代码"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/createTemp [post]
|
||||
func (a *AutoCodeTemplateApi) Create(c *gin.Context) {
|
||||
var info request.AutoCode
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = utils.Verify(info, utils.AutoCodeVerify)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = info.Pretreatment()
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = autoCodeTemplateService.Create(c.Request.Context(), info)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
} else {
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// AddFunc
|
||||
// @Tags AddFunc
|
||||
// @Summary 增加方法
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.AutoCode true "增加方法"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/addFunc [post]
|
||||
func (a *AutoCodeTemplateApi) AddFunc(c *gin.Context) {
|
||||
var info request.AutoFunc
|
||||
err := c.ShouldBindJSON(&info)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
var tempMap map[string]string
|
||||
if info.IsPreview {
|
||||
info.Router = "填充router"
|
||||
info.FuncName = "填充funcName"
|
||||
info.Method = "填充method"
|
||||
info.Description = "填充description"
|
||||
tempMap, err = autoCodeTemplateService.GetApiAndServer(info)
|
||||
} else {
|
||||
err = autoCodeTemplateService.AddFunc(info)
|
||||
}
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("注入失败!", zap.Error(err))
|
||||
response.FailWithMessage("注入失败", c)
|
||||
} else {
|
||||
if info.IsPreview {
|
||||
response.OkWithDetailed(tempMap, "注入成功", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("注入成功", c)
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,19 @@ type ApiGroup struct {
|
||||
DictionaryApi
|
||||
AuthorityMenuApi
|
||||
OperationRecordApi
|
||||
AutoCodeHistoryApi
|
||||
DictionaryDetailApi
|
||||
AuthorityBtnApi
|
||||
ChatGptApi
|
||||
SysExportTemplateApi
|
||||
AutoCodePluginApi
|
||||
AutoCodePackageApi
|
||||
AutoCodeHistoryApi
|
||||
AutoCodeTemplateApi
|
||||
SysParamsApi
|
||||
SysVersionApi
|
||||
SysErrorApi
|
||||
LoginLogApi
|
||||
ApiTokenApi
|
||||
SkillsApi
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -27,14 +36,22 @@ var (
|
||||
userService = service.ServiceGroupApp.SystemServiceGroup.UserService
|
||||
initDBService = service.ServiceGroupApp.SystemServiceGroup.InitDBService
|
||||
casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService
|
||||
autoCodeService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService
|
||||
baseMenuService = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService
|
||||
authorityService = service.ServiceGroupApp.SystemServiceGroup.AuthorityService
|
||||
dictionaryService = service.ServiceGroupApp.SystemServiceGroup.DictionaryService
|
||||
systemConfigService = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService
|
||||
operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
|
||||
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistoryService
|
||||
dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
|
||||
authorityBtnService = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService
|
||||
chatGptService = service.ServiceGroupApp.SystemServiceGroup.ChatGptService
|
||||
systemConfigService = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService
|
||||
sysParamsService = service.ServiceGroupApp.SystemServiceGroup.SysParamsService
|
||||
operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
|
||||
dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
|
||||
autoCodeService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService
|
||||
autoCodePluginService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePlugin
|
||||
autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
|
||||
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory
|
||||
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
|
||||
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
|
||||
sysErrorService = service.ServiceGroupApp.SystemServiceGroup.SysErrorService
|
||||
loginLogService = service.ServiceGroupApp.SystemServiceGroup.LoginLogService
|
||||
apiTokenService = service.ServiceGroupApp.SystemServiceGroup.ApiTokenService
|
||||
skillsService = service.ServiceGroupApp.SystemServiceGroup.SkillsService
|
||||
)
|
||||
|
||||
@@ -45,6 +45,97 @@ func (s *SystemApiApi) CreateApi(c *gin.Context) {
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// SyncApi
|
||||
// @Tags SysApi
|
||||
// @Summary 同步API
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "同步API"
|
||||
// @Router /api/syncApi [get]
|
||||
func (s *SystemApiApi) SyncApi(c *gin.Context) {
|
||||
newApis, deleteApis, ignoreApis, err := apiService.SyncApi()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("同步失败!", zap.Error(err))
|
||||
response.FailWithMessage("同步失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(gin.H{
|
||||
"newApis": newApis,
|
||||
"deleteApis": deleteApis,
|
||||
"ignoreApis": ignoreApis,
|
||||
}, c)
|
||||
}
|
||||
|
||||
// GetApiGroups
|
||||
// @Tags SysApi
|
||||
// @Summary 获取API分组
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "获取API分组"
|
||||
// @Router /api/getApiGroups [get]
|
||||
func (s *SystemApiApi) GetApiGroups(c *gin.Context) {
|
||||
groups, apiGroupMap, err := apiService.GetApiGroups()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(gin.H{
|
||||
"groups": groups,
|
||||
"apiGroupMap": apiGroupMap,
|
||||
}, c)
|
||||
}
|
||||
|
||||
// IgnoreApi
|
||||
// @Tags IgnoreApi
|
||||
// @Summary 忽略API
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "同步API"
|
||||
// @Router /api/ignoreApi [post]
|
||||
func (s *SystemApiApi) IgnoreApi(c *gin.Context) {
|
||||
var ignoreApi system.SysIgnoreApi
|
||||
err := c.ShouldBindJSON(&ignoreApi)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = apiService.IgnoreApi(ignoreApi)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("忽略失败!", zap.Error(err))
|
||||
response.FailWithMessage("忽略失败", c)
|
||||
return
|
||||
}
|
||||
response.Ok(c)
|
||||
}
|
||||
|
||||
// EnterSyncApi
|
||||
// @Tags SysApi
|
||||
// @Summary 确认同步API
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "确认同步API"
|
||||
// @Router /api/enterSyncApi [post]
|
||||
func (s *SystemApiApi) EnterSyncApi(c *gin.Context) {
|
||||
var syncApi systemRes.SysSyncApis
|
||||
err := c.ShouldBindJSON(&syncApi)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = apiService.EnterSyncApi(syncApi)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("忽略失败!", zap.Error(err))
|
||||
response.FailWithMessage("忽略失败", c)
|
||||
return
|
||||
}
|
||||
response.Ok(c)
|
||||
}
|
||||
|
||||
// DeleteApi
|
||||
// @Tags SysApi
|
||||
// @Summary 删除api
|
||||
@@ -179,7 +270,8 @@ func (s *SystemApiApi) UpdateApi(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{data=systemRes.SysAPIListResponse,msg=string} "获取所有的Api 不分页,返回包括api列表"
|
||||
// @Router /api/getAllApis [post]
|
||||
func (s *SystemApiApi) GetAllApis(c *gin.Context) {
|
||||
apis, err := apiService.GetAllApis()
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
apis, err := apiService.GetAllApis(authorityID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
@@ -221,7 +313,7 @@ func (s *SystemApiApi) DeleteApisByIds(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{msg=string} "刷新成功"
|
||||
// @Router /api/freshCasbin [get]
|
||||
func (s *SystemApiApi) FreshCasbin(c *gin.Context) {
|
||||
err := apiService.FreshCasbin()
|
||||
err := casbinService.FreshCasbin()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("刷新失败!", zap.Error(err))
|
||||
response.FailWithMessage("刷新失败", c)
|
||||
@@ -229,3 +321,61 @@ func (s *SystemApiApi) FreshCasbin(c *gin.Context) {
|
||||
}
|
||||
response.OkWithMessage("刷新成功", c)
|
||||
}
|
||||
|
||||
// GetApiRoles
|
||||
// @Tags SysApi
|
||||
// @Summary 获取拥有指定API权限的角色ID列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param path query string true "API路径"
|
||||
// @Param method query string true "请求方法"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取成功"
|
||||
// @Router /api/getApiRoles [get]
|
||||
func (s *SystemApiApi) GetApiRoles(c *gin.Context) {
|
||||
path := c.Query("path")
|
||||
method := c.Query("method")
|
||||
if path == "" || method == "" {
|
||||
response.FailWithMessage("API路径和请求方法不能为空", c)
|
||||
return
|
||||
}
|
||||
authorityIds, err := casbinService.GetAuthoritiesByApi(path, method)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if authorityIds == nil {
|
||||
authorityIds = []uint{}
|
||||
}
|
||||
response.OkWithDetailed(authorityIds, "获取成功", c)
|
||||
}
|
||||
|
||||
// SetApiRoles
|
||||
// @Tags SysApi
|
||||
// @Summary 全量覆盖某API关联的角色列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.SetApiAuthorities true "API路径、请求方法和角色ID列表"
|
||||
// @Success 200 {object} response.Response{msg=string} "设置成功"
|
||||
// @Router /api/setApiRoles [post]
|
||||
func (s *SystemApiApi) SetApiRoles(c *gin.Context) {
|
||||
var req systemReq.SetApiAuthorities
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.Path == "" || req.Method == "" {
|
||||
response.FailWithMessage("API路径和请求方法不能为空", c)
|
||||
return
|
||||
}
|
||||
if err := casbinService.SetApiAuthorities(req.Path, req.Method, req.AuthorityIds); err != nil {
|
||||
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 刷新casbin缓存使策略立即生效
|
||||
_ = casbinService.FreshCasbin()
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
sysReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ApiTokenApi struct{}
|
||||
|
||||
// CreateApiToken 签发Token
|
||||
func (s *ApiTokenApi) CreateApiToken(c *gin.Context) {
|
||||
var req struct {
|
||||
UserID uint `json:"userId"`
|
||||
AuthorityID uint `json:"authorityId"`
|
||||
Days int `json:"days"` // -1为永久, 其他为天数
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
token := system.SysApiToken{
|
||||
UserID: req.UserID,
|
||||
AuthorityID: req.AuthorityID,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
jwtStr, err := apiTokenService.CreateApiToken(token, req.Days)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("签发失败!", zap.Error(err))
|
||||
response.FailWithMessage("签发失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"token": jwtStr}, "签发成功", c)
|
||||
}
|
||||
|
||||
// GetApiTokenList 获取列表
|
||||
func (s *ApiTokenApi) GetApiTokenList(c *gin.Context) {
|
||||
var pageInfo sysReq.SysApiTokenSearch
|
||||
err := c.ShouldBindJSON(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := apiTokenService.GetApiTokenList(pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// DeleteApiToken 作废Token
|
||||
func (s *ApiTokenApi) DeleteApiToken(c *gin.Context) {
|
||||
var req system.SysApiToken
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = apiTokenService.DeleteApiToken(req.ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("作废失败!", zap.Error(err))
|
||||
response.FailWithMessage("作废失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("作废成功", c)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
@@ -25,26 +24,35 @@ type AuthorityApi struct{}
|
||||
// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "创建角色,返回包括系统角色详情"
|
||||
// @Router /authority/createAuthority [post]
|
||||
func (a *AuthorityApi) CreateAuthority(c *gin.Context) {
|
||||
var authority system.SysAuthority
|
||||
err := c.ShouldBindJSON(&authority)
|
||||
if err != nil {
|
||||
var authority, authBack system.SysAuthority
|
||||
var err error
|
||||
|
||||
if err = c.ShouldBindJSON(&authority); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
err = utils.Verify(authority, utils.AuthorityVerify)
|
||||
if err != nil {
|
||||
if err = utils.Verify(authority, utils.AuthorityVerify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if authBack, err := authorityService.CreateAuthority(authority); err != nil {
|
||||
|
||||
if *authority.ParentId == 0 && global.GVA_CONFIG.System.UseStrictAuth {
|
||||
authority.ParentId = utils.Pointer(utils.GetUserAuthorityId(c))
|
||||
}
|
||||
|
||||
if authBack, err = authorityService.CreateAuthority(authority); err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败"+err.Error(), c)
|
||||
} else {
|
||||
_ = menuService.AddMenuAuthority(systemReq.DefaultMenu(), authority.AuthorityId)
|
||||
_ = casbinService.UpdateCasbin(authority.AuthorityId, systemReq.DefaultCasbin())
|
||||
response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "创建成功", c)
|
||||
return
|
||||
}
|
||||
err = casbinService.FreshCasbin()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建成功,权限刷新失败。", zap.Error(err))
|
||||
response.FailWithMessage("创建成功,权限刷新失败。"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "创建成功", c)
|
||||
}
|
||||
|
||||
// CopyAuthority
|
||||
@@ -73,7 +81,8 @@ func (a *AuthorityApi) CopyAuthority(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
authBack, err := authorityService.CopyAuthority(copyInfo)
|
||||
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||
authBack, err := authorityService.CopyAuthority(adminAuthorityID, copyInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("拷贝失败!", zap.Error(err))
|
||||
response.FailWithMessage("拷贝失败"+err.Error(), c)
|
||||
@@ -93,22 +102,22 @@ func (a *AuthorityApi) CopyAuthority(c *gin.Context) {
|
||||
// @Router /authority/deleteAuthority [post]
|
||||
func (a *AuthorityApi) DeleteAuthority(c *gin.Context) {
|
||||
var authority system.SysAuthority
|
||||
err := c.ShouldBindJSON(&authority)
|
||||
if err != nil {
|
||||
var err error
|
||||
if err = c.ShouldBindJSON(&authority); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = utils.Verify(authority, utils.AuthorityIdVerify)
|
||||
if err != nil {
|
||||
if err = utils.Verify(authority, utils.AuthorityIdVerify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = authorityService.DeleteAuthority(&authority)
|
||||
if err != nil { // 删除角色之前需要判断是否有用户正在使用此角色
|
||||
// 删除角色之前需要判断是否有用户正在使用此角色
|
||||
if err = authorityService.DeleteAuthority(&authority); err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
_ = casbinService.FreshCasbin()
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
@@ -120,7 +129,7 @@ func (a *AuthorityApi) DeleteAuthority(c *gin.Context) {
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysAuthority true "权限id, 权限名, 父角色id"
|
||||
// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "更新角色信息,返回包括系统角色详情"
|
||||
// @Router /authority/updateAuthority [post]
|
||||
// @Router /authority/updateAuthority [put]
|
||||
func (a *AuthorityApi) UpdateAuthority(c *gin.Context) {
|
||||
var auth system.SysAuthority
|
||||
err := c.ShouldBindJSON(&auth)
|
||||
@@ -152,29 +161,14 @@ func (a *AuthorityApi) UpdateAuthority(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取角色列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /authority/getAuthorityList [post]
|
||||
func (a *AuthorityApi) GetAuthorityList(c *gin.Context) {
|
||||
var pageInfo request.PageInfo
|
||||
err := c.ShouldBindJSON(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = utils.Verify(pageInfo, utils.PageInfoVerify)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := authorityService.GetAuthorityInfoList(pageInfo)
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
list, err := authorityService.GetAuthorityInfoList(authorityID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
||||
|
||||
// SetDataAuthority
|
||||
@@ -198,7 +192,8 @@ func (a *AuthorityApi) SetDataAuthority(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = authorityService.SetDataAuthority(auth)
|
||||
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||
err = authorityService.SetDataAuthority(adminAuthorityID, auth)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置失败"+err.Error(), c)
|
||||
@@ -206,3 +201,57 @@ func (a *AuthorityApi) SetDataAuthority(c *gin.Context) {
|
||||
}
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// GetUsersByAuthority
|
||||
// @Tags Authority
|
||||
// @Summary 获取拥有指定角色的用户ID列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param authorityId query uint true "角色ID"
|
||||
// @Success 200 {object} response.Response{data=[]uint,msg=string} "获取成功"
|
||||
// @Router /authority/getUsersByAuthority [get]
|
||||
func (a *AuthorityApi) GetUsersByAuthority(c *gin.Context) {
|
||||
var req systemReq.SetRoleUsers
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
userIds, err := authorityService.GetUserIdsByAuthorityId(req.AuthorityId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if userIds == nil {
|
||||
userIds = []uint{}
|
||||
}
|
||||
response.OkWithDetailed(userIds, "获取成功", c)
|
||||
}
|
||||
|
||||
// SetRoleUsers
|
||||
// @Tags Authority
|
||||
// @Summary 全量覆盖某角色关联的用户列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.SetRoleUsers true "角色ID和用户ID列表"
|
||||
// @Success 200 {object} response.Response{msg=string} "设置成功"
|
||||
// @Router /authority/setRoleUsers [post]
|
||||
func (a *AuthorityApi) SetRoleUsers(c *gin.Context) {
|
||||
var req systemReq.SetRoleUsers
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.AuthorityId == 0 {
|
||||
response.FailWithMessage("角色ID不能为空", c)
|
||||
return
|
||||
}
|
||||
if err := authorityService.SetRoleUsers(req.AuthorityId, req.UserIds); err != nil {
|
||||
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
@@ -1,98 +1,16 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AutoCodeApi struct{}
|
||||
|
||||
// PreviewTemp
|
||||
// @Tags AutoCode
|
||||
// @Summary 预览创建后的代码
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.AutoCodeStruct true "预览创建代码"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "预览创建后的代码"
|
||||
// @Router /autoCode/preview [post]
|
||||
func (autoApi *AutoCodeApi) PreviewTemp(c *gin.Context) {
|
||||
var a system.AutoCodeStruct
|
||||
_ = c.ShouldBindJSON(&a)
|
||||
if err := utils.Verify(a, utils.AutoCodeVerify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
a.Pretreatment() // 处理go关键字
|
||||
a.PackageT = utils.FirstUpper(a.Package)
|
||||
autoCode, err := autoCodeService.PreviewTemp(a)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("预览失败!", zap.Error(err))
|
||||
response.FailWithMessage("预览失败", c)
|
||||
} else {
|
||||
response.OkWithDetailed(gin.H{"autoCode": autoCode}, "预览成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTemp
|
||||
// @Tags AutoCode
|
||||
// @Summary 自动代码模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.AutoCodeStruct true "创建自动代码"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /autoCode/createTemp [post]
|
||||
func (autoApi *AutoCodeApi) CreateTemp(c *gin.Context) {
|
||||
var a system.AutoCodeStruct
|
||||
_ = c.ShouldBindJSON(&a)
|
||||
if err := utils.Verify(a, utils.AutoCodeVerify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
a.Pretreatment()
|
||||
var apiIds []uint
|
||||
if a.AutoCreateApiToSql {
|
||||
if ids, err := autoCodeService.AutoCreateApi(&a); err != nil {
|
||||
global.GVA_LOG.Error("自动化创建失败!请自行清空垃圾数据!", zap.Error(err))
|
||||
c.Writer.Header().Add("success", "false")
|
||||
c.Writer.Header().Add("msg", url.QueryEscape("自动化创建失败!请自行清空垃圾数据!"))
|
||||
return
|
||||
} else {
|
||||
apiIds = ids
|
||||
}
|
||||
}
|
||||
a.PackageT = utils.FirstUpper(a.Package)
|
||||
err := autoCodeService.CreateTemp(a, apiIds...)
|
||||
if err != nil {
|
||||
if errors.Is(err, system.ErrAutoMove) {
|
||||
c.Writer.Header().Add("success", "true")
|
||||
c.Writer.Header().Add("msg", url.QueryEscape(err.Error()))
|
||||
} else {
|
||||
c.Writer.Header().Add("success", "false")
|
||||
c.Writer.Header().Add("msg", url.QueryEscape(err.Error()))
|
||||
_ = os.Remove("./ginvueadmin.zip")
|
||||
}
|
||||
} else {
|
||||
c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", "ginvueadmin.zip")) // fmt.Sprintf("attachment; filename=%s", filename)对下载的文件重命名
|
||||
c.Writer.Header().Add("Content-Type", "application/json")
|
||||
c.Writer.Header().Add("success", "true")
|
||||
c.File("./ginvueadmin.zip")
|
||||
_ = os.Remove("./ginvueadmin.zip")
|
||||
}
|
||||
}
|
||||
|
||||
// GetDB
|
||||
// @Tags AutoCode
|
||||
// @Summary 获取当前所有数据库
|
||||
@@ -100,7 +18,7 @@ func (autoApi *AutoCodeApi) CreateTemp(c *gin.Context) {
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前所有数据库"
|
||||
// @Router /autoCode/getDatabase [get]
|
||||
// @Router /autoCode/getDB [get]
|
||||
func (autoApi *AutoCodeApi) GetDB(c *gin.Context) {
|
||||
businessDB := c.Query("businessDB")
|
||||
dbs, err := autoCodeService.Database(businessDB).GetDB(businessDB)
|
||||
@@ -130,8 +48,19 @@ func (autoApi *AutoCodeApi) GetDB(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前数据库所有表"
|
||||
// @Router /autoCode/getTables [get]
|
||||
func (autoApi *AutoCodeApi) GetTables(c *gin.Context) {
|
||||
dbName := c.DefaultQuery("dbName", global.GVA_CONFIG.Mysql.Dbname)
|
||||
dbName := c.Query("dbName")
|
||||
businessDB := c.Query("businessDB")
|
||||
if dbName == "" {
|
||||
dbName = *global.GVA_ACTIVE_DBNAME
|
||||
if businessDB != "" {
|
||||
for _, db := range global.GVA_CONFIG.DBList {
|
||||
if db.AliasName == businessDB {
|
||||
dbName = db.Dbname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tables, err := autoCodeService.Database(businessDB).GetTables(businessDB, dbName)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询table失败!", zap.Error(err))
|
||||
@@ -151,7 +80,17 @@ func (autoApi *AutoCodeApi) GetTables(c *gin.Context) {
|
||||
// @Router /autoCode/getColumn [get]
|
||||
func (autoApi *AutoCodeApi) GetColumn(c *gin.Context) {
|
||||
businessDB := c.Query("businessDB")
|
||||
dbName := c.DefaultQuery("dbName", global.GVA_CONFIG.Mysql.Dbname)
|
||||
dbName := c.Query("dbName")
|
||||
if dbName == "" {
|
||||
dbName = *global.GVA_ACTIVE_DBNAME
|
||||
if businessDB != "" {
|
||||
for _, db := range global.GVA_CONFIG.DBList {
|
||||
if db.AliasName == businessDB {
|
||||
dbName = db.Dbname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tableName := c.Query("tableName")
|
||||
columns, err := autoCodeService.Database(businessDB).GetColumn(businessDB, tableName, dbName)
|
||||
if err != nil {
|
||||
@@ -162,154 +101,17 @@ func (autoApi *AutoCodeApi) GetColumn(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePackage
|
||||
// @Tags AutoCode
|
||||
// @Summary 创建package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysAutoCode true "创建package"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||
// @Router /autoCode/createPackage [post]
|
||||
func (autoApi *AutoCodeApi) CreatePackage(c *gin.Context) {
|
||||
var a system.SysAutoCode
|
||||
_ = c.ShouldBindJSON(&a)
|
||||
if err := utils.Verify(a, utils.AutoPackageVerify); err != nil {
|
||||
func (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) {
|
||||
var llm common.JSONMap
|
||||
if err := c.ShouldBindJSON(&llm); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err := autoCodeService.CreateAutoCode(&a)
|
||||
data, err := autoCodeService.LLMAuto(c.Request.Context(), llm)
|
||||
if err != nil {
|
||||
|
||||
global.GVA_LOG.Error("创建成功!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败", c)
|
||||
} else {
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPackage
|
||||
// @Tags AutoCode
|
||||
// @Summary 获取package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||
// @Router /autoCode/getPackage [post]
|
||||
func (autoApi *AutoCodeApi) GetPackage(c *gin.Context) {
|
||||
pkgs, err := autoCodeService.GetPackage()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
} else {
|
||||
response.OkWithDetailed(gin.H{"pkgs": pkgs}, "获取成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// DelPackage
|
||||
// @Tags AutoCode
|
||||
// @Summary 删除package
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysAutoCode true "创建package"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "删除package成功"
|
||||
// @Router /autoCode/delPackage [post]
|
||||
func (autoApi *AutoCodeApi) DelPackage(c *gin.Context) {
|
||||
var a system.SysAutoCode
|
||||
_ = c.ShouldBindJSON(&a)
|
||||
err := autoCodeService.DelPackage(a)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
} else {
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// AutoPlug
|
||||
// @Tags AutoCode
|
||||
// @Summary 创建插件模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysAutoCode true "创建插件模板"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建插件模板成功"
|
||||
// @Router /autoCode/createPlug [post]
|
||||
func (autoApi *AutoCodeApi) AutoPlug(c *gin.Context) {
|
||||
var a system.AutoPlugReq
|
||||
err := c.ShouldBindJSON(&a)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
global.GVA_LOG.Error("大模型生成失败!", zap.Error(err))
|
||||
response.FailWithMessage("大模型生成失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
a.Snake = strings.ToLower(a.PlugName)
|
||||
a.NeedModel = a.HasRequest || a.HasResponse
|
||||
err = autoCodeService.CreatePlug(a)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("预览失败!", zap.Error(err))
|
||||
response.FailWithMessage("预览失败", c)
|
||||
return
|
||||
}
|
||||
response.Ok(c)
|
||||
}
|
||||
|
||||
// InstallPlugin
|
||||
// @Tags AutoCode
|
||||
// @Summary 安装插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept multipart/form-data
|
||||
// @Produce application/json
|
||||
// @Param plug formData file true "this is a test file"
|
||||
// @Success 200 {object} response.Response{data=[]interface{},msg=string} "安装插件成功"
|
||||
// @Router /autoCode/installPlugin [post]
|
||||
func (autoApi *AutoCodeApi) InstallPlugin(c *gin.Context) {
|
||||
header, err := c.FormFile("plug")
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
web, server, err := autoCodeService.InstallPlugin(header)
|
||||
webStr := "web插件安装成功"
|
||||
serverStr := "server插件安装成功"
|
||||
if web == -1 {
|
||||
webStr = "web端插件未成功安装,请按照文档自行解压安装,如果为纯后端插件请忽略此条提示"
|
||||
}
|
||||
if server == -1 {
|
||||
serverStr = "server端插件未成功安装,请按照文档自行解压安装,如果为纯前端插件请忽略此条提示"
|
||||
}
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData([]interface{}{
|
||||
gin.H{
|
||||
"code": web,
|
||||
"msg": webStr,
|
||||
},
|
||||
gin.H{
|
||||
"code": server,
|
||||
"msg": serverStr,
|
||||
}}, c)
|
||||
}
|
||||
|
||||
// PubPlug
|
||||
// @Tags AutoCode
|
||||
// @Summary 打包插件
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysAutoCode true "打包插件"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||
// @Router /autoCode/pubPlug [get]
|
||||
func (autoApi *AutoCodeApi) PubPlug(c *gin.Context) {
|
||||
plugName := c.Query("plugName")
|
||||
snake := strings.ToLower(plugName)
|
||||
zipPath, err := autoCodeService.PubPlug(snake)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("打包失败!", zap.Error(err))
|
||||
response.FailWithMessage("打包失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage(fmt.Sprintf("打包成功,文件路径为:%s", zipPath), c)
|
||||
response.OkWithData(data, c)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (b *BaseApi) Captcha(c *gin.Context) {
|
||||
driver := base64Captcha.NewDriverDigit(global.GVA_CONFIG.Captcha.ImgHeight, global.GVA_CONFIG.Captcha.ImgWidth, global.GVA_CONFIG.Captcha.KeyLong, 0.7, 80)
|
||||
// cp := base64Captcha.NewCaptcha(driver, store.UseWithCtx(c)) // v8下使用redis
|
||||
cp := base64Captcha.NewCaptcha(driver, store)
|
||||
id, b64s, err := cp.Generate()
|
||||
id, b64s, _, err := cp.Generate()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("验证码获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("验证码获取失败", c)
|
||||
|
||||
@@ -33,7 +33,8 @@ func (cas *CasbinApi) UpdateCasbin(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = casbinService.UpdateCasbin(cmr.AuthorityId, cmr.CasbinInfos)
|
||||
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||
err = casbinService.UpdateCasbin(adminAuthorityID, cmr.AuthorityId, cmr.CasbinInfos)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||
response.FailWithMessage("更新失败", c)
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
sysModel "github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ChatGptApi struct{}
|
||||
|
||||
func (chat *ChatGptApi) CreateSK(c *gin.Context) {
|
||||
var option sysModel.SysChatGptOption
|
||||
c.ShouldBindJSON(&option)
|
||||
err := chatGptService.CreateSK(option)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
func (chat *ChatGptApi) GetSK(c *gin.Context) {
|
||||
var option sysModel.SysChatGptOption
|
||||
c.ShouldBindJSON(&option)
|
||||
_, err := chatGptService.GetSK()
|
||||
if err != nil {
|
||||
response.OkWithDetailed(gin.H{
|
||||
"ok": false,
|
||||
}, "无sk或获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{
|
||||
"ok": true,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (chat *ChatGptApi) DeleteSK(c *gin.Context) {
|
||||
err := chatGptService.DeleteSK()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
func (chat *ChatGptApi) GetTable(c *gin.Context) {
|
||||
var req request.ChatGptRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
sql, results, err := chatGptService.GetTable(req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithDetailed(gin.H{
|
||||
"sql": sql,
|
||||
"results": results,
|
||||
}, "生成失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{
|
||||
"sql": sql,
|
||||
"results": results,
|
||||
}, "ChatGpt生成完成", c)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -118,31 +117,75 @@ func (s *DictionaryApi) FindSysDictionary(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query request.SysDictionarySearch true "页码, 每页大小, 搜索条件"
|
||||
// @Param data query request.SysDictionarySearch true "字典 name 或者 type"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /sysDictionary/getSysDictionaryList [get]
|
||||
func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) {
|
||||
var pageInfo request.SysDictionarySearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
var dictionary request.SysDictionarySearch
|
||||
err := c.ShouldBindQuery(&dictionary)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = utils.Verify(pageInfo.PageInfo, utils.PageInfoVerify)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := dictionaryService.GetSysDictionaryInfoList(pageInfo)
|
||||
list, err := dictionaryService.GetSysDictionaryInfoList(c, dictionary)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
||||
|
||||
// ExportSysDictionary
|
||||
// @Tags SysDictionary
|
||||
// @Summary 导出字典JSON(包含字典详情)
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query system.SysDictionary true "字典ID"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "导出字典JSON"
|
||||
// @Router /sysDictionary/exportSysDictionary [get]
|
||||
func (s *DictionaryApi) ExportSysDictionary(c *gin.Context) {
|
||||
var dictionary system.SysDictionary
|
||||
err := c.ShouldBindQuery(&dictionary)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if dictionary.ID == 0 {
|
||||
response.FailWithMessage("字典ID不能为空", c)
|
||||
return
|
||||
}
|
||||
exportData, err := dictionaryService.ExportSysDictionary(dictionary.ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("导出失败!", zap.Error(err))
|
||||
response.FailWithMessage("导出失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(exportData, "导出成功", c)
|
||||
}
|
||||
|
||||
// ImportSysDictionary
|
||||
// @Tags SysDictionary
|
||||
// @Summary 导入字典JSON(包含字典详情)
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.ImportSysDictionaryRequest true "字典JSON数据"
|
||||
// @Success 200 {object} response.Response{msg=string} "导入字典"
|
||||
// @Router /sysDictionary/importSysDictionary [post]
|
||||
func (s *DictionaryApi) ImportSysDictionary(c *gin.Context) {
|
||||
var req request.ImportSysDictionaryRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = dictionaryService.ImportSysDictionary(req.Json)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("导入失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("导入成功", c)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
@@ -146,3 +148,120 @@ func (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) {
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetDictionaryTreeList
|
||||
// @Tags SysDictionaryDetail
|
||||
// @Summary 获取字典详情树形结构
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param sysDictionaryID query int true "字典ID"
|
||||
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
|
||||
// @Router /sysDictionaryDetail/getDictionaryTreeList [get]
|
||||
func (s *DictionaryDetailApi) GetDictionaryTreeList(c *gin.Context) {
|
||||
sysDictionaryID := c.Query("sysDictionaryID")
|
||||
if sysDictionaryID == "" {
|
||||
response.FailWithMessage("字典ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
var id uint
|
||||
if idUint64, err := strconv.ParseUint(sysDictionaryID, 10, 32); err != nil {
|
||||
response.FailWithMessage("字典ID格式错误", c)
|
||||
return
|
||||
} else {
|
||||
id = uint(idUint64)
|
||||
}
|
||||
|
||||
list, err := dictionaryDetailService.GetDictionaryTreeList(id)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetDictionaryTreeListByType
|
||||
// @Tags SysDictionaryDetail
|
||||
// @Summary 根据字典类型获取字典详情树形结构
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param type query string true "字典类型"
|
||||
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
|
||||
// @Router /sysDictionaryDetail/getDictionaryTreeListByType [get]
|
||||
func (s *DictionaryDetailApi) GetDictionaryTreeListByType(c *gin.Context) {
|
||||
dictType := c.Query("type")
|
||||
if dictType == "" {
|
||||
response.FailWithMessage("字典类型不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := dictionaryDetailService.GetDictionaryTreeListByType(dictType)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetDictionaryDetailsByParent
|
||||
// @Tags SysDictionaryDetail
|
||||
// @Summary 根据父级ID获取字典详情
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query request.GetDictionaryDetailsByParentRequest true "查询参数"
|
||||
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情列表"
|
||||
// @Router /sysDictionaryDetail/getDictionaryDetailsByParent [get]
|
||||
func (s *DictionaryDetailApi) GetDictionaryDetailsByParent(c *gin.Context) {
|
||||
var req request.GetDictionaryDetailsByParentRequest
|
||||
err := c.ShouldBindQuery(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := dictionaryDetailService.GetDictionaryDetailsByParent(req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetDictionaryPath
|
||||
// @Tags SysDictionaryDetail
|
||||
// @Summary 获取字典详情的完整路径
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id query uint true "字典详情ID"
|
||||
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情路径"
|
||||
// @Router /sysDictionaryDetail/getDictionaryPath [get]
|
||||
func (s *DictionaryDetailApi) GetDictionaryPath(c *gin.Context) {
|
||||
idStr := c.Query("id")
|
||||
if idStr == "" {
|
||||
response.FailWithMessage("字典详情ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
var id uint
|
||||
if idUint64, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
response.FailWithMessage("字典详情ID格式错误", c)
|
||||
return
|
||||
} else {
|
||||
id = uint(idUint64)
|
||||
}
|
||||
|
||||
path, err := dictionaryDetailService.GetDictionaryPath(id)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"path": path}, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SysErrorApi struct{}
|
||||
|
||||
// CreateSysError 创建错误日志
|
||||
// @Tags SysError
|
||||
// @Summary 创建错误日志
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysError true "创建错误日志"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /sysError/createSysError [post]
|
||||
func (sysErrorApi *SysErrorApi) CreateSysError(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var sysError system.SysError
|
||||
err := c.ShouldBindJSON(&sysError)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = sysErrorService.CreateSysError(ctx, &sysError)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysError 删除错误日志
|
||||
// @Tags SysError
|
||||
// @Summary 删除错误日志
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysError true "删除错误日志"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /sysError/deleteSysError [delete]
|
||||
func (sysErrorApi *SysErrorApi) DeleteSysError(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
err := sysErrorService.DeleteSysError(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysErrorByIds 批量删除错误日志
|
||||
// @Tags SysError
|
||||
// @Summary 批量删除错误日志
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||
// @Router /sysError/deleteSysErrorByIds [delete]
|
||||
func (sysErrorApi *SysErrorApi) DeleteSysErrorByIds(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
IDs := c.QueryArray("IDs[]")
|
||||
err := sysErrorService.DeleteSysErrorByIds(ctx, IDs)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateSysError 更新错误日志
|
||||
// @Tags SysError
|
||||
// @Summary 更新错误日志
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysError true "更新错误日志"
|
||||
// @Success 200 {object} response.Response{msg=string} "更新成功"
|
||||
// @Router /sysError/updateSysError [put]
|
||||
func (sysErrorApi *SysErrorApi) UpdateSysError(c *gin.Context) {
|
||||
// 从ctx获取标准context进行业务行为
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var sysError system.SysError
|
||||
err := c.ShouldBindJSON(&sysError)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = sysErrorService.UpdateSysError(ctx, sysError)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// FindSysError 用id查询错误日志
|
||||
// @Tags SysError
|
||||
// @Summary 用id查询错误日志
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param ID query uint true "用id查询错误日志"
|
||||
// @Success 200 {object} response.Response{data=system.SysError,msg=string} "查询成功"
|
||||
// @Router /sysError/findSysError [get]
|
||||
func (sysErrorApi *SysErrorApi) FindSysError(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
resysError, err := sysErrorService.GetSysError(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(resysError, c)
|
||||
}
|
||||
|
||||
// GetSysErrorList 分页获取错误日志列表
|
||||
// @Tags SysError
|
||||
// @Summary 分页获取错误日志列表
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query systemReq.SysErrorSearch true "分页获取错误日志列表"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /sysError/getSysErrorList [get]
|
||||
func (sysErrorApi *SysErrorApi) GetSysErrorList(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var pageInfo systemReq.SysErrorSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := sysErrorService.GetSysErrorInfoList(ctx, pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysErrorSolution 触发错误日志的异步处理
|
||||
// @Tags SysError
|
||||
// @Summary 根据ID触发处理:标记为处理中,1分钟后自动改为处理完成
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id query string true "错误日志ID"
|
||||
// @Success 200 {object} response.Response{msg=string} "处理已提交"
|
||||
// @Router /sysError/getSysErrorSolution [get]
|
||||
func (sysErrorApi *SysErrorApi) GetSysErrorSolution(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 兼容 id 与 ID 两种参数
|
||||
ID := c.Query("id")
|
||||
if ID == "" {
|
||||
response.FailWithMessage("缺少参数: id", c)
|
||||
return
|
||||
}
|
||||
|
||||
err := sysErrorService.GetSysErrorSolution(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("处理触发失败!", zap.Error(err))
|
||||
response.FailWithMessage("处理触发失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("已提交至AI处理", c)
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 用于token一次性存储
|
||||
var (
|
||||
exportTokenCache = make(map[string]interface{})
|
||||
exportTokenExpiration = make(map[string]time.Time)
|
||||
tokenMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// 五分钟检测窗口过期
|
||||
func cleanupExpiredTokens() {
|
||||
for {
|
||||
time.Sleep(5 * time.Minute)
|
||||
tokenMutex.Lock()
|
||||
now := time.Now()
|
||||
for token, expiry := range exportTokenExpiration {
|
||||
if now.After(expiry) {
|
||||
delete(exportTokenCache, token)
|
||||
delete(exportTokenExpiration, token)
|
||||
}
|
||||
}
|
||||
tokenMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
go cleanupExpiredTokens()
|
||||
}
|
||||
|
||||
type SysExportTemplateApi struct {
|
||||
}
|
||||
|
||||
var sysExportTemplateService = service.ServiceGroupApp.SystemServiceGroup.SysExportTemplateService
|
||||
|
||||
// PreviewSQL 预览最终生成的SQL
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 预览最终生成的SQL(不执行查询,仅返回SQL字符串)
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param templateID query string true "导出模板ID"
|
||||
// @Param params query string false "查询参数编码字符串,参考 ExportExcel 组件"
|
||||
// @Success 200 {object} response.Response{data=map[string]string} "获取成功"
|
||||
// @Router /sysExportTemplate/previewSQL [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) PreviewSQL(c *gin.Context) {
|
||||
templateID := c.Query("templateID")
|
||||
if templateID == "" {
|
||||
response.FailWithMessage("模板ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 直接复用导出接口的参数组织方式:使用 URL Query,其中 params 为内部编码的查询字符串
|
||||
queryParams := c.Request.URL.Query()
|
||||
|
||||
if sqlPreview, err := sysExportTemplateService.PreviewSQL(templateID, queryParams); err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
} else {
|
||||
response.OkWithData(gin.H{"sql": sqlPreview}, c)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSysExportTemplate 创建导出模板
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 创建导出模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysExportTemplate true "创建导出模板"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||
// @Router /sysExportTemplate/createSysExportTemplate [post]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) CreateSysExportTemplate(c *gin.Context) {
|
||||
var sysExportTemplate system.SysExportTemplate
|
||||
err := c.ShouldBindJSON(&sysExportTemplate)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
verify := utils.Rules{
|
||||
"Name": {utils.NotEmpty()},
|
||||
}
|
||||
if err := utils.Verify(sysExportTemplate, verify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := sysExportTemplateService.CreateSysExportTemplate(&sysExportTemplate); err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败", c)
|
||||
} else {
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSysExportTemplate 删除导出模板
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 删除导出模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysExportTemplate true "删除导出模板"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
|
||||
// @Router /sysExportTemplate/deleteSysExportTemplate [delete]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplate(c *gin.Context) {
|
||||
var sysExportTemplate system.SysExportTemplate
|
||||
err := c.ShouldBindJSON(&sysExportTemplate)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := sysExportTemplateService.DeleteSysExportTemplate(sysExportTemplate); err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
} else {
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSysExportTemplateByIds 批量删除导出模板
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 批量删除导出模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.IdsReq true "批量删除导出模板"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"批量删除成功"}"
|
||||
// @Router /sysExportTemplate/deleteSysExportTemplateByIds [delete]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplateByIds(c *gin.Context) {
|
||||
var IDS request.IdsReq
|
||||
err := c.ShouldBindJSON(&IDS)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := sysExportTemplateService.DeleteSysExportTemplateByIds(IDS); err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败", c)
|
||||
} else {
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateSysExportTemplate 更新导出模板
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 更新导出模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysExportTemplate true "更新导出模板"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
|
||||
// @Router /sysExportTemplate/updateSysExportTemplate [put]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) UpdateSysExportTemplate(c *gin.Context) {
|
||||
var sysExportTemplate system.SysExportTemplate
|
||||
err := c.ShouldBindJSON(&sysExportTemplate)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
verify := utils.Rules{
|
||||
"Name": {utils.NotEmpty()},
|
||||
}
|
||||
if err := utils.Verify(sysExportTemplate, verify); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := sysExportTemplateService.UpdateSysExportTemplate(sysExportTemplate); err != nil {
|
||||
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||
response.FailWithMessage("更新失败", c)
|
||||
} else {
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// FindSysExportTemplate 用id查询导出模板
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 用id查询导出模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query system.SysExportTemplate true "用id查询导出模板"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
|
||||
// @Router /sysExportTemplate/findSysExportTemplate [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) FindSysExportTemplate(c *gin.Context) {
|
||||
var sysExportTemplate system.SysExportTemplate
|
||||
err := c.ShouldBindQuery(&sysExportTemplate)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if resysExportTemplate, err := sysExportTemplateService.GetSysExportTemplate(sysExportTemplate.ID); err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败", c)
|
||||
} else {
|
||||
response.OkWithData(gin.H{"resysExportTemplate": resysExportTemplate}, c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSysExportTemplateList 分页获取导出模板列表
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 分页获取导出模板列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query systemReq.SysExportTemplateSearch true "分页获取导出模板列表"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
|
||||
// @Router /sysExportTemplate/getSysExportTemplateList [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gin.Context) {
|
||||
var pageInfo systemReq.SysExportTemplateSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if list, total, err := sysExportTemplateService.GetSysExportTemplateInfoList(pageInfo); err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
} else {
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ExportExcel 导出表格token
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 导出表格
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/exportExcel [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
|
||||
templateID := c.Query("templateID")
|
||||
if templateID == "" {
|
||||
response.FailWithMessage("模板ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
queryParams := c.Request.URL.Query()
|
||||
|
||||
//创造一次性token
|
||||
token := utils.RandomString(32) // 随机32位
|
||||
|
||||
// 记录本次请求参数
|
||||
exportParams := map[string]interface{}{
|
||||
"templateID": templateID,
|
||||
"queryParams": queryParams,
|
||||
}
|
||||
|
||||
// 参数保留记录完成鉴权
|
||||
tokenMutex.Lock()
|
||||
exportTokenCache[token] = exportParams
|
||||
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 生成一次性链接
|
||||
exportUrl := fmt.Sprintf("/sysExportTemplate/exportExcelByToken?token=%s", token)
|
||||
response.OkWithData(exportUrl, c)
|
||||
}
|
||||
|
||||
// ExportExcelByToken 导出表格
|
||||
// @Tags ExportExcelByToken
|
||||
// @Summary 导出表格
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/exportExcelByToken [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcelByToken(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
response.FailWithMessage("导出token不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取token并且从缓存中剔除
|
||||
tokenMutex.RLock()
|
||||
exportParamsRaw, exists := exportTokenCache[token]
|
||||
expiry, _ := exportTokenExpiration[token]
|
||||
tokenMutex.RUnlock()
|
||||
|
||||
if !exists || time.Now().After(expiry) {
|
||||
global.GVA_LOG.Error("导出token无效或已过期!")
|
||||
response.FailWithMessage("导出token无效或已过期", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 从token获取参数
|
||||
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||||
if !ok {
|
||||
global.GVA_LOG.Error("解析导出参数失败!")
|
||||
response.FailWithMessage("解析导出参数失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取导出参数
|
||||
templateID := exportParams["templateID"].(string)
|
||||
queryParams := exportParams["queryParams"].(url.Values)
|
||||
|
||||
// 清理一次性token
|
||||
tokenMutex.Lock()
|
||||
delete(exportTokenCache, token)
|
||||
delete(exportTokenExpiration, token)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 导出
|
||||
if file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
} else {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx"))
|
||||
c.Header("success", "true")
|
||||
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// ExportTemplate 导出表格模板
|
||||
// @Tags SysExportTemplate
|
||||
// @Summary 导出表格模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/exportTemplate [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplate(c *gin.Context) {
|
||||
templateID := c.Query("templateID")
|
||||
if templateID == "" {
|
||||
response.FailWithMessage("模板ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创造一次性token
|
||||
token := utils.RandomString(32) // 随机32位
|
||||
|
||||
// 记录本次请求参数
|
||||
exportParams := map[string]interface{}{
|
||||
"templateID": templateID,
|
||||
"isTemplate": true,
|
||||
}
|
||||
|
||||
// 参数保留记录完成鉴权
|
||||
tokenMutex.Lock()
|
||||
exportTokenCache[token] = exportParams
|
||||
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 生成一次性链接
|
||||
exportUrl := fmt.Sprintf("/sysExportTemplate/exportTemplateByToken?token=%s", token)
|
||||
response.OkWithData(exportUrl, c)
|
||||
}
|
||||
|
||||
// ExportTemplateByToken 通过token导出表格模板
|
||||
// @Tags ExportTemplateByToken
|
||||
// @Summary 通过token导出表格模板
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/exportTemplateByToken [get]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplateByToken(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
response.FailWithMessage("导出token不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取token并且从缓存中剔除
|
||||
tokenMutex.RLock()
|
||||
exportParamsRaw, exists := exportTokenCache[token]
|
||||
expiry, _ := exportTokenExpiration[token]
|
||||
tokenMutex.RUnlock()
|
||||
|
||||
if !exists || time.Now().After(expiry) {
|
||||
global.GVA_LOG.Error("导出token无效或已过期!")
|
||||
response.FailWithMessage("导出token无效或已过期", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 从token获取参数
|
||||
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||||
if !ok {
|
||||
global.GVA_LOG.Error("解析导出参数失败!")
|
||||
response.FailWithMessage("解析导出参数失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否为模板导出
|
||||
isTemplate, _ := exportParams["isTemplate"].(bool)
|
||||
if !isTemplate {
|
||||
global.GVA_LOG.Error("token类型错误!")
|
||||
response.FailWithMessage("token类型错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取导出参数
|
||||
templateID := exportParams["templateID"].(string)
|
||||
|
||||
// 清理一次性token
|
||||
tokenMutex.Lock()
|
||||
delete(exportTokenCache, token)
|
||||
delete(exportTokenExpiration, token)
|
||||
tokenMutex.Unlock()
|
||||
|
||||
// 导出模板
|
||||
if file, name, err := sysExportTemplateService.ExportTemplate(templateID); err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
} else {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx"))
|
||||
c.Header("success", "true")
|
||||
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// ImportExcel 导入表格
|
||||
// @Tags SysImportTemplate
|
||||
// @Summary 导入表格
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Router /sysExportTemplate/importExcel [post]
|
||||
func (sysExportTemplateApi *SysExportTemplateApi) ImportExcel(c *gin.Context) {
|
||||
templateID := c.Query("templateID")
|
||||
if templateID == "" {
|
||||
response.FailWithMessage("模板ID不能为空", c)
|
||||
return
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("文件获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("文件获取失败", c)
|
||||
return
|
||||
}
|
||||
if err := sysExportTemplateService.ImportExcel(templateID, file); err != nil {
|
||||
global.GVA_LOG.Error(err.Error(), zap.Error(err))
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
} else {
|
||||
response.OkWithMessage("导入成功", c)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -19,7 +20,7 @@ type JwtApi struct{}
|
||||
// @Success 200 {object} response.Response{msg=string} "jwt加入黑名单"
|
||||
// @Router /jwt/jsonInBlacklist [post]
|
||||
func (j *JwtApi) JsonInBlacklist(c *gin.Context) {
|
||||
token := c.Request.Header.Get("x-token")
|
||||
token := utils.GetToken(c)
|
||||
jwt := system.JwtBlacklist{Jwt: token}
|
||||
err := jwtService.JsonInBlacklist(jwt)
|
||||
if err != nil {
|
||||
@@ -27,5 +28,6 @@ func (j *JwtApi) JsonInBlacklist(c *gin.Context) {
|
||||
response.FailWithMessage("jwt作废失败", c)
|
||||
return
|
||||
}
|
||||
utils.ClearToken(c)
|
||||
response.OkWithMessage("jwt作废成功", c)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LoginLogApi struct{}
|
||||
|
||||
func (s *LoginLogApi) DeleteLoginLog(c *gin.Context) {
|
||||
var loginLog system.SysLoginLog
|
||||
err := c.ShouldBindJSON(&loginLog)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = loginLogService.DeleteLoginLog(loginLog)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
func (s *LoginLogApi) DeleteLoginLogByIds(c *gin.Context) {
|
||||
var SDS request.IdsReq
|
||||
err := c.ShouldBindJSON(&SDS)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = loginLogService.DeleteLoginLogByIds(SDS)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
|
||||
func (s *LoginLogApi) FindLoginLog(c *gin.Context) {
|
||||
var loginLog system.SysLoginLog
|
||||
err := c.ShouldBindQuery(&loginLog)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
reLoginLog, err := loginLogService.GetLoginLog(loginLog.ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(reLoginLog, "查询成功", c)
|
||||
}
|
||||
|
||||
func (s *LoginLogApi) GetLoginLogList(c *gin.Context) {
|
||||
var pageInfo systemReq.SysLoginLogSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := loginLogService.GetLoginLogInfoList(pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
@@ -45,7 +45,8 @@ func (a *AuthorityMenuApi) GetMenu(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{data=systemRes.SysBaseMenusResponse,msg=string} "获取用户动态路由,返回包括系统菜单列表"
|
||||
// @Router /menu/getBaseMenuTree [post]
|
||||
func (a *AuthorityMenuApi) GetBaseMenuTree(c *gin.Context) {
|
||||
menus, err := menuService.GetBaseMenuTree()
|
||||
authority := utils.GetUserAuthorityId(c)
|
||||
menus, err := menuService.GetBaseMenuTree(authority)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
@@ -74,7 +75,8 @@ func (a *AuthorityMenuApi) AddMenuAuthority(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := menuService.AddMenuAuthority(authorityMenu.Menus, authorityMenu.AuthorityId); err != nil {
|
||||
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||
if err := menuService.AddMenuAuthority(authorityMenu.Menus, adminAuthorityID, authorityMenu.AuthorityId); err != nil {
|
||||
global.GVA_LOG.Error("添加失败!", zap.Error(err))
|
||||
response.FailWithMessage("添加失败", c)
|
||||
} else {
|
||||
@@ -141,7 +143,7 @@ func (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) {
|
||||
err = menuService.AddBaseMenu(menu)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("添加失败!", zap.Error(err))
|
||||
response.FailWithMessage("添加失败", c)
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("添加成功", c)
|
||||
@@ -171,7 +173,7 @@ func (a *AuthorityMenuApi) DeleteBaseMenu(c *gin.Context) {
|
||||
err = baseMenuService.DeleteBaseMenu(menu.ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
@@ -242,6 +244,76 @@ func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) {
|
||||
response.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetMenuRoles
|
||||
// @Tags AuthorityMenu
|
||||
// @Summary 获取拥有指定菜单的角色ID列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param menuId query uint true "菜单ID"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取成功"
|
||||
// @Router /menu/getMenuRoles [get]
|
||||
func (a *AuthorityMenuApi) GetMenuRoles(c *gin.Context) {
|
||||
var req systemReq.SetMenuAuthorities
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.MenuId == 0 {
|
||||
response.FailWithMessage("菜单ID不能为空", c)
|
||||
return
|
||||
}
|
||||
authorityIds, err := menuService.GetAuthoritiesByMenuId(req.MenuId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if authorityIds == nil {
|
||||
authorityIds = []uint{}
|
||||
}
|
||||
defaultRouterAuthorityIds, err := menuService.GetDefaultRouterAuthorityIds(req.MenuId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取首页角色失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if defaultRouterAuthorityIds == nil {
|
||||
defaultRouterAuthorityIds = []uint{}
|
||||
}
|
||||
response.OkWithDetailed(gin.H{
|
||||
"authorityIds": authorityIds,
|
||||
"defaultRouterAuthorityIds": defaultRouterAuthorityIds,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SetMenuRoles
|
||||
// @Tags AuthorityMenu
|
||||
// @Summary 全量覆盖某菜单关联的角色列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.SetMenuAuthorities true "菜单ID和角色ID列表"
|
||||
// @Success 200 {object} response.Response{msg=string} "设置成功"
|
||||
// @Router /menu/setMenuRoles [post]
|
||||
func (a *AuthorityMenuApi) SetMenuRoles(c *gin.Context) {
|
||||
var req systemReq.SetMenuAuthorities
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.MenuId == 0 {
|
||||
response.FailWithMessage("菜单ID不能为空", c)
|
||||
return
|
||||
}
|
||||
if err := menuService.SetMenuAuthorities(req.MenuId, req.AuthorityIds); err != nil {
|
||||
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// GetMenuList
|
||||
// @Tags Menu
|
||||
// @Summary 分页获取基础menu列表
|
||||
@@ -252,27 +324,12 @@ func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取基础menu列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /menu/getMenuList [post]
|
||||
func (a *AuthorityMenuApi) GetMenuList(c *gin.Context) {
|
||||
var pageInfo request.PageInfo
|
||||
err := c.ShouldBindJSON(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = utils.Verify(pageInfo, utils.PageInfoVerify)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
menuList, total, err := menuService.GetInfoList()
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
menuList, err := menuService.GetInfoList(authorityID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: menuList,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
response.OkWithDetailed(menuList, "获取成功", c)
|
||||
}
|
||||
|
||||
@@ -13,31 +13,6 @@ import (
|
||||
|
||||
type OperationRecordApi struct{}
|
||||
|
||||
// CreateSysOperationRecord
|
||||
// @Tags SysOperationRecord
|
||||
// @Summary 创建SysOperationRecord
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysOperationRecord true "创建SysOperationRecord"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建SysOperationRecord"
|
||||
// @Router /sysOperationRecord/createSysOperationRecord [post]
|
||||
func (s *OperationRecordApi) CreateSysOperationRecord(c *gin.Context) {
|
||||
var sysOperationRecord system.SysOperationRecord
|
||||
err := c.ShouldBindJSON(&sysOperationRecord)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = operationRecordService.CreateSysOperationRecord(sysOperationRecord)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysOperationRecord
|
||||
// @Tags SysOperationRecord
|
||||
// @Summary 删除SysOperationRecord
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SysParamsApi struct{}
|
||||
|
||||
// CreateSysParams 创建参数
|
||||
// @Tags SysParams
|
||||
// @Summary 创建参数
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysParams true "创建参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /sysParams/createSysParams [post]
|
||||
func (sysParamsApi *SysParamsApi) CreateSysParams(c *gin.Context) {
|
||||
var sysParams system.SysParams
|
||||
err := c.ShouldBindJSON(&sysParams)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = sysParamsService.CreateSysParams(&sysParams)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysParams 删除参数
|
||||
// @Tags SysParams
|
||||
// @Summary 删除参数
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysParams true "删除参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /sysParams/deleteSysParams [delete]
|
||||
func (sysParamsApi *SysParamsApi) DeleteSysParams(c *gin.Context) {
|
||||
ID := c.Query("ID")
|
||||
err := sysParamsService.DeleteSysParams(ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysParamsByIds 批量删除参数
|
||||
// @Tags SysParams
|
||||
// @Summary 批量删除参数
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||
// @Router /sysParams/deleteSysParamsByIds [delete]
|
||||
func (sysParamsApi *SysParamsApi) DeleteSysParamsByIds(c *gin.Context) {
|
||||
IDs := c.QueryArray("IDs[]")
|
||||
err := sysParamsService.DeleteSysParamsByIds(IDs)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateSysParams 更新参数
|
||||
// @Tags SysParams
|
||||
// @Summary 更新参数
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysParams true "更新参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "更新成功"
|
||||
// @Router /sysParams/updateSysParams [put]
|
||||
func (sysParamsApi *SysParamsApi) UpdateSysParams(c *gin.Context) {
|
||||
var sysParams system.SysParams
|
||||
err := c.ShouldBindJSON(&sysParams)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = sysParamsService.UpdateSysParams(sysParams)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// FindSysParams 用id查询参数
|
||||
// @Tags SysParams
|
||||
// @Summary 用id查询参数
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query system.SysParams true "用id查询参数"
|
||||
// @Success 200 {object} response.Response{data=system.SysParams,msg=string} "查询成功"
|
||||
// @Router /sysParams/findSysParams [get]
|
||||
func (sysParamsApi *SysParamsApi) FindSysParams(c *gin.Context) {
|
||||
ID := c.Query("ID")
|
||||
resysParams, err := sysParamsService.GetSysParams(ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(resysParams, c)
|
||||
}
|
||||
|
||||
// GetSysParamsList 分页获取参数列表
|
||||
// @Tags SysParams
|
||||
// @Summary 分页获取参数列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query systemReq.SysParamsSearch true "分页获取参数列表"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /sysParams/getSysParamsList [get]
|
||||
func (sysParamsApi *SysParamsApi) GetSysParamsList(c *gin.Context) {
|
||||
var pageInfo systemReq.SysParamsSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := sysParamsService.GetSysParamsInfoList(pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysParam 根据key获取参数value
|
||||
// @Tags SysParams
|
||||
// @Summary 根据key获取参数value
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param key query string true "key"
|
||||
// @Success 200 {object} response.Response{data=system.SysParams,msg=string} "获取成功"
|
||||
// @Router /sysParams/getSysParam [get]
|
||||
func (sysParamsApi *SysParamsApi) GetSysParam(c *gin.Context) {
|
||||
k := c.Query("key")
|
||||
params, err := sysParamsService.GetSysParam(k)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(params, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SkillsApi struct{}
|
||||
|
||||
func (s *SkillsApi) GetTools(c *gin.Context) {
|
||||
data, err := skillsService.Tools(c.Request.Context())
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取工具列表失败", zap.Error(err))
|
||||
response.FailWithMessage("获取工具列表失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"tools": data}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetSkillList(c *gin.Context) {
|
||||
var req request.SkillToolRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
data, err := skillsService.List(c.Request.Context(), req.Tool)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取技能列表失败", zap.Error(err))
|
||||
response.FailWithMessage("获取技能列表失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"skills": data}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetSkillDetail(c *gin.Context) {
|
||||
var req request.SkillDetailRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
data, err := skillsService.Detail(c.Request.Context(), req.Tool, req.Skill)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取技能详情失败", zap.Error(err))
|
||||
response.FailWithMessage("获取技能详情失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"detail": data}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) SaveSkill(c *gin.Context) {
|
||||
var req request.SkillSaveRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.Save(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("保存技能失败", zap.Error(err))
|
||||
response.FailWithMessage("保存技能失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("保存成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) DeleteSkill(c *gin.Context) {
|
||||
var req request.SkillDeleteRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.Delete(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("删除技能失败", zap.Error(err))
|
||||
response.FailWithMessage("删除技能失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) CreateScript(c *gin.Context) {
|
||||
var req request.SkillScriptCreateRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
fileName, content, err := skillsService.CreateScript(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建脚本失败", zap.Error(err))
|
||||
response.FailWithMessage("创建脚本失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetScript(c *gin.Context) {
|
||||
var req request.SkillFileRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
content, err := skillsService.GetScript(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("读取脚本失败", zap.Error(err))
|
||||
response.FailWithMessage("读取脚本失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) SaveScript(c *gin.Context) {
|
||||
var req request.SkillFileSaveRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.SaveScript(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("保存脚本失败", zap.Error(err))
|
||||
response.FailWithMessage("保存脚本失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("保存成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) CreateResource(c *gin.Context) {
|
||||
var req request.SkillResourceCreateRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
fileName, content, err := skillsService.CreateResource(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建资源失败", zap.Error(err))
|
||||
response.FailWithMessage("创建资源失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetResource(c *gin.Context) {
|
||||
var req request.SkillFileRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
content, err := skillsService.GetResource(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("读取资源失败", zap.Error(err))
|
||||
response.FailWithMessage("读取资源失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) SaveResource(c *gin.Context) {
|
||||
var req request.SkillFileSaveRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.SaveResource(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("保存资源失败", zap.Error(err))
|
||||
response.FailWithMessage("保存资源失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("保存成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) CreateReference(c *gin.Context) {
|
||||
var req request.SkillReferenceCreateRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
fileName, content, err := skillsService.CreateReference(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建参考失败", zap.Error(err))
|
||||
response.FailWithMessage("创建参考失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetReference(c *gin.Context) {
|
||||
var req request.SkillFileRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
content, err := skillsService.GetReference(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("读取参考失败", zap.Error(err))
|
||||
response.FailWithMessage("读取参考失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) SaveReference(c *gin.Context) {
|
||||
var req request.SkillFileSaveRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.SaveReference(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("保存参考失败", zap.Error(err))
|
||||
response.FailWithMessage("保存参考失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("保存成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) CreateTemplate(c *gin.Context) {
|
||||
var req request.SkillTemplateCreateRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
fileName, content, err := skillsService.CreateTemplate(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建模板失败", zap.Error(err))
|
||||
response.FailWithMessage("创建模板失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetTemplate(c *gin.Context) {
|
||||
var req request.SkillFileRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
content, err := skillsService.GetTemplate(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("读取模板失败", zap.Error(err))
|
||||
response.FailWithMessage("读取模板失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) SaveTemplate(c *gin.Context) {
|
||||
var req request.SkillFileSaveRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.SaveTemplate(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("保存模板失败", zap.Error(err))
|
||||
response.FailWithMessage("保存模板失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("保存成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) GetGlobalConstraint(c *gin.Context) {
|
||||
var req request.SkillToolRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
content, exists, err := skillsService.GetGlobalConstraint(c.Request.Context(), req.Tool)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("读取全局约束失败", zap.Error(err))
|
||||
response.FailWithMessage("读取全局约束失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"content": content, "exists": exists}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) SaveGlobalConstraint(c *gin.Context) {
|
||||
var req request.SkillGlobalConstraintSaveRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
if err := skillsService.SaveGlobalConstraint(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("保存全局约束失败", zap.Error(err))
|
||||
response.FailWithMessage("保存全局约束失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("保存成功", c)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) PackageSkill(c *gin.Context) {
|
||||
var req request.SkillPackageRequest
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
|
||||
fileName, data, err := skillsService.Package(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("打包技能失败", zap.Error(err))
|
||||
response.FailWithMessage("打包技能失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "application/zip")
|
||||
c.Header("Content-Disposition", "attachment; filename=\""+fileName+"\"")
|
||||
c.Data(http.StatusOK, "application/zip", data)
|
||||
}
|
||||
|
||||
func (s *SkillsApi) DownloadOnlineSkill(c *gin.Context) {
|
||||
var req request.DownloadOnlineSkillReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := skillsService.DownloadOnlineSkill(c.Request.Context(), req); err != nil {
|
||||
global.GVA_LOG.Error("下载在线技能失败", zap.Error(err))
|
||||
response.FailWithMessage("下载在线技能失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("下载成功", c)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -56,19 +55,20 @@ func (s *SystemApi) SetSystemConfig(c *gin.Context) {
|
||||
|
||||
// ReloadSystem
|
||||
// @Tags System
|
||||
// @Summary 重启系统
|
||||
// @Summary 重载系统
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "重启系统"
|
||||
// @Success 200 {object} response.Response{msg=string} "重载系统"
|
||||
// @Router /system/reloadSystem [post]
|
||||
func (s *SystemApi) ReloadSystem(c *gin.Context) {
|
||||
err := utils.Reload()
|
||||
// 触发系统重载事件
|
||||
err := utils.GlobalSystemEvents.TriggerReload()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("重启系统失败!", zap.Error(err))
|
||||
response.FailWithMessage("重启系统失败", c)
|
||||
global.GVA_LOG.Error("重载系统失败!", zap.Error(err))
|
||||
response.FailWithMessage("重载系统失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("重启系统成功", c)
|
||||
response.OkWithMessage("重载系统成功", c)
|
||||
}
|
||||
|
||||
// GetServerInfo
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
@@ -27,8 +27,6 @@ import (
|
||||
func (b *BaseApi) Login(c *gin.Context) {
|
||||
var l systemReq.Login
|
||||
err := c.ShouldBindJSON(&l)
|
||||
key := c.ClientIP()
|
||||
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
@@ -39,6 +37,7 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
key := c.ClientIP()
|
||||
// 判断验证码是否开启
|
||||
openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数
|
||||
openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
|
||||
@@ -48,49 +47,76 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
var oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v)
|
||||
|
||||
if !oc || (l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||
u := &system.SysUser{Username: l.Username, Password: l.Password}
|
||||
user, err := userService.Login(u)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
return
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
return
|
||||
}
|
||||
b.TokenNext(c, *user)
|
||||
if oc && (l.Captcha == "" || l.CaptchaId == "" || !store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("验证码错误", c)
|
||||
// 记录登录失败日志
|
||||
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||
Username: l.Username,
|
||||
Ip: c.ClientIP(),
|
||||
Agent: c.Request.UserAgent(),
|
||||
Status: false,
|
||||
ErrorMessage: "验证码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("验证码错误", c)
|
||||
|
||||
u := &system.SysUser{Username: l.Username, Password: l.Password}
|
||||
user, err := userService.Login(u)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
// 记录登录失败日志
|
||||
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||
Username: l.Username,
|
||||
Ip: c.ClientIP(),
|
||||
Agent: c.Request.UserAgent(),
|
||||
Status: false,
|
||||
ErrorMessage: "用户名不存在或者密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
// 记录登录失败日志
|
||||
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||
Username: l.Username,
|
||||
Ip: c.ClientIP(),
|
||||
Agent: c.Request.UserAgent(),
|
||||
Status: false,
|
||||
ErrorMessage: "用户被禁止登录",
|
||||
UserID: user.ID,
|
||||
})
|
||||
return
|
||||
}
|
||||
b.TokenNext(c, *user)
|
||||
}
|
||||
|
||||
// TokenNext 登录以后签发jwt
|
||||
func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
|
||||
j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
|
||||
claims := j.CreateClaims(systemReq.BaseClaims{
|
||||
UUID: user.UUID,
|
||||
ID: user.ID,
|
||||
NickName: user.NickName,
|
||||
Username: user.Username,
|
||||
AuthorityId: user.AuthorityId,
|
||||
})
|
||||
token, err := j.CreateToken(claims)
|
||||
token, claims, err := utils.LoginToken(&user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取token失败", c)
|
||||
return
|
||||
}
|
||||
// 记录登录成功日志
|
||||
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||
Username: user.Username,
|
||||
Ip: c.ClientIP(),
|
||||
Agent: c.Request.UserAgent(),
|
||||
Status: true,
|
||||
UserID: user.ID,
|
||||
ErrorMessage: "登录成功",
|
||||
})
|
||||
if !global.GVA_CONFIG.System.UseMultipoint {
|
||||
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
response.OkWithDetailed(systemRes.LoginResponse{
|
||||
User: user,
|
||||
Token: token,
|
||||
@@ -100,11 +126,12 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
|
||||
}
|
||||
|
||||
if jwtStr, err := jwtService.GetRedisJWT(user.Username); err == redis.Nil {
|
||||
if err := jwtService.SetRedisJWT(token, user.Username); err != nil {
|
||||
if err := utils.SetRedisJWT(token, user.Username); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置登录状态失败", c)
|
||||
return
|
||||
}
|
||||
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
response.OkWithDetailed(systemRes.LoginResponse{
|
||||
User: user,
|
||||
Token: token,
|
||||
@@ -120,10 +147,11 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
|
||||
response.FailWithMessage("jwt作废失败", c)
|
||||
return
|
||||
}
|
||||
if err := jwtService.SetRedisJWT(token, user.Username); err != nil {
|
||||
if err := utils.SetRedisJWT(token, user.GetUsername()); err != nil {
|
||||
response.FailWithMessage("设置登录状态失败", c)
|
||||
return
|
||||
}
|
||||
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
response.OkWithDetailed(systemRes.LoginResponse{
|
||||
User: user,
|
||||
Token: token,
|
||||
@@ -189,7 +217,7 @@ func (b *BaseApi) ChangePassword(c *gin.Context) {
|
||||
}
|
||||
uid := utils.GetUserID(c)
|
||||
u := &system.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password}
|
||||
_, err = userService.ChangePassword(u, req.NewPassword)
|
||||
err = userService.ChangePassword(u, req.NewPassword)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||
response.FailWithMessage("修改失败,原密码与当前账户不符", c)
|
||||
@@ -204,11 +232,11 @@ func (b *BaseApi) ChangePassword(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Param data body systemReq.GetUserList true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取用户列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /user/getUserList [post]
|
||||
func (b *BaseApi) GetUserList(c *gin.Context) {
|
||||
var pageInfo request.PageInfo
|
||||
var pageInfo systemReq.GetUserList
|
||||
err := c.ShouldBindJSON(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
@@ -261,16 +289,17 @@ func (b *BaseApi) SetUserAuthority(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
claims := utils.GetUserInfo(c)
|
||||
j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
|
||||
claims.AuthorityId = sua.AuthorityId
|
||||
if token, err := j.CreateToken(*claims); err != nil {
|
||||
token, err := utils.NewJWT().CreateToken(*claims)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
} else {
|
||||
c.Header("new-token", token)
|
||||
c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))
|
||||
response.OkWithMessage("修改成功", c)
|
||||
return
|
||||
}
|
||||
c.Header("new-token", token)
|
||||
c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))
|
||||
utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
response.OkWithMessage("修改成功", c)
|
||||
}
|
||||
|
||||
// SetUserAuthorities
|
||||
@@ -289,7 +318,8 @@ func (b *BaseApi) SetUserAuthorities(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = userService.SetUserAuthorities(sua.ID, sua.AuthorityIds)
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
err = userService.SetUserAuthorities(authorityID, sua.ID, sua.AuthorityIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||
response.FailWithMessage("修改失败", c)
|
||||
@@ -321,7 +351,7 @@ func (b *BaseApi) DeleteUser(c *gin.Context) {
|
||||
}
|
||||
jwtId := utils.GetUserID(c)
|
||||
if jwtId == uint(reqId.ID) {
|
||||
response.FailWithMessage("删除失败, 自杀失败", c)
|
||||
response.FailWithMessage("删除失败, 无法删除自己。", c)
|
||||
return
|
||||
}
|
||||
err = userService.DeleteUser(reqId.ID)
|
||||
@@ -354,9 +384,9 @@ func (b *BaseApi) SetUserInfo(c *gin.Context) {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if len(user.AuthorityIds) != 0 {
|
||||
err = userService.SetUserAuthorities(user.ID, user.AuthorityIds)
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
err = userService.SetUserAuthorities(authorityID, user.ID, user.AuthorityIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置失败", c)
|
||||
@@ -371,7 +401,6 @@ func (b *BaseApi) SetUserInfo(c *gin.Context) {
|
||||
HeaderImg: user.HeaderImg,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
SideMode: user.SideMode,
|
||||
Enable: user.Enable,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -407,7 +436,6 @@ func (b *BaseApi) SetSelfInfo(c *gin.Context) {
|
||||
HeaderImg: user.HeaderImg,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
SideMode: user.SideMode,
|
||||
Enable: user.Enable,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -418,6 +446,32 @@ func (b *BaseApi) SetSelfInfo(c *gin.Context) {
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// SetSelfSetting
|
||||
// @Tags SysUser
|
||||
// @Summary 设置用户配置
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body map[string]interface{} true "用户配置数据"
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户配置"
|
||||
// @Router /user/SetSelfSetting [put]
|
||||
func (b *BaseApi) SetSelfSetting(c *gin.Context) {
|
||||
var req common.JSONMap
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
err = userService.SetSelfSetting(req, utils.GetUserID(c))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||
response.FailWithMessage("设置失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// GetUserInfo
|
||||
// @Tags SysUser
|
||||
// @Summary 获取用户信息
|
||||
@@ -446,13 +500,13 @@ func (b *BaseApi) GetUserInfo(c *gin.Context) {
|
||||
// @Success 200 {object} response.Response{msg=string} "重置用户密码"
|
||||
// @Router /user/resetPassword [post]
|
||||
func (b *BaseApi) ResetPassword(c *gin.Context) {
|
||||
var user system.SysUser
|
||||
err := c.ShouldBindJSON(&user)
|
||||
var rps systemReq.ResetPassword
|
||||
err := c.ShouldBindJSON(&rps)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = userService.ResetPassword(user.ID)
|
||||
err = userService.ResetPassword(rps.ID, rps.Password)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("重置失败!", zap.Error(err))
|
||||
response.FailWithMessage("重置失败"+err.Error(), c)
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SysVersionApi struct{}
|
||||
|
||||
// buildMenuTree 构建菜单树结构
|
||||
func buildMenuTree(menus []system.SysBaseMenu) []system.SysBaseMenu {
|
||||
// 创建菜单映射
|
||||
menuMap := make(map[uint]*system.SysBaseMenu)
|
||||
for i := range menus {
|
||||
menuMap[menus[i].ID] = &menus[i]
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
var rootMenus []system.SysBaseMenu
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId == 0 {
|
||||
// 根菜单
|
||||
menuData := convertMenuToStruct(menu, menuMap)
|
||||
rootMenus = append(rootMenus, menuData)
|
||||
}
|
||||
}
|
||||
|
||||
// 按sort排序根菜单
|
||||
sort.Slice(rootMenus, func(i, j int) bool {
|
||||
return rootMenus[i].Sort < rootMenus[j].Sort
|
||||
})
|
||||
|
||||
return rootMenus
|
||||
}
|
||||
|
||||
// convertMenuToStruct 将菜单转换为结构体并递归处理子菜单
|
||||
func convertMenuToStruct(menu system.SysBaseMenu, menuMap map[uint]*system.SysBaseMenu) system.SysBaseMenu {
|
||||
result := system.SysBaseMenu{
|
||||
Path: menu.Path,
|
||||
Name: menu.Name,
|
||||
Hidden: menu.Hidden,
|
||||
Component: menu.Component,
|
||||
Sort: menu.Sort,
|
||||
Meta: menu.Meta,
|
||||
}
|
||||
|
||||
// 清理并复制参数数据
|
||||
if len(menu.Parameters) > 0 {
|
||||
cleanParameters := make([]system.SysBaseMenuParameter, 0, len(menu.Parameters))
|
||||
for _, param := range menu.Parameters {
|
||||
cleanParam := system.SysBaseMenuParameter{
|
||||
Type: param.Type,
|
||||
Key: param.Key,
|
||||
Value: param.Value,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID
|
||||
}
|
||||
cleanParameters = append(cleanParameters, cleanParam)
|
||||
}
|
||||
result.Parameters = cleanParameters
|
||||
}
|
||||
|
||||
// 清理并复制菜单按钮数据
|
||||
if len(menu.MenuBtn) > 0 {
|
||||
cleanMenuBtns := make([]system.SysBaseMenuBtn, 0, len(menu.MenuBtn))
|
||||
for _, btn := range menu.MenuBtn {
|
||||
cleanBtn := system.SysBaseMenuBtn{
|
||||
Name: btn.Name,
|
||||
Desc: btn.Desc,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID
|
||||
}
|
||||
cleanMenuBtns = append(cleanMenuBtns, cleanBtn)
|
||||
}
|
||||
result.MenuBtn = cleanMenuBtns
|
||||
}
|
||||
|
||||
// 查找并处理子菜单
|
||||
var children []system.SysBaseMenu
|
||||
for _, childMenu := range menuMap {
|
||||
if childMenu.ParentId == menu.ID {
|
||||
childData := convertMenuToStruct(*childMenu, menuMap)
|
||||
children = append(children, childData)
|
||||
}
|
||||
}
|
||||
|
||||
// 按sort排序子菜单
|
||||
if len(children) > 0 {
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].Sort < children[j].Sort
|
||||
})
|
||||
result.Children = children
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// DeleteSysVersion 删除版本管理
|
||||
// @Tags SysVersion
|
||||
// @Summary 删除版本管理
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysVersion true "删除版本管理"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /sysVersion/deleteSysVersion [delete]
|
||||
func (sysVersionApi *SysVersionApi) DeleteSysVersion(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
err := sysVersionService.DeleteSysVersion(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysVersionByIds 批量删除版本管理
|
||||
// @Tags SysVersion
|
||||
// @Summary 批量删除版本管理
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||
// @Router /sysVersion/deleteSysVersionByIds [delete]
|
||||
func (sysVersionApi *SysVersionApi) DeleteSysVersionByIds(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
IDs := c.QueryArray("IDs[]")
|
||||
err := sysVersionService.DeleteSysVersionByIds(ctx, IDs)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
|
||||
// FindSysVersion 用id查询版本管理
|
||||
// @Tags SysVersion
|
||||
// @Summary 用id查询版本管理
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param ID query uint true "用id查询版本管理"
|
||||
// @Success 200 {object} response.Response{data=system.SysVersion,msg=string} "查询成功"
|
||||
// @Router /sysVersion/findSysVersion [get]
|
||||
func (sysVersionApi *SysVersionApi) FindSysVersion(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
resysVersion, err := sysVersionService.GetSysVersion(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(resysVersion, c)
|
||||
}
|
||||
|
||||
// GetSysVersionList 分页获取版本管理列表
|
||||
// @Tags SysVersion
|
||||
// @Summary 分页获取版本管理列表
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query systemReq.SysVersionSearch true "分页获取版本管理列表"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /sysVersion/getSysVersionList [get]
|
||||
func (sysVersionApi *SysVersionApi) GetSysVersionList(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var pageInfo systemReq.SysVersionSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := sysVersionService.GetSysVersionInfoList(ctx, pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysVersionPublic 不需要鉴权的版本管理接口
|
||||
// @Tags SysVersion
|
||||
// @Summary 不需要鉴权的版本管理接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /sysVersion/getSysVersionPublic [get]
|
||||
func (sysVersionApi *SysVersionApi) GetSysVersionPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
sysVersionService.GetSysVersionPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的版本管理接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// ExportVersion 创建发版数据
|
||||
// @Tags SysVersion
|
||||
// @Summary 创建发版数据
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.ExportVersionRequest true "创建发版数据"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /sysVersion/exportVersion [post]
|
||||
func (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req systemReq.ExportVersionRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取选中的菜单数据
|
||||
var menuData []system.SysBaseMenu
|
||||
if len(req.MenuIds) > 0 {
|
||||
menuData, err = sysVersionService.GetMenusByIds(ctx, req.MenuIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取菜单数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取菜单数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的API数据
|
||||
var apiData []system.SysApi
|
||||
if len(req.ApiIds) > 0 {
|
||||
apiData, err = sysVersionService.GetApisByIds(ctx, req.ApiIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取API数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取API数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的字典数据
|
||||
var dictData []system.SysDictionary
|
||||
if len(req.DictIds) > 0 {
|
||||
dictData, err = sysVersionService.GetDictionariesByIds(ctx, req.DictIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取字典数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取字典数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 处理菜单数据,构建递归的children结构
|
||||
processedMenus := buildMenuTree(menuData)
|
||||
|
||||
// 处理API数据,清除ID和时间戳字段
|
||||
processedApis := make([]system.SysApi, 0, len(apiData))
|
||||
for _, api := range apiData {
|
||||
cleanApi := system.SysApi{
|
||||
Path: api.Path,
|
||||
Description: api.Description,
|
||||
ApiGroup: api.ApiGroup,
|
||||
Method: api.Method,
|
||||
}
|
||||
processedApis = append(processedApis, cleanApi)
|
||||
}
|
||||
|
||||
// 处理字典数据,清除ID和时间戳字段,包含字典详情
|
||||
processedDicts := make([]system.SysDictionary, 0, len(dictData))
|
||||
for _, dict := range dictData {
|
||||
cleanDict := system.SysDictionary{
|
||||
Name: dict.Name,
|
||||
Type: dict.Type,
|
||||
Status: dict.Status,
|
||||
Desc: dict.Desc,
|
||||
}
|
||||
|
||||
// 处理字典详情数据,清除ID和时间戳字段
|
||||
cleanDetails := make([]system.SysDictionaryDetail, 0, len(dict.SysDictionaryDetails))
|
||||
for _, detail := range dict.SysDictionaryDetails {
|
||||
cleanDetail := system.SysDictionaryDetail{
|
||||
Label: detail.Label,
|
||||
Value: detail.Value,
|
||||
Extend: detail.Extend,
|
||||
Status: detail.Status,
|
||||
Sort: detail.Sort,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysDictionaryID
|
||||
}
|
||||
cleanDetails = append(cleanDetails, cleanDetail)
|
||||
}
|
||||
cleanDict.SysDictionaryDetails = cleanDetails
|
||||
|
||||
processedDicts = append(processedDicts, cleanDict)
|
||||
}
|
||||
|
||||
// 构建导出数据
|
||||
exportData := systemRes.ExportVersionResponse{
|
||||
Version: systemReq.VersionInfo{
|
||||
Name: req.VersionName,
|
||||
Code: req.VersionCode,
|
||||
Description: req.Description,
|
||||
ExportTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Menus: processedMenus,
|
||||
Apis: processedApis,
|
||||
Dictionaries: processedDicts,
|
||||
}
|
||||
|
||||
// 转换为JSON
|
||||
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("JSON序列化失败!", zap.Error(err))
|
||||
response.FailWithMessage("JSON序列化失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存版本记录
|
||||
version := system.SysVersion{
|
||||
VersionName: utils.Pointer(req.VersionName),
|
||||
VersionCode: utils.Pointer(req.VersionCode),
|
||||
Description: utils.Pointer(req.Description),
|
||||
VersionData: utils.Pointer(string(jsonData)),
|
||||
}
|
||||
|
||||
err = sysVersionService.CreateSysVersion(ctx, &version)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("保存版本记录失败!", zap.Error(err))
|
||||
response.FailWithMessage("保存版本记录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建发版成功", c)
|
||||
}
|
||||
|
||||
// DownloadVersionJson 下载版本JSON数据
|
||||
// @Tags SysVersion
|
||||
// @Summary 下载版本JSON数据
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param ID query string true "版本ID"
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "下载成功"
|
||||
// @Router /sysVersion/downloadVersionJson [get]
|
||||
func (sysVersionApi *SysVersionApi) DownloadVersionJson(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
if ID == "" {
|
||||
response.FailWithMessage("版本ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取版本记录
|
||||
version, err := sysVersionService.GetSysVersion(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取版本记录失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取版本记录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建JSON数据
|
||||
var jsonData []byte
|
||||
if version.VersionData != nil && *version.VersionData != "" {
|
||||
jsonData = []byte(*version.VersionData)
|
||||
} else {
|
||||
// 如果没有存储的JSON数据,构建一个基本的结构
|
||||
basicData := systemRes.ExportVersionResponse{
|
||||
Version: systemReq.VersionInfo{
|
||||
Name: *version.VersionName,
|
||||
Code: *version.VersionCode,
|
||||
Description: *version.Description,
|
||||
ExportTime: version.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Menus: []system.SysBaseMenu{},
|
||||
Apis: []system.SysApi{},
|
||||
}
|
||||
jsonData, _ = json.MarshalIndent(basicData, "", " ")
|
||||
}
|
||||
|
||||
// 设置下载响应头
|
||||
filename := fmt.Sprintf("version_%s_%s.json", *version.VersionCode, time.Now().Format("20060102150405"))
|
||||
c.Header("Content-Type", "application/json")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
c.Header("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
|
||||
c.Data(http.StatusOK, "application/json", jsonData)
|
||||
}
|
||||
|
||||
// ImportVersion 导入版本数据
|
||||
// @Tags SysVersion
|
||||
// @Summary 导入版本数据
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.ImportVersionRequest true "版本JSON数据"
|
||||
// @Success 200 {object} response.Response{msg=string} "导入成功"
|
||||
// @Router /sysVersion/importVersion [post]
|
||||
func (sysVersionApi *SysVersionApi) ImportVersion(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 获取JSON数据
|
||||
var importData systemReq.ImportVersionRequest
|
||||
err := c.ShouldBindJSON(&importData)
|
||||
if err != nil {
|
||||
response.FailWithMessage("解析JSON数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证数据格式
|
||||
if importData.VersionInfo.Name == "" || importData.VersionInfo.Code == "" {
|
||||
response.FailWithMessage("版本信息格式错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 导入菜单数据
|
||||
if len(importData.ExportMenu) > 0 {
|
||||
if err := sysVersionService.ImportMenus(ctx, importData.ExportMenu); err != nil {
|
||||
global.GVA_LOG.Error("导入菜单失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入菜单失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 导入API数据
|
||||
if len(importData.ExportApi) > 0 {
|
||||
if err := sysVersionService.ImportApis(importData.ExportApi); err != nil {
|
||||
global.GVA_LOG.Error("导入API失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入API失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 导入字典数据
|
||||
if len(importData.ExportDictionary) > 0 {
|
||||
if err := sysVersionService.ImportDictionaries(importData.ExportDictionary); err != nil {
|
||||
global.GVA_LOG.Error("导入字典失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入字典失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 创建导入记录
|
||||
jsonData, _ := json.Marshal(importData)
|
||||
version := system.SysVersion{
|
||||
VersionName: utils.Pointer(importData.VersionInfo.Name),
|
||||
VersionCode: utils.Pointer(fmt.Sprintf("%s_imported_%s", importData.VersionInfo.Code, time.Now().Format("20060102150405"))),
|
||||
Description: utils.Pointer(fmt.Sprintf("导入版本: %s", importData.VersionInfo.Description)),
|
||||
VersionData: utils.Pointer(string(jsonData)),
|
||||
}
|
||||
|
||||
err = sysVersionService.CreateSysVersion(ctx, &version)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("保存导入记录失败!", zap.Error(err))
|
||||
// 这里不返回错误,因为数据已经导入成功
|
||||
}
|
||||
|
||||
response.OkWithMessage("导入成功", c)
|
||||
}
|
||||
+223
-141
@@ -2,90 +2,103 @@
|
||||
|
||||
# jwt configuration
|
||||
jwt:
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
# zap logger configuration
|
||||
zap:
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
db: 0
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
#是否使用redis集群模式
|
||||
useCluster: false
|
||||
#使用集群模式addr和db默认无效
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "177.7.0.14:7000"
|
||||
- "177.7.0.15:7001"
|
||||
- "177.7.0.13:7002"
|
||||
|
||||
# redis-list configuration
|
||||
redis-list:
|
||||
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
|
||||
useCluster: false # 是否使用redis集群模式
|
||||
addr: 177.7.0.14:6379 # 使用集群模式addr和db默认无效
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "177.7.0.14:7000"
|
||||
- "177.7.0.15:7001"
|
||||
- "177.7.0.13:7002"
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
auth-source: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
|
||||
# email configuration
|
||||
email:
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: public # Change to "develop" to skip authentication for development mode
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
# 严格角色模式 打开后权限将会存在上下级关系
|
||||
use-strict-auth: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
|
||||
# mysql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
mysql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
@@ -97,107 +110,176 @@ db-list:
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# local configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
|
||||
# autocode configuration
|
||||
autocode:
|
||||
transfer-restart: true
|
||||
# root 自动适配项目根目录
|
||||
# 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
root: ""
|
||||
server: /server
|
||||
server-plug: /plugin/%s
|
||||
server-api: /api/v1/%s
|
||||
server-initialize: /initialize
|
||||
server-model: /model/%s
|
||||
server-request: /model/%s/request/
|
||||
server-router: /router/%s
|
||||
server-service: /service/%s
|
||||
web: /web/src
|
||||
web-api: /api
|
||||
web-form: /view
|
||||
web-table: /view
|
||||
web: web/src
|
||||
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
server: server
|
||||
module: 'github.com/flipped-aurora/gin-vue-admin/server'
|
||||
ai-path: "" # AI服务路径
|
||||
|
||||
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
|
||||
# minio oss configuration
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: "http://host:9000/yourBucketName"
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# aws s3 configuration (minio compatible)
|
||||
aws-s3:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# cloudflare r2 configuration
|
||||
cloudflare-r2:
|
||||
bucket: xxxx0bucket
|
||||
base-url: https://gin.vue.admin.com
|
||||
path: uploads
|
||||
account-id: xxx_account_id
|
||||
access-key-id: xxx_key_id
|
||||
secret-access-key: xxx_secret_key
|
||||
|
||||
# huawei obs configuration
|
||||
hua-wei-obs:
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
|
||||
# excel configuration
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
dir: ./resource/excel/
|
||||
|
||||
# timer task db clear table
|
||||
Timer:
|
||||
start: true
|
||||
spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3
|
||||
detail:
|
||||
- tableName: sys_operation_records
|
||||
compareField: created_at
|
||||
interval: 2160h
|
||||
- tableName: jwt_blacklists
|
||||
compareField: created_at
|
||||
interval: 168h
|
||||
# disk usage configuration
|
||||
disk-list:
|
||||
- mount-point: "/"
|
||||
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mcp:
|
||||
name: GVA_MCP
|
||||
version: v1.0.0
|
||||
sse_path: /sse
|
||||
message_path: /message
|
||||
url_prefix: ''
|
||||
addr: 8889
|
||||
separate: false
|
||||
|
||||
+225
-176
@@ -2,124 +2,105 @@
|
||||
|
||||
# jwt configuration
|
||||
jwt:
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
# zap logger configuration
|
||||
zap:
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
db: 0
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
#是否使用redis集群模式
|
||||
useCluster: false
|
||||
#使用集群模式addr和db默认无效
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "172.21.0.3:7000"
|
||||
- "172.21.0.4:7001"
|
||||
- "172.21.0.2:7002"
|
||||
|
||||
# redis-list configuration
|
||||
redis-list:
|
||||
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
|
||||
useCluster: false # 是否使用redis集群模式
|
||||
addr: 127.0.0.1:6379 # 使用集群模式addr和db默认无效
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "172.21.0.3:7000"
|
||||
- "172.21.0.4:7001"
|
||||
- "172.21.0.2:7002"
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
auth-source: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
|
||||
# email configuration
|
||||
email:
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
# 严格角色模式 打开后权限将会存在上下级关系
|
||||
use-strict-auth: false
|
||||
# 禁用自动迁移数据库表结构,生产环境建议设为true,手动迁移
|
||||
disable-auto-migrate: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
|
||||
# mysql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
mysql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
@@ -131,107 +112,175 @@ db-list:
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# local configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
|
||||
# autocode configuration
|
||||
autocode:
|
||||
transfer-restart: true
|
||||
# root 自动适配项目根目录
|
||||
# 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
root: ""
|
||||
server: /server
|
||||
server-plug: /plugin/%s
|
||||
server-api: /api/v1/%s
|
||||
server-initialize: /initialize
|
||||
server-model: /model/%s
|
||||
server-request: /model/%s/request/
|
||||
server-router: /router/%s
|
||||
server-service: /service/%s
|
||||
web: /web/src
|
||||
web-api: /api
|
||||
web-form: /view
|
||||
web-table: /view
|
||||
web: web/src
|
||||
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
server: server
|
||||
module: 'github.com/flipped-aurora/gin-vue-admin/server'
|
||||
ai-path: "" # AI服务路径
|
||||
|
||||
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
|
||||
# minio oss configuration
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: "http://host:9000/yourBucketName"
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# aws s3 configuration (minio compatible)
|
||||
aws-s3:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# cloudflare r2 configuration
|
||||
cloudflare-r2:
|
||||
bucket: xxxx0bucket
|
||||
base-url: https://gin.vue.admin.com
|
||||
path: uploads
|
||||
account-id: xxx_account_id
|
||||
access-key-id: xxx_key_id
|
||||
secret-access-key: xxx_secret_key
|
||||
|
||||
# huawei obs configuration
|
||||
hua-wei-obs:
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
|
||||
# excel configuration
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
dir: ./resource/excel/
|
||||
|
||||
# timer task db clear table
|
||||
Timer:
|
||||
start: true
|
||||
spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3
|
||||
detail:
|
||||
- tableName: sys_operation_records
|
||||
compareField: created_at
|
||||
interval: 2160h
|
||||
- tableName: jwt_blacklists
|
||||
compareField: created_at
|
||||
interval: 168h
|
||||
# disk usage configuration
|
||||
disk-list:
|
||||
- mount-point: "/"
|
||||
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mcp:
|
||||
name: GVA_MCP
|
||||
version: v1.0.0
|
||||
sse_path: /sse
|
||||
message_path: /message
|
||||
url_prefix: ''
|
||||
addr: 8889
|
||||
separate: false
|
||||
|
||||
+18
-14
@@ -1,18 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Autocode struct {
|
||||
SModel string `mapstructure:"server-model" json:"server-model" yaml:"server-model"`
|
||||
SRouter string `mapstructure:"server-router" json:"server-router" yaml:"server-router"`
|
||||
Server string `mapstructure:"server" json:"server" yaml:"server"`
|
||||
SApi string `mapstructure:"server-api" json:"server-api" yaml:"server-api"`
|
||||
SPlug string `mapstructure:"server-plug" json:"server-plug" yaml:"server-plug"`
|
||||
SInitialize string `mapstructure:"server-initialize" json:"server-initialize" yaml:"server-initialize"`
|
||||
Root string `mapstructure:"root" json:"root" yaml:"root"`
|
||||
WTable string `mapstructure:"web-table" json:"web-table" yaml:"web-table"`
|
||||
Web string `mapstructure:"web" json:"web" yaml:"web"`
|
||||
SService string `mapstructure:"server-service" json:"server-service" yaml:"server-service"`
|
||||
SRequest string `mapstructure:"server-request" json:"server-request" yaml:"server-request"`
|
||||
WApi string `mapstructure:"web-api" json:"web-api" yaml:"web-api"`
|
||||
WForm string `mapstructure:"web-form" json:"web-form" yaml:"web-form"`
|
||||
TransferRestart bool `mapstructure:"transfer-restart" json:"transfer-restart" yaml:"transfer-restart"`
|
||||
Web string `mapstructure:"web" json:"web" yaml:"web"`
|
||||
Root string `mapstructure:"root" json:"root" yaml:"root"`
|
||||
Server string `mapstructure:"server" json:"server" yaml:"server"`
|
||||
Module string `mapstructure:"module" json:"module" yaml:"module"`
|
||||
AiPath string `mapstructure:"ai-path" json:"ai-path" yaml:"ai-path"`
|
||||
}
|
||||
|
||||
func (a *Autocode) WebRoot() string {
|
||||
webs := strings.Split(a.Web, "/")
|
||||
if len(webs) == 0 {
|
||||
webs = strings.Split(a.Web, "\\")
|
||||
}
|
||||
return filepath.Join(webs...)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ type Captcha struct {
|
||||
KeyLong int `mapstructure:"key-long" json:"key-long" yaml:"key-long"` // 验证码长度
|
||||
ImgWidth int `mapstructure:"img-width" json:"img-width" yaml:"img-width"` // 验证码宽度
|
||||
ImgHeight int `mapstructure:"img-height" json:"img-height" yaml:"img-height"` // 验证码高度
|
||||
OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码
|
||||
OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码次数,如3代表错误三次后出现验证码
|
||||
OpenCaptchaTimeOut int `mapstructure:"open-captcha-timeout" json:"open-captcha-timeout" yaml:"open-captcha-timeout"` // 防爆破验证码超时时间,单位:s(秒)
|
||||
}
|
||||
|
||||
+21
-13
@@ -1,12 +1,14 @@
|
||||
package config
|
||||
|
||||
type Server struct {
|
||||
JWT JWT `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
|
||||
Zap Zap `mapstructure:"zap" json:"zap" yaml:"zap"`
|
||||
Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"`
|
||||
Email Email `mapstructure:"email" json:"email" yaml:"email"`
|
||||
System System `mapstructure:"system" json:"system" yaml:"system"`
|
||||
Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
|
||||
JWT JWT `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
|
||||
Zap Zap `mapstructure:"zap" json:"zap" yaml:"zap"`
|
||||
Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"`
|
||||
RedisList []Redis `mapstructure:"redis-list" json:"redis-list" yaml:"redis-list"`
|
||||
Mongo Mongo `mapstructure:"mongo" json:"mongo" yaml:"mongo"`
|
||||
Email Email `mapstructure:"email" json:"email" yaml:"email"`
|
||||
System System `mapstructure:"system" json:"system" yaml:"system"`
|
||||
Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
|
||||
// auto
|
||||
AutoCode Autocode `mapstructure:"autocode" json:"autocode" yaml:"autocode"`
|
||||
// gorm
|
||||
@@ -17,16 +19,22 @@ type Server struct {
|
||||
Sqlite Sqlite `mapstructure:"sqlite" json:"sqlite" yaml:"sqlite"`
|
||||
DBList []SpecializedDB `mapstructure:"db-list" json:"db-list" yaml:"db-list"`
|
||||
// oss
|
||||
Local Local `mapstructure:"local" json:"local" yaml:"local"`
|
||||
Qiniu Qiniu `mapstructure:"qiniu" json:"qiniu" yaml:"qiniu"`
|
||||
AliyunOSS AliyunOSS `mapstructure:"aliyun-oss" json:"aliyun-oss" yaml:"aliyun-oss"`
|
||||
HuaWeiObs HuaWeiObs `mapstructure:"hua-wei-obs" json:"hua-wei-obs" yaml:"hua-wei-obs"`
|
||||
TencentCOS TencentCOS `mapstructure:"tencent-cos" json:"tencent-cos" yaml:"tencent-cos"`
|
||||
AwsS3 AwsS3 `mapstructure:"aws-s3" json:"aws-s3" yaml:"aws-s3"`
|
||||
Local Local `mapstructure:"local" json:"local" yaml:"local"`
|
||||
Qiniu Qiniu `mapstructure:"qiniu" json:"qiniu" yaml:"qiniu"`
|
||||
AliyunOSS AliyunOSS `mapstructure:"aliyun-oss" json:"aliyun-oss" yaml:"aliyun-oss"`
|
||||
HuaWeiObs HuaWeiObs `mapstructure:"hua-wei-obs" json:"hua-wei-obs" yaml:"hua-wei-obs"`
|
||||
TencentCOS TencentCOS `mapstructure:"tencent-cos" json:"tencent-cos" yaml:"tencent-cos"`
|
||||
AwsS3 AwsS3 `mapstructure:"aws-s3" json:"aws-s3" yaml:"aws-s3"`
|
||||
CloudflareR2 CloudflareR2 `mapstructure:"cloudflare-r2" json:"cloudflare-r2" yaml:"cloudflare-r2"`
|
||||
Minio Minio `mapstructure:"minio" json:"minio" yaml:"minio"`
|
||||
|
||||
Excel Excel `mapstructure:"excel" json:"excel" yaml:"excel"`
|
||||
Timer Timer `mapstructure:"timer" json:"timer" yaml:"timer"`
|
||||
|
||||
DiskList []DiskList `mapstructure:"disk-list" json:"disk-list" yaml:"disk-list"`
|
||||
|
||||
// 跨域配置
|
||||
Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"`
|
||||
|
||||
// MCP配置
|
||||
MCP MCP `mapstructure:"mcp" json:"mcp" yaml:"mcp"`
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type DsnProvider interface {
|
||||
Dsn() string
|
||||
}
|
||||
@@ -9,21 +15,36 @@ type DsnProvider interface {
|
||||
|
||||
// GeneralDB 也被 Pgsql 和 Mysql 原样使用
|
||||
type GeneralDB struct {
|
||||
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`
|
||||
Port string `mapstructure:"port" json:"port" yaml:"port"`
|
||||
Config string `mapstructure:"config" json:"config" yaml:"config"` // 高级配置
|
||||
Dbname string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` // 数据库名
|
||||
Username string `mapstructure:"username" json:"username" yaml:"username"` // 数据库密码
|
||||
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
|
||||
Path string `mapstructure:"path" json:"path" yaml:"path"`
|
||||
Engine string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"` //数据库引擎,默认InnoDB
|
||||
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 数据库前缀
|
||||
Port string `mapstructure:"port" json:"port" yaml:"port"` // 数据库端口
|
||||
Config string `mapstructure:"config" json:"config" yaml:"config"` // 高级配置
|
||||
Dbname string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` // 数据库名
|
||||
Username string `mapstructure:"username" json:"username" yaml:"username"` // 数据库账号
|
||||
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
|
||||
Path string `mapstructure:"path" json:"path" yaml:"path"` // 数据库地址
|
||||
Engine string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"` // 数据库引擎,默认InnoDB
|
||||
LogMode string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"` // 是否开启Gorm全局日志
|
||||
MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` // 空闲中的最大连接数
|
||||
MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
|
||||
Singular bool `mapstructure:"singular" json:"singular" yaml:"singular"` //是否开启全局禁用复数,true表示开启
|
||||
Singular bool `mapstructure:"singular" json:"singular" yaml:"singular"` // 是否开启全局禁用复数,true表示开启
|
||||
LogZap bool `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` // 是否通过zap写入日志文件
|
||||
}
|
||||
|
||||
func (c GeneralDB) LogLevel() logger.LogLevel {
|
||||
switch strings.ToLower(c.LogMode) {
|
||||
case "silent":
|
||||
return logger.Silent
|
||||
case "error":
|
||||
return logger.Error
|
||||
case "warn":
|
||||
return logger.Warn
|
||||
case "info":
|
||||
return logger.Info
|
||||
default:
|
||||
return logger.Info
|
||||
}
|
||||
}
|
||||
|
||||
type SpecializedDB struct {
|
||||
Type string `mapstructure:"type" json:"type" yaml:"type"`
|
||||
AliasName string `mapstructure:"alias-name" json:"alias-name" yaml:"alias-name"`
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
type Disk struct {
|
||||
MountPoint string `mapstructure:"mount-point" json:"mount-point" yaml:"mount-point"`
|
||||
}
|
||||
|
||||
type DiskList struct {
|
||||
Disk `yaml:",inline" mapstructure:",squash"`
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package config
|
||||
|
||||
type Email struct {
|
||||
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
|
||||
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
|
||||
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
|
||||
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL
|
||||
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
|
||||
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
|
||||
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
|
||||
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL
|
||||
IsLoginAuth bool `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth 是否使用LoginAuth认证方式(适用于IBM、微软邮箱服务器等)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@ package config
|
||||
type Mssql struct {
|
||||
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||
}
|
||||
//dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
|
||||
|
||||
// Dsn "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
|
||||
func (m *Mssql) Dsn() string {
|
||||
return "sqlserver://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "?database=" + m.Dbname + "&encrypt=disable"
|
||||
}
|
||||
|
||||
func (m *Mssql) GetLogMode() string {
|
||||
return m.LogMode
|
||||
}
|
||||
|
||||
@@ -7,7 +7,3 @@ type Mysql struct {
|
||||
func (m *Mysql) Dsn() string {
|
||||
return m.Username + ":" + m.Password + "@tcp(" + m.Path + ":" + m.Port + ")/" + m.Dbname + "?" + m.Config
|
||||
}
|
||||
|
||||
func (m *Mysql) GetLogMode() string {
|
||||
return m.LogMode
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Oracle struct {
|
||||
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (m *Oracle) Dsn() string {
|
||||
return "oracle://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "/" + m.Dbname + "?" + m.Config
|
||||
dsn := fmt.Sprintf("oracle://%s:%s@%s/%s?%s", url.PathEscape(m.Username), url.PathEscape(m.Password),
|
||||
net.JoinHostPort(m.Path, m.Port), url.PathEscape(m.Dbname), m.Config)
|
||||
return dsn
|
||||
|
||||
}
|
||||
|
||||
func (m *Oracle) GetLogMode() string {
|
||||
return m.LogMode
|
||||
}
|
||||
|
||||
@@ -15,7 +15,3 @@ func (p *Pgsql) Dsn() string {
|
||||
func (p *Pgsql) LinkDsn(dbname string) string {
|
||||
return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + dbname + " port=" + p.Port + " " + p.Config
|
||||
}
|
||||
|
||||
func (m *Pgsql) GetLogMode() string {
|
||||
return m.LogMode
|
||||
}
|
||||
|
||||
@@ -11,7 +11,3 @@ type Sqlite struct {
|
||||
func (s *Sqlite) Dsn() string {
|
||||
return filepath.Join(s.Path, s.Dbname+".db")
|
||||
}
|
||||
|
||||
func (s *Sqlite) GetLogMode() string {
|
||||
return s.LogMode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
type MCP struct {
|
||||
Name string `mapstructure:"name" json:"name" yaml:"name"` // MCP名称
|
||||
Version string `mapstructure:"version" json:"version" yaml:"version"` // MCP版本
|
||||
SSEPath string `mapstructure:"sse_path" json:"sse_path" yaml:"sse_path"` // SSE路径
|
||||
MessagePath string `mapstructure:"message_path" json:"message_path" yaml:"message_path"` // 消息路径
|
||||
UrlPrefix string `mapstructure:"url_prefix" json:"url_prefix" yaml:"url_prefix"` // URL前缀
|
||||
Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` // 独立MCP服务端口
|
||||
Separate bool `mapstructure:"separate" json:"separate" yaml:"separate"` // 是否独立运行MCP服务
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Mongo struct {
|
||||
Coll string `json:"coll" yaml:"coll" mapstructure:"coll"` // collection name
|
||||
Options string `json:"options" yaml:"options" mapstructure:"options"` // mongodb options
|
||||
Database string `json:"database" yaml:"database" mapstructure:"database"` // database name
|
||||
Username string `json:"username" yaml:"username" mapstructure:"username"` // 用户名
|
||||
Password string `json:"password" yaml:"password" mapstructure:"password"` // 密码
|
||||
AuthSource string `json:"auth-source" yaml:"auth-source" mapstructure:"auth-source"` // 验证数据库
|
||||
MinPoolSize uint64 `json:"min-pool-size" yaml:"min-pool-size" mapstructure:"min-pool-size"` // 最小连接池
|
||||
MaxPoolSize uint64 `json:"max-pool-size" yaml:"max-pool-size" mapstructure:"max-pool-size"` // 最大连接池
|
||||
SocketTimeoutMs int64 `json:"socket-timeout-ms" yaml:"socket-timeout-ms" mapstructure:"socket-timeout-ms"` // socket超时时间
|
||||
ConnectTimeoutMs int64 `json:"connect-timeout-ms" yaml:"connect-timeout-ms" mapstructure:"connect-timeout-ms"` // 连接超时时间
|
||||
IsZap bool `json:"is-zap" yaml:"is-zap" mapstructure:"is-zap"` // 是否开启zap日志
|
||||
Hosts []*MongoHost `json:"hosts" yaml:"hosts" mapstructure:"hosts"` // 主机列表
|
||||
}
|
||||
|
||||
type MongoHost struct {
|
||||
Host string `json:"host" yaml:"host" mapstructure:"host"` // ip地址
|
||||
Port string `json:"port" yaml:"port" mapstructure:"port"` // 端口
|
||||
}
|
||||
|
||||
// Uri .
|
||||
func (x *Mongo) Uri() string {
|
||||
length := len(x.Hosts)
|
||||
hosts := make([]string, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
if x.Hosts[i].Host != "" && x.Hosts[i].Port != "" {
|
||||
hosts = append(hosts, x.Hosts[i].Host+":"+x.Hosts[i].Port)
|
||||
}
|
||||
}
|
||||
if x.Options != "" {
|
||||
return fmt.Sprintf("mongodb://%s/%s?%s", strings.Join(hosts, ","), x.Database, x.Options)
|
||||
}
|
||||
return fmt.Sprintf("mongodb://%s/%s", strings.Join(hosts, ","), x.Database)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package config
|
||||
|
||||
type CloudflareR2 struct {
|
||||
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
|
||||
BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
|
||||
Path string `mapstructure:"path" json:"path" yaml:"path"`
|
||||
AccountID string `mapstructure:"account-id" json:"account-id" yaml:"account-id"`
|
||||
AccessKeyID string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
|
||||
SecretAccessKey string `mapstructure:"secret-access-key" json:"secret-access-key" yaml:"secret-access-key"`
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
type Minio struct {
|
||||
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
|
||||
AccessKeyId string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
|
||||
AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"`
|
||||
BucketName string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
|
||||
UseSSL bool `mapstructure:"use-ssl" json:"use-ssl" yaml:"use-ssl"`
|
||||
BasePath string `mapstructure:"base-path" json:"base-path" yaml:"base-path"`
|
||||
BucketUrl string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"`
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package config
|
||||
|
||||
type Redis struct {
|
||||
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口
|
||||
Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码
|
||||
DB int `mapstructure:"db" json:"db" yaml:"db"` // redis的哪个数据库
|
||||
Name string `mapstructure:"name" json:"name" yaml:"name"` // 代表当前实例的名字
|
||||
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口
|
||||
Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码
|
||||
DB int `mapstructure:"db" json:"db" yaml:"db"` // 单实例模式下redis的哪个数据库
|
||||
UseCluster bool `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"` // 是否使用集群模式
|
||||
ClusterAddrs []string `mapstructure:"clusterAddrs" json:"clusterAddrs" yaml:"clusterAddrs"` // 集群模式下的节点地址列表
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package config
|
||||
|
||||
type System struct {
|
||||
Env string `mapstructure:"env" json:"env" yaml:"env"` // 环境值
|
||||
DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` // 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql
|
||||
OssType string `mapstructure:"oss-type" json:"oss-type" yaml:"oss-type"` // Oss类型
|
||||
RouterPrefix string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"`
|
||||
Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` // 端口值
|
||||
LimitCountIP int `mapstructure:"iplimit-count" json:"iplimit-count" yaml:"iplimit-count"`
|
||||
LimitTimeIP int `mapstructure:"iplimit-time" json:"iplimit-time" yaml:"iplimit-time"`
|
||||
UseMultipoint bool `mapstructure:"use-multipoint" json:"use-multipoint" yaml:"use-multipoint"` // 多点登录拦截
|
||||
UseRedis bool `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"` // 使用redis
|
||||
UseMultipoint bool `mapstructure:"use-multipoint" json:"use-multipoint" yaml:"use-multipoint"` // 多点登录拦截
|
||||
UseRedis bool `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"` // 使用redis
|
||||
UseMongo bool `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"` // 使用mongo
|
||||
UseStrictAuth bool `mapstructure:"use-strict-auth" json:"use-strict-auth" yaml:"use-strict-auth"` // 使用树形角色分配模式
|
||||
DisableAutoMigrate bool `mapstructure:"disable-auto-migrate" json:"disable-auto-migrate" yaml:"disable-auto-migrate"` // 自动迁移数据库表结构,生产环境建议设为false,手动迁移
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package config
|
||||
|
||||
type Timer struct {
|
||||
Spec string `mapstructure:"spec" json:"spec" yaml:"spec"` // CRON表达式
|
||||
Detail []Detail `mapstructure:"detail" json:"detail" yaml:"detail"`
|
||||
Start bool `mapstructure:"start" json:"start" yaml:"start"` // 是否启用
|
||||
WithSeconds bool `mapstructure:"with_seconds" json:"with_seconds" yaml:"with_seconds"` // 是否精确到秒
|
||||
|
||||
}
|
||||
|
||||
type Detail struct {
|
||||
TableName string `mapstructure:"tableName" json:"tableName" yaml:"tableName"` // 需要清理的表名
|
||||
CompareField string `mapstructure:"compareField" json:"compareField" yaml:"compareField"` // 需要比较时间的字段
|
||||
Interval string `mapstructure:"interval" json:"interval" yaml:"interval"` // 时间间隔
|
||||
}
|
||||
+46
-35
@@ -2,7 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"go.uber.org/zap/zapcore"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Zap struct {
|
||||
@@ -12,49 +12,60 @@ type Zap struct {
|
||||
Director string `mapstructure:"director" json:"director" yaml:"director"` // 日志文件夹
|
||||
EncodeLevel string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"` // 编码级
|
||||
StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` // 栈名
|
||||
|
||||
MaxAge int `mapstructure:"max-age" json:"max-age" yaml:"max-age"` // 日志留存时间
|
||||
ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"` // 显示行
|
||||
LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` // 输出控制台
|
||||
ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"` // 显示行
|
||||
LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` // 输出控制台
|
||||
RetentionDay int `mapstructure:"retention-day" json:"retention-day" yaml:"retention-day"` // 日志保留天数
|
||||
}
|
||||
|
||||
// ZapEncodeLevel 根据 EncodeLevel 返回 zapcore.LevelEncoder
|
||||
// Levels 根据字符串转化为 zapcore.Levels
|
||||
func (c *Zap) Levels() []zapcore.Level {
|
||||
levels := make([]zapcore.Level, 0, 7)
|
||||
level, err := zapcore.ParseLevel(c.Level)
|
||||
if err != nil {
|
||||
level = zapcore.DebugLevel
|
||||
}
|
||||
for ; level <= zapcore.FatalLevel; level++ {
|
||||
levels = append(levels, level)
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
func (c *Zap) Encoder() zapcore.Encoder {
|
||||
config := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
NameKey: "name",
|
||||
LevelKey: "level",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "message",
|
||||
StacktraceKey: c.StacktraceKey,
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||
encoder.AppendString(c.Prefix + t.Format("2006-01-02 15:04:05.000"))
|
||||
},
|
||||
EncodeLevel: c.LevelEncoder(),
|
||||
EncodeCaller: zapcore.FullCallerEncoder,
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
}
|
||||
if c.Format == "json" {
|
||||
return zapcore.NewJSONEncoder(config)
|
||||
}
|
||||
return zapcore.NewConsoleEncoder(config)
|
||||
|
||||
}
|
||||
|
||||
// LevelEncoder 根据 EncodeLevel 返回 zapcore.LevelEncoder
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *Zap) ZapEncodeLevel() zapcore.LevelEncoder {
|
||||
func (c *Zap) LevelEncoder() zapcore.LevelEncoder {
|
||||
switch {
|
||||
case z.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
|
||||
case c.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
|
||||
return zapcore.LowercaseLevelEncoder
|
||||
case z.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
|
||||
case c.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
|
||||
return zapcore.LowercaseColorLevelEncoder
|
||||
case z.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
|
||||
case c.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
|
||||
return zapcore.CapitalLevelEncoder
|
||||
case z.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
|
||||
case c.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
|
||||
return zapcore.CapitalColorLevelEncoder
|
||||
default:
|
||||
return zapcore.LowercaseLevelEncoder
|
||||
}
|
||||
}
|
||||
|
||||
// TransportLevel 根据字符串转化为 zapcore.Level
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *Zap) TransportLevel() zapcore.Level {
|
||||
z.Level = strings.ToLower(z.Level)
|
||||
switch z.Level {
|
||||
case "debug":
|
||||
return zapcore.DebugLevel
|
||||
case "info":
|
||||
return zapcore.InfoLevel
|
||||
case "warn":
|
||||
return zapcore.WarnLevel
|
||||
case "error":
|
||||
return zapcore.WarnLevel
|
||||
case "dpanic":
|
||||
return zapcore.DPanicLevel
|
||||
case "panic":
|
||||
return zapcore.PanicLevel
|
||||
case "fatal":
|
||||
return zapcore.FatalLevel
|
||||
default:
|
||||
return zapcore.DebugLevel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,49 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cutter 实现 io.Writer 接口
|
||||
// 用于日志切割, strings.Join([]string{director,layout, formats..., level+".log"}, os.PathSeparator)
|
||||
type Cutter struct {
|
||||
level string // 日志级别(debug, info, warn, error, dpanic, panic, fatal)
|
||||
format string // 时间格式(2006-01-02)
|
||||
Director string // 日志文件夹
|
||||
file *os.File // 文件句柄
|
||||
mutex *sync.RWMutex // 读写锁
|
||||
level string // 日志级别(debug, info, warn, error, dpanic, panic, fatal)
|
||||
layout string // 时间格式 2006-01-02 15:04:05
|
||||
formats []string // 自定义参数([]string{Director,"2006-01-02", "business"(此参数可不写), level+".log"}
|
||||
director string // 日志文件夹
|
||||
retentionDay int //日志保留天数
|
||||
file *os.File // 文件句柄
|
||||
mutex *sync.RWMutex // 读写锁
|
||||
}
|
||||
|
||||
type CutterOption func(*Cutter)
|
||||
|
||||
// WithCutterFormat 设置时间格式
|
||||
func WithCutterFormat(format string) CutterOption {
|
||||
// CutterWithLayout 时间格式
|
||||
func CutterWithLayout(layout string) CutterOption {
|
||||
return func(c *Cutter) {
|
||||
c.format = format
|
||||
c.layout = layout
|
||||
}
|
||||
}
|
||||
|
||||
func NewCutter(director string, level string, options ...CutterOption) *Cutter {
|
||||
// CutterWithFormats 格式化参数
|
||||
func CutterWithFormats(format ...string) CutterOption {
|
||||
return func(c *Cutter) {
|
||||
if len(format) > 0 {
|
||||
c.formats = format
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewCutter(director string, level string, retentionDay int, options ...CutterOption) *Cutter {
|
||||
rotate := &Cutter{
|
||||
level: level,
|
||||
Director: director,
|
||||
mutex: new(sync.RWMutex),
|
||||
level: level,
|
||||
director: director,
|
||||
retentionDay: retentionDay,
|
||||
mutex: new(sync.RWMutex),
|
||||
}
|
||||
for i := 0; i < len(options); i++ {
|
||||
options[i](rotate)
|
||||
@@ -51,47 +64,62 @@ func (c *Cutter) Write(bytes []byte) (n int, err error) {
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
}()
|
||||
var business string
|
||||
if strings.Contains(string(bytes), "business") {
|
||||
var compile *regexp.Regexp
|
||||
compile, err = regexp.Compile(`{"business": "([^,]+)"}`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if compile.Match(bytes) {
|
||||
finds := compile.FindSubmatch(bytes)
|
||||
business = string(finds[len(finds)-1])
|
||||
bytes = compile.ReplaceAll(bytes, []byte(""))
|
||||
}
|
||||
compile, err = regexp.Compile(`"business": "([^,]+)"`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if compile.Match(bytes) {
|
||||
finds := compile.FindSubmatch(bytes)
|
||||
business = string(finds[len(finds)-1])
|
||||
bytes = compile.ReplaceAll(bytes, []byte(""))
|
||||
}
|
||||
length := len(c.formats)
|
||||
values := make([]string, 0, 3+length)
|
||||
values = append(values, c.director)
|
||||
if c.layout != "" {
|
||||
values = append(values, time.Now().Format(c.layout))
|
||||
}
|
||||
format := time.Now().Format(c.format)
|
||||
formats := make([]string, 0, 4)
|
||||
formats = append(formats, c.Director)
|
||||
if format != "" {
|
||||
formats = append(formats, format)
|
||||
for i := 0; i < length; i++ {
|
||||
values = append(values, c.formats[i])
|
||||
}
|
||||
if business != "" {
|
||||
formats = append(formats, business)
|
||||
}
|
||||
formats = append(formats, c.level+".log")
|
||||
filename := filepath.Join(formats...)
|
||||
dirname := filepath.Dir(filename)
|
||||
err = os.MkdirAll(dirname, 0755)
|
||||
values = append(values, c.level+".log")
|
||||
filename := filepath.Join(values...)
|
||||
director := filepath.Dir(filename)
|
||||
err = os.MkdirAll(director, os.ModePerm)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
err := removeNDaysFolders(c.director, c.retentionDay)
|
||||
if err != nil {
|
||||
fmt.Println("清理过期日志失败", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.file.Write(bytes)
|
||||
}
|
||||
|
||||
func (c *Cutter) Sync() error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
if c.file != nil {
|
||||
return c.file.Sync()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 增加日志目录文件清理 小于等于零的值默认忽略不再处理
|
||||
func removeNDaysFolders(dir string, days int) error {
|
||||
if days <= 0 {
|
||||
return nil
|
||||
}
|
||||
cutoff := time.Now().AddDate(0, 0, -days)
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() && info.ModTime().Before(cutoff) && path != dir {
|
||||
err = os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"os"
|
||||
)
|
||||
|
||||
var FileRotatelogs = new(fileRotatelogs)
|
||||
|
||||
type fileRotatelogs struct{}
|
||||
|
||||
// GetWriteSyncer 获取 zapcore.WriteSyncer
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (r *fileRotatelogs) GetWriteSyncer(level string) zapcore.WriteSyncer {
|
||||
fileWriter := NewCutter(global.GVA_CONFIG.Zap.Director, level, WithCutterFormat("2006-01-02"))
|
||||
if global.GVA_CONFIG.Zap.LogInConsole {
|
||||
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter))
|
||||
}
|
||||
return zapcore.AddSync(fileWriter)
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Zap = new(_zap)
|
||||
|
||||
type _zap struct{}
|
||||
|
||||
// GetEncoder 获取 zapcore.Encoder
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *_zap) GetEncoder() zapcore.Encoder {
|
||||
if global.GVA_CONFIG.Zap.Format == "json" {
|
||||
return zapcore.NewJSONEncoder(z.GetEncoderConfig())
|
||||
}
|
||||
return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
|
||||
}
|
||||
|
||||
// GetEncoderConfig 获取zapcore.EncoderConfig
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
|
||||
return zapcore.EncoderConfig{
|
||||
MessageKey: "message",
|
||||
LevelKey: "level",
|
||||
TimeKey: "time",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
StacktraceKey: global.GVA_CONFIG.Zap.StacktraceKey,
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: global.GVA_CONFIG.Zap.ZapEncodeLevel(),
|
||||
EncodeTime: z.CustomTimeEncoder,
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.FullCallerEncoder,
|
||||
}
|
||||
}
|
||||
|
||||
// GetEncoderCore 获取Encoder的 zapcore.Core
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
|
||||
writer := FileRotatelogs.GetWriteSyncer(l.String()) // 日志分割
|
||||
return zapcore.NewCore(z.GetEncoder(), writer, level)
|
||||
}
|
||||
|
||||
// CustomTimeEncoder 自定义日志输出时间格式
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *_zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||
encoder.AppendString(global.GVA_CONFIG.Zap.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
|
||||
}
|
||||
|
||||
// GetZapCores 根据配置文件的Level获取 []zapcore.Core
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *_zap) GetZapCores() []zapcore.Core {
|
||||
cores := make([]zapcore.Core, 0, 7)
|
||||
for level := global.GVA_CONFIG.Zap.TransportLevel(); level <= zapcore.FatalLevel; level++ {
|
||||
cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
|
||||
}
|
||||
return cores
|
||||
}
|
||||
|
||||
// GetLevelPriority 根据 zapcore.Level 获取 zap.LevelEnablerFunc
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
|
||||
switch level {
|
||||
case zapcore.DebugLevel:
|
||||
return func(level zapcore.Level) bool { // 调试级别
|
||||
return level == zap.DebugLevel
|
||||
}
|
||||
case zapcore.InfoLevel:
|
||||
return func(level zapcore.Level) bool { // 日志级别
|
||||
return level == zap.InfoLevel
|
||||
}
|
||||
case zapcore.WarnLevel:
|
||||
return func(level zapcore.Level) bool { // 警告级别
|
||||
return level == zap.WarnLevel
|
||||
}
|
||||
case zapcore.ErrorLevel:
|
||||
return func(level zapcore.Level) bool { // 错误级别
|
||||
return level == zap.ErrorLevel
|
||||
}
|
||||
case zapcore.DPanicLevel:
|
||||
return func(level zapcore.Level) bool { // dpanic级别
|
||||
return level == zap.DPanicLevel
|
||||
}
|
||||
case zapcore.PanicLevel:
|
||||
return func(level zapcore.Level) bool { // panic级别
|
||||
return level == zap.PanicLevel
|
||||
}
|
||||
case zapcore.FatalLevel:
|
||||
return func(level zapcore.Level) bool { // 终止级别
|
||||
return level == zap.FatalLevel
|
||||
}
|
||||
default:
|
||||
return func(level zapcore.Level) bool { // 调试级别
|
||||
return level == zap.DebugLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
astutil "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils/stacktrace"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ZapCore struct {
|
||||
level zapcore.Level
|
||||
zapcore.Core
|
||||
}
|
||||
|
||||
func NewZapCore(level zapcore.Level) *ZapCore {
|
||||
entity := &ZapCore{level: level}
|
||||
syncer := entity.WriteSyncer()
|
||||
levelEnabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
|
||||
return l == level
|
||||
})
|
||||
entity.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, levelEnabler)
|
||||
return entity
|
||||
}
|
||||
|
||||
func (z *ZapCore) WriteSyncer(formats ...string) zapcore.WriteSyncer {
|
||||
cutter := NewCutter(
|
||||
global.GVA_CONFIG.Zap.Director,
|
||||
z.level.String(),
|
||||
global.GVA_CONFIG.Zap.RetentionDay,
|
||||
CutterWithLayout(time.DateOnly),
|
||||
CutterWithFormats(formats...),
|
||||
)
|
||||
if global.GVA_CONFIG.Zap.LogInConsole {
|
||||
multiSyncer := zapcore.NewMultiWriteSyncer(os.Stdout, cutter)
|
||||
return zapcore.AddSync(multiSyncer)
|
||||
}
|
||||
return zapcore.AddSync(cutter)
|
||||
}
|
||||
|
||||
func (z *ZapCore) Enabled(level zapcore.Level) bool {
|
||||
return z.level == level
|
||||
}
|
||||
|
||||
func (z *ZapCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
return z.Core.With(fields)
|
||||
}
|
||||
|
||||
func (z *ZapCore) Check(entry zapcore.Entry, check *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if z.Enabled(entry.Level) {
|
||||
return check.AddCore(entry, z)
|
||||
}
|
||||
return check
|
||||
}
|
||||
|
||||
func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
for i := 0; i < len(fields); i++ {
|
||||
if fields[i].Key == "business" || fields[i].Key == "folder" || fields[i].Key == "directory" {
|
||||
syncer := z.WriteSyncer(fields[i].String)
|
||||
z.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level)
|
||||
}
|
||||
}
|
||||
// 先写入原日志目标
|
||||
err := z.Core.Write(entry, fields)
|
||||
|
||||
// 捕捉 Error 及以上级别日志并入库,且可提取 zap.Error(err) 的错误内容
|
||||
if entry.Level >= zapcore.ErrorLevel {
|
||||
// 避免与 GORM zap 写入互相递归:跳过由 gorm logger writer 触发的日志
|
||||
if strings.Contains(entry.Caller.File, "gorm_logger_writer.go") {
|
||||
return err
|
||||
}
|
||||
|
||||
form := "后端"
|
||||
level := entry.Level.String()
|
||||
// 生成基础信息
|
||||
info := entry.Message
|
||||
|
||||
// 提取 zap.Error(err) 内容
|
||||
var errStr string
|
||||
for i := 0; i < len(fields); i++ {
|
||||
f := fields[i]
|
||||
if f.Type == zapcore.ErrorType || f.Key == "error" || f.Key == "err" {
|
||||
if f.Interface != nil {
|
||||
errStr = fmt.Sprintf("%v", f.Interface)
|
||||
} else if f.String != "" {
|
||||
errStr = f.String
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if errStr != "" {
|
||||
info = fmt.Sprintf("%s | 错误: %s", info, errStr)
|
||||
}
|
||||
|
||||
// 附加来源与堆栈信息
|
||||
if entry.Caller.File != "" {
|
||||
info = fmt.Sprintf("%s \n 源文件:%s:%d", info, entry.Caller.File, entry.Caller.Line)
|
||||
}
|
||||
stack := entry.Stack
|
||||
if stack != "" {
|
||||
info = fmt.Sprintf("%s \n 调用栈:%s", info, stack)
|
||||
// 解析最终业务调用方,并提取其方法源码
|
||||
if frame, ok := stacktrace.FindFinalCaller(stack); ok {
|
||||
fnName, fnSrc, sLine, eLine, exErr := astutil.ExtractFuncSourceByPosition(frame.File, frame.Line)
|
||||
if exErr == nil {
|
||||
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s lines %d-%d)\n----- 产生日志的方法代码如下 -----\n%s", info, frame.File, frame.Line, fnName, sLine, eLine, fnSrc)
|
||||
} else {
|
||||
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s) | extract_err=%v", info, frame.File, frame.Line, fnName, exErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用后台上下文,避免依赖 gin.Context
|
||||
ctx := context.Background()
|
||||
_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(ctx, &system.SysError{
|
||||
Form: &form,
|
||||
Info: &info,
|
||||
Level: level,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (z *ZapCore) Sync() error {
|
||||
return z.Core.Sync()
|
||||
}
|
||||
+24
-19
@@ -2,48 +2,53 @@ package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/initialize"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type server interface {
|
||||
ListenAndServe() error
|
||||
}
|
||||
|
||||
func RunWindowsServer() {
|
||||
if global.GVA_CONFIG.System.UseMultipoint || global.GVA_CONFIG.System.UseRedis {
|
||||
func RunServer() {
|
||||
if global.GVA_CONFIG.System.UseRedis {
|
||||
// 初始化redis服务
|
||||
initialize.Redis()
|
||||
if global.GVA_CONFIG.System.UseMultipoint {
|
||||
initialize.RedisList()
|
||||
}
|
||||
}
|
||||
|
||||
if global.GVA_CONFIG.System.UseMongo {
|
||||
err := initialize.Mongo.Initialization()
|
||||
if err != nil {
|
||||
zap.L().Error(fmt.Sprintf("%+v", err))
|
||||
}
|
||||
}
|
||||
// 从db加载jwt数据
|
||||
if global.GVA_DB != nil {
|
||||
system.LoadAll()
|
||||
}
|
||||
|
||||
Router := initialize.Routers()
|
||||
Router.Static("/form-generator", "./resource/page")
|
||||
|
||||
address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr)
|
||||
s := initServer(address, Router)
|
||||
// 保证文本顺序输出
|
||||
// In order to ensure that the text order output can be deleted
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
global.GVA_LOG.Info("server run success on ", zap.String("address", address))
|
||||
|
||||
fmt.Printf(`
|
||||
欢迎使用 gin-vue-admin
|
||||
当前版本:v2.5.7
|
||||
加群方式:微信号:shouzi_1994 QQ群:622360840
|
||||
当前版本:%s
|
||||
加群方式:微信号:shouzi_1994 QQ群:470239250
|
||||
项目地址:https://github.com/flipped-aurora/gin-vue-admin
|
||||
插件市场:https://plugin.gin-vue-admin.com
|
||||
GVA讨论社区:https://support.qq.com/products/371961
|
||||
默认自动化文档地址:http://127.0.0.1%s/swagger/index.html
|
||||
默认MCP SSE地址:http://127.0.0.1%s%s
|
||||
默认MCP Message地址:http://127.0.0.1%s%s
|
||||
默认前端文件运行地址:http://127.0.0.1:8080
|
||||
如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/coffee/index.html
|
||||
`, address)
|
||||
global.GVA_LOG.Error(s.ListenAndServe().Error())
|
||||
--------------------------------------版权声明--------------------------------------
|
||||
** 版权所有方:flipped-aurora开源团队 **
|
||||
** 版权持有公司:北京翻转极光科技有限责任公司 **
|
||||
** 剔除授权标识需购买商用授权:https://plugin.gin-vue-admin.com/license **
|
||||
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
|
||||
`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
|
||||
initServer(address, Router, 10*time.Minute, 10*time.Minute)
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/fvbock/endless"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func initServer(address string, router *gin.Engine) server {
|
||||
s := endless.NewServer(address, router)
|
||||
s.ReadHeaderTimeout = 20 * time.Second
|
||||
s.WriteTimeout = 20 * time.Second
|
||||
s.MaxHeaderBytes = 1 << 20
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type server interface {
|
||||
ListenAndServe() error
|
||||
Shutdown(context.Context) error
|
||||
}
|
||||
|
||||
// initServer 启动服务并实现优雅关闭
|
||||
func initServer(address string, router *gin.Engine, readTimeout, writeTimeout time.Duration) {
|
||||
// 创建服务
|
||||
srv := &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
ReadTimeout: readTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
// 在goroutine中启动服务
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
fmt.Printf("listen: %s\n", err)
|
||||
zap.L().Error("server启动失败", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待中断信号以优雅地关闭服务器
|
||||
quit := make(chan os.Signal, 1)
|
||||
// kill (无参数) 默认发送 syscall.SIGTERM
|
||||
// kill -2 发送 syscall.SIGINT
|
||||
// kill -9 发送 syscall.SIGKILL,但是无法被捕获,所以不需要添加
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
zap.L().Info("关闭WEB服务...")
|
||||
|
||||
// 设置5秒的超时时间
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
zap.L().Fatal("WEB服务关闭异常", zap.Error(err))
|
||||
}
|
||||
|
||||
zap.L().Info("WEB服务已关闭")
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func initServer(address string, router *gin.Engine) server {
|
||||
return &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
ReadTimeout: 20 * time.Second,
|
||||
WriteTimeout: 20 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
}
|
||||
+43
-41
@@ -3,58 +3,26 @@ package core
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
_ "github.com/flipped-aurora/gin-vue-admin/server/packfile"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Viper //
|
||||
// 优先级: 命令行 > 环境变量 > 默认值
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func Viper(path ...string) *viper.Viper {
|
||||
var config string
|
||||
|
||||
if len(path) == 0 {
|
||||
flag.StringVar(&config, "c", "", "choose config file.")
|
||||
flag.Parse()
|
||||
if config == "" { // 判断命令行参数是否为空
|
||||
if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空
|
||||
switch gin.Mode() {
|
||||
case gin.DebugMode:
|
||||
config = internal.ConfigDefaultFile
|
||||
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, internal.ConfigDefaultFile)
|
||||
case gin.ReleaseMode:
|
||||
config = internal.ConfigReleaseFile
|
||||
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, internal.ConfigReleaseFile)
|
||||
case gin.TestMode:
|
||||
config = internal.ConfigTestFile
|
||||
fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.EnvGinMode, internal.ConfigTestFile)
|
||||
}
|
||||
} else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config
|
||||
config = configEnv
|
||||
fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", internal.ConfigEnv, config)
|
||||
}
|
||||
} else { // 命令行参数不为空 将值赋值于config
|
||||
fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config)
|
||||
}
|
||||
} else { // 函数传递的可变参数的第一个值赋值于config
|
||||
config = path[0]
|
||||
fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config)
|
||||
}
|
||||
// Viper 配置
|
||||
func Viper() *viper.Viper {
|
||||
config := getConfigPath()
|
||||
|
||||
v := viper.New()
|
||||
v.SetConfigFile(config)
|
||||
v.SetConfigType("yaml")
|
||||
err := v.ReadInConfig()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
||||
panic(fmt.Errorf("fatal error config file: %w", err))
|
||||
}
|
||||
v.WatchConfig()
|
||||
|
||||
@@ -65,10 +33,44 @@ func Viper(path ...string) *viper.Viper {
|
||||
}
|
||||
})
|
||||
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
|
||||
fmt.Println(err)
|
||||
panic(fmt.Errorf("fatal error unmarshal config: %w", err))
|
||||
}
|
||||
|
||||
// root 适配性 根据root位置去找到对应迁移位置,保证root路径有效
|
||||
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
|
||||
return v
|
||||
}
|
||||
|
||||
// getConfigPath 获取配置文件路径, 优先级: 命令行 > 环境变量 > 默认值
|
||||
func getConfigPath() (config string) {
|
||||
// `-c` flag parse
|
||||
flag.StringVar(&config, "c", "", "choose config file.")
|
||||
flag.Parse()
|
||||
if config != "" { // 命令行参数不为空 将值赋值于config
|
||||
fmt.Printf("您正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\n", config)
|
||||
return
|
||||
}
|
||||
if env := os.Getenv(internal.ConfigEnv); env != "" { // 判断环境变量 GVA_CONFIG
|
||||
config = env
|
||||
fmt.Printf("您正在使用 %s 环境变量, config 的路径为 %s\n", internal.ConfigEnv, config)
|
||||
return
|
||||
}
|
||||
|
||||
switch gin.Mode() { // 根据 gin 模式文件名
|
||||
case gin.DebugMode:
|
||||
config = internal.ConfigDebugFile
|
||||
case gin.ReleaseMode:
|
||||
config = internal.ConfigReleaseFile
|
||||
case gin.TestMode:
|
||||
config = internal.ConfigTestFile
|
||||
}
|
||||
fmt.Printf("您正在使用 gin 的 %s 模式运行, config 的路径为 %s\n", gin.Mode(), config)
|
||||
|
||||
_, err := os.Stat(config)
|
||||
if err != nil || os.IsNotExist(err) {
|
||||
config = internal.ConfigDefaultFile
|
||||
fmt.Printf("配置文件路径不存在, 使用默认配置文件路径: %s\n", config)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+21
-13
@@ -1,13 +1,13 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"os"
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Zap 获取 zap.Logger
|
||||
@@ -17,12 +17,20 @@ func Zap() (logger *zap.Logger) {
|
||||
fmt.Printf("create %v directory\n", global.GVA_CONFIG.Zap.Director)
|
||||
_ = os.Mkdir(global.GVA_CONFIG.Zap.Director, os.ModePerm)
|
||||
}
|
||||
|
||||
cores := internal.Zap.GetZapCores()
|
||||
logger = zap.New(zapcore.NewTee(cores...))
|
||||
|
||||
if global.GVA_CONFIG.Zap.ShowLine {
|
||||
logger = logger.WithOptions(zap.AddCaller())
|
||||
levels := global.GVA_CONFIG.Zap.Levels()
|
||||
length := len(levels)
|
||||
cores := make([]zapcore.Core, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
core := internal.NewZapCore(levels[i])
|
||||
cores = append(cores, core)
|
||||
}
|
||||
// 构建基础 logger(错误级别的入库逻辑已在自定义 ZapCore 中处理)
|
||||
logger = zap.New(zapcore.NewTee(cores...))
|
||||
// 启用 Error 及以上级别的堆栈捕捉,确保 entry.Stack 可用
|
||||
opts := []zap.Option{zap.AddStacktrace(zapcore.ErrorLevel)}
|
||||
if global.GVA_CONFIG.Zap.ShowLine {
|
||||
opts = append(opts, zap.AddCaller())
|
||||
}
|
||||
logger = logger.WithOptions(opts...)
|
||||
return logger
|
||||
}
|
||||
|
||||
+2905
-473
File diff suppressed because it is too large
Load Diff
+2901
-531
File diff suppressed because it is too large
Load Diff
+1834
-353
File diff suppressed because it is too large
Load Diff
+25
-8
@@ -1,8 +1,13 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/qiniu/qmgo"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils/timer"
|
||||
"github.com/songzhibin97/gkit/cache/local_cache"
|
||||
|
||||
@@ -18,18 +23,22 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
GVA_DB *gorm.DB
|
||||
GVA_DBList map[string]*gorm.DB
|
||||
GVA_REDIS *redis.Client
|
||||
GVA_CONFIG config.Server
|
||||
GVA_VP *viper.Viper
|
||||
GVA_DB *gorm.DB
|
||||
GVA_DBList map[string]*gorm.DB
|
||||
GVA_REDIS redis.UniversalClient
|
||||
GVA_REDISList map[string]redis.UniversalClient
|
||||
GVA_MONGO *qmgo.QmgoClient
|
||||
GVA_CONFIG config.Server
|
||||
GVA_VP *viper.Viper
|
||||
// GVA_LOG *oplogging.Logger
|
||||
GVA_LOG *zap.Logger
|
||||
GVA_Timer timer.Timer = timer.NewTimerTask()
|
||||
GVA_Concurrency_Control = &singleflight.Group{}
|
||||
|
||||
BlackCache local_cache.Cache
|
||||
lock sync.RWMutex
|
||||
GVA_ROUTERS gin.RoutesInfo
|
||||
GVA_ACTIVE_DBNAME *string
|
||||
GVA_MCP_SERVER *server.MCPServer
|
||||
BlackCache local_cache.Cache
|
||||
lock sync.RWMutex
|
||||
)
|
||||
|
||||
// GetGlobalDBByDBName 通过名称获取db list中的db
|
||||
@@ -49,3 +58,11 @@ func MustGetGlobalDBByDBName(dbname string) *gorm.DB {
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func GetRedis(name string) redis.UniversalClient {
|
||||
redis, ok := GVA_REDISList[name]
|
||||
if !ok || redis == nil {
|
||||
panic(fmt.Sprintf("redis `%s` no init", name))
|
||||
}
|
||||
return redis
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type GVA_MODEL struct {
|
||||
ID uint `gorm:"primarykey"` // 主键ID
|
||||
ID uint `gorm:"primarykey" json:"ID"` // 主键ID
|
||||
CreatedAt time.Time // 创建时间
|
||||
UpdatedAt time.Time // 更新时间
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package global
|
||||
|
||||
// Version 版本信息
|
||||
// 目前只有Version正式使用 其余为预留
|
||||
const (
|
||||
// Version 当前版本号
|
||||
Version = "v2.9.0"
|
||||
// AppName 应用名称
|
||||
AppName = "Gin-Vue-Admin"
|
||||
// Description 应用描述
|
||||
Description = "使用gin+vue进行极速开发的全栈开发基础平台"
|
||||
)
|
||||
+171
-93
@@ -1,129 +1,207 @@
|
||||
module github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
go 1.18
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
|
||||
github.com/aws/aws-sdk-go v1.44.307
|
||||
github.com/casbin/casbin/v2 v2.71.1
|
||||
github.com/casbin/gorm-adapter/v3 v3.18.0
|
||||
github.com/flipped-aurora/ws v1.0.2
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/glebarez/sqlite v1.8.0
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1
|
||||
github.com/casbin/casbin/v2 v2.103.0
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0
|
||||
github.com/dzwvip/gorm-oracle v0.1.2
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/goccy/go-json v0.10.4
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible
|
||||
github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/otiai10/copy v1.7.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/mark3labs/mcp-go v0.41.1
|
||||
github.com/mholt/archives v0.1.1
|
||||
github.com/minio/minio-go/v7 v7.0.84
|
||||
github.com/mojocn/base64Captcha v1.3.8
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/qiniu/api.v7/v7 v7.4.1
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
github.com/qiniu/go-sdk/v7 v7.25.2
|
||||
github.com/qiniu/qmgo v1.1.9
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sashabaranov/go-openai v1.14.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.6
|
||||
github.com/songzhibin97/gkit v1.2.11
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/songzhibin97/gkit v1.2.13
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.1
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.42
|
||||
github.com/unrolled/secure v1.13.0
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/text v0.11.0
|
||||
gorm.io/driver/mysql v1.5.1
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
gorm.io/driver/sqlserver v1.5.1
|
||||
gorm.io/gorm v1.25.2
|
||||
nhooyr.io/websocket v1.8.7
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60
|
||||
github.com/unrolled/secure v1.17.0
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
go.mongodb.org/mongo-driver v1.17.2
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/text v0.24.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/datatypes v1.2.5
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlserver v1.5.4
|
||||
gorm.io/gen v0.3.26
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/STARRY-S/zip v0.2.1 // indirect
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
||||
github.com/aws/smithy-go v1.24.1 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.0 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.7 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/casbin/govaluate v1.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.2 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.8.0 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minlz v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.4.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/sijms/go-ora/v2 v2.7.17 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.5 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/thoas/go-funk v0.7.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 // indirect
|
||||
github.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.13.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/plugin/dbresolver v1.4.1 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/sqlite v1.23.0 // indirect
|
||||
gorm.io/hints v1.1.2 // indirect
|
||||
gorm.io/plugin/dbresolver v1.5.3 // indirect
|
||||
modernc.org/fileutil v1.3.0 // indirect
|
||||
modernc.org/libc v1.61.9 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
modernc.org/sqlite v1.34.5 // indirect
|
||||
)
|
||||
|
||||
+498
-462
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import (
|
||||
adapter "github.com/casbin/gorm-adapter/v3"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
|
||||
sysModel "github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -18,7 +19,7 @@ func init() {
|
||||
system.RegisterInit(initOrderEnsureTables, &ensureTables{})
|
||||
}
|
||||
|
||||
func (ensureTables) InitializerName() string {
|
||||
func (e *ensureTables) InitializerName() string {
|
||||
return "ensure_tables_created"
|
||||
}
|
||||
func (e *ensureTables) InitializeData(ctx context.Context) (next context.Context, err error) {
|
||||
@@ -47,15 +48,24 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
|
||||
sysModel.SysBaseMenuParameter{},
|
||||
sysModel.SysBaseMenuBtn{},
|
||||
sysModel.SysAuthorityBtn{},
|
||||
sysModel.SysAutoCode{},
|
||||
sysModel.SysChatGptOption{},
|
||||
|
||||
sysModel.SysAutoCodePackage{},
|
||||
sysModel.SysExportTemplate{},
|
||||
sysModel.Condition{},
|
||||
sysModel.JoinTemplate{},
|
||||
sysModel.SysParams{},
|
||||
sysModel.SysVersion{},
|
||||
sysModel.SysError{},
|
||||
sysModel.SysLoginLog{},
|
||||
sysModel.SysApiToken{},
|
||||
adapter.CasbinRule{},
|
||||
|
||||
example.ExaFile{},
|
||||
example.ExaCustomer{},
|
||||
example.ExaFileChunk{},
|
||||
example.ExaFileUploadAndDownload{},
|
||||
example.ExaAttachmentCategory{},
|
||||
|
||||
model.Info{},
|
||||
}
|
||||
for _, t := range tables {
|
||||
_ = db.AutoMigrate(&t)
|
||||
@@ -83,14 +93,20 @@ func (e *ensureTables) TableCreated(ctx context.Context) bool {
|
||||
sysModel.SysBaseMenuParameter{},
|
||||
sysModel.SysBaseMenuBtn{},
|
||||
sysModel.SysAuthorityBtn{},
|
||||
sysModel.SysAutoCode{},
|
||||
sysModel.SysChatGptOption{},
|
||||
sysModel.SysAutoCodePackage{},
|
||||
sysModel.SysExportTemplate{},
|
||||
sysModel.Condition{},
|
||||
sysModel.JoinTemplate{},
|
||||
|
||||
adapter.CasbinRule{},
|
||||
|
||||
example.ExaFile{},
|
||||
example.ExaCustomer{},
|
||||
example.ExaFileChunk{},
|
||||
example.ExaFileUploadAndDownload{},
|
||||
example.ExaAttachmentCategory{},
|
||||
|
||||
model.Info{},
|
||||
}
|
||||
yes := true
|
||||
for _, t := range tables {
|
||||
|
||||
@@ -11,32 +11,40 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Gorm 初始化数据库并产生数据库全局变量
|
||||
// Author SliverHorn
|
||||
func Gorm() *gorm.DB {
|
||||
switch global.GVA_CONFIG.System.DbType {
|
||||
case "mysql":
|
||||
global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mysql.Dbname
|
||||
return GormMysql()
|
||||
case "pgsql":
|
||||
global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Pgsql.Dbname
|
||||
return GormPgSql()
|
||||
case "oracle":
|
||||
global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Oracle.Dbname
|
||||
return GormOracle()
|
||||
case "mssql":
|
||||
global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mssql.Dbname
|
||||
return GormMssql()
|
||||
case "sqlite":
|
||||
global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Sqlite.Dbname
|
||||
return GormSqlite()
|
||||
default:
|
||||
global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mysql.Dbname
|
||||
return GormMysql()
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterTables 注册数据库表专用
|
||||
// Author SliverHorn
|
||||
func RegisterTables() {
|
||||
if global.GVA_CONFIG.System.DisableAutoMigrate {
|
||||
global.GVA_LOG.Info("auto-migrate is disabled, skipping table registration")
|
||||
return
|
||||
}
|
||||
|
||||
db := global.GVA_DB
|
||||
err := db.AutoMigrate(
|
||||
// 系统模块表
|
||||
|
||||
system.SysApi{},
|
||||
system.SysIgnoreApi{},
|
||||
system.SysUser{},
|
||||
system.SysBaseMenu{},
|
||||
system.JwtBlacklist{},
|
||||
@@ -48,17 +56,32 @@ func RegisterTables() {
|
||||
system.SysBaseMenuParameter{},
|
||||
system.SysBaseMenuBtn{},
|
||||
system.SysAuthorityBtn{},
|
||||
system.SysAutoCode{},
|
||||
system.SysChatGptOption{},
|
||||
system.SysAutoCodePackage{},
|
||||
system.SysExportTemplate{},
|
||||
system.Condition{},
|
||||
system.JoinTemplate{},
|
||||
system.SysParams{},
|
||||
system.SysVersion{},
|
||||
system.SysError{},
|
||||
system.SysApiToken{},
|
||||
system.SysLoginLog{},
|
||||
|
||||
example.ExaFile{},
|
||||
example.ExaCustomer{},
|
||||
example.ExaFileChunk{},
|
||||
example.ExaFileUploadAndDownload{},
|
||||
example.ExaAttachmentCategory{},
|
||||
)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("register table failed", zap.Error(err))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err = bizModel()
|
||||
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("register biz_table failed", zap.Error(err))
|
||||
os.Exit(0)
|
||||
}
|
||||
global.GVA_LOG.Info("register table success")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
)
|
||||
|
||||
func bizModel() error {
|
||||
db := global.GVA_DB
|
||||
err := db.AutoMigrate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
package initialize
|
||||
|
||||
/*
|
||||
* @Author: 逆光飞翔 191180776@qq.com
|
||||
* @Date: 2022-12-08 17:25:49
|
||||
@@ -6,7 +8,6 @@
|
||||
* @FilePath: \server\initialize\gorm_mssql.go
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
@@ -27,7 +28,9 @@ func GormMssql() *gorm.DB {
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
|
||||
@@ -44,10 +47,12 @@ func GormMssqlByConfig(m config.Mssql) *gorm.DB {
|
||||
return nil
|
||||
}
|
||||
mssqlConfig := sqlserver.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
|
||||
|
||||
@@ -12,18 +12,32 @@ import (
|
||||
// GormMysql 初始化Mysql数据库
|
||||
// Author [piexlmax](https://github.com/piexlmax)
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
// Author [ByteZhou-2018](https://github.com/ByteZhou-2018)
|
||||
func GormMysql() *gorm.DB {
|
||||
m := global.GVA_CONFIG.Mysql
|
||||
return initMysqlDatabase(m)
|
||||
}
|
||||
|
||||
// GormMysqlByConfig 通过传入配置初始化Mysql数据库
|
||||
func GormMysqlByConfig(m config.Mysql) *gorm.DB {
|
||||
return initMysqlDatabase(m)
|
||||
}
|
||||
|
||||
// initMysqlDatabase 初始化Mysql数据库的辅助函数
|
||||
func initMysqlDatabase(m config.Mysql) *gorm.DB {
|
||||
if m.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mysqlConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
return nil
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
|
||||
sqlDB, _ := db.DB()
|
||||
@@ -32,24 +46,3 @@ func GormMysql() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
// GormMysqlByConfig 初始化Mysql数据库用过传入配置
|
||||
func GormMysqlByConfig(m config.Mysql) *gorm.DB {
|
||||
if m.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
mysqlConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,32 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
//"github.com/dzwvip/oracle"
|
||||
oracle "github.com/dzwvip/gorm-oracle"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
|
||||
|
||||
//_ "github.com/godror/godror"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormOracle 初始化oracle数据库
|
||||
// 如果需要Oracle库 放开import里的注释 把下方 mysql.Config 改为 oracle.Config ; mysql.New 改为 oracle.New
|
||||
func GormOracle() *gorm.DB {
|
||||
m := global.GVA_CONFIG.Oracle
|
||||
if m.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
oracleConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
|
||||
return db
|
||||
}
|
||||
return initOracleDatabase(m)
|
||||
}
|
||||
|
||||
// GormOracleByConfig 初始化Oracle数据库用过传入配置
|
||||
func GormOracleByConfig(m config.Oracle) *gorm.DB {
|
||||
return initOracleDatabase(m)
|
||||
}
|
||||
|
||||
// initOracleDatabase 初始化Oracle数据库的辅助函数
|
||||
func initOracleDatabase(m config.Oracle) *gorm.DB {
|
||||
if m.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
oracleConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(oracle.Open(m.Dsn()), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -13,25 +13,16 @@ import (
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func GormPgSql() *gorm.DB {
|
||||
p := global.GVA_CONFIG.Pgsql
|
||||
if p.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
pgsqlConfig := postgres.Config{
|
||||
DSN: p.Dsn(), // DSN data source name
|
||||
PreferSimpleProtocol: false,
|
||||
}
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(p.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(p.MaxOpenConns)
|
||||
return db
|
||||
}
|
||||
return initPgSqlDatabase(p)
|
||||
}
|
||||
|
||||
// GormPgSqlByConfig 初始化 Postgresql 数据库 通过参数
|
||||
// GormPgSqlByConfig 初始化 Postgresql 数据库 通过指定参数
|
||||
func GormPgSqlByConfig(p config.Pgsql) *gorm.DB {
|
||||
return initPgSqlDatabase(p)
|
||||
}
|
||||
|
||||
// initPgSqlDatabase 初始化 Postgresql 数据库的辅助函数
|
||||
func initPgSqlDatabase(p config.Pgsql) *gorm.DB {
|
||||
if p.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -39,7 +30,9 @@ func GormPgSqlByConfig(p config.Pgsql) *gorm.DB {
|
||||
DSN: p.Dsn(), // DSN data source name
|
||||
PreferSimpleProtocol: false,
|
||||
}
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := p.GeneralDB
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -11,27 +11,23 @@ import (
|
||||
// GormSqlite 初始化Sqlite数据库
|
||||
func GormSqlite() *gorm.DB {
|
||||
s := global.GVA_CONFIG.Sqlite
|
||||
if s.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(s.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(s.MaxOpenConns)
|
||||
return db
|
||||
}
|
||||
return initSqliteDatabase(s)
|
||||
}
|
||||
|
||||
// GormSqliteByConfig 初始化Sqlite数据库用过传入配置
|
||||
func GormSqliteByConfig(s config.Sqlite) *gorm.DB {
|
||||
return initSqliteDatabase(s)
|
||||
}
|
||||
|
||||
// initSqliteDatabase 初始化Sqlite数据库辅助函数
|
||||
func initSqliteDatabase(s config.Sqlite) *gorm.DB {
|
||||
if s.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := s.GeneralDB
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 假设这是初始化逻辑的一部分
|
||||
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
)
|
||||
|
||||
// 初始化全局函数
|
||||
func SetupHandlers() {
|
||||
// 注册系统重载处理函数
|
||||
utils.GlobalSystemEvents.RegisterReloadHandler(func() error {
|
||||
return Reload()
|
||||
})
|
||||
}
|
||||
@@ -1,62 +1,31 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"gorm.io/gorm/schema"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
type DBBASE interface {
|
||||
GetLogMode() string
|
||||
}
|
||||
|
||||
var Gorm = new(_gorm)
|
||||
|
||||
type _gorm struct{}
|
||||
|
||||
// Config gorm 自定义配置
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (g *_gorm) Config(prefix string, singular bool) *gorm.Config {
|
||||
config := &gorm.Config{
|
||||
func (g *_gorm) Config(general config.GeneralDB) *gorm.Config {
|
||||
return &gorm.Config{
|
||||
Logger: logger.New(NewWriter(general), logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond,
|
||||
LogLevel: general.LogLevel(),
|
||||
Colorful: true,
|
||||
}),
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: prefix,
|
||||
SingularTable: singular,
|
||||
TablePrefix: general.Prefix,
|
||||
SingularTable: general.Singular,
|
||||
},
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
}
|
||||
_default := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond,
|
||||
LogLevel: logger.Warn,
|
||||
Colorful: true,
|
||||
})
|
||||
var logMode DBBASE
|
||||
switch global.GVA_CONFIG.System.DbType {
|
||||
case "mysql":
|
||||
logMode = &global.GVA_CONFIG.Mysql
|
||||
case "pgsql":
|
||||
logMode = &global.GVA_CONFIG.Pgsql
|
||||
case "oracle":
|
||||
logMode = &global.GVA_CONFIG.Oracle
|
||||
default:
|
||||
logMode = &global.GVA_CONFIG.Mysql
|
||||
}
|
||||
|
||||
switch logMode.GetLogMode() {
|
||||
case "silent", "Silent":
|
||||
config.Logger = _default.LogMode(logger.Silent)
|
||||
case "error", "Error":
|
||||
config.Logger = _default.LogMode(logger.Error)
|
||||
case "warn", "Warn":
|
||||
config.Logger = _default.LogMode(logger.Warn)
|
||||
case "info", "Info":
|
||||
config.Logger = _default.LogMode(logger.Info)
|
||||
default:
|
||||
config.Logger = _default.LogMode(logger.Info)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
config config.GeneralDB
|
||||
writer logger.Writer
|
||||
}
|
||||
|
||||
func NewWriter(config config.GeneralDB) *Writer {
|
||||
return &Writer{config: config}
|
||||
}
|
||||
|
||||
// Printf 格式化打印日志
|
||||
func (c *Writer) Printf(message string, data ...any) {
|
||||
|
||||
// 当有日志时候均需要输出到控制台
|
||||
fmt.Printf(message, data...)
|
||||
|
||||
// 当开启了zap的情况,会打印到日志记录
|
||||
if c.config.LogZap {
|
||||
switch c.config.LogLevel() {
|
||||
case logger.Silent:
|
||||
global.GVA_LOG.Debug(fmt.Sprintf(message, data...))
|
||||
case logger.Error:
|
||||
global.GVA_LOG.Error(fmt.Sprintf(message, data...))
|
||||
case logger.Warn:
|
||||
global.GVA_LOG.Warn(fmt.Sprintf(message, data...))
|
||||
case logger.Info:
|
||||
global.GVA_LOG.Info(fmt.Sprintf(message, data...))
|
||||
default:
|
||||
global.GVA_LOG.Info(fmt.Sprintf(message, data...))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type writer struct {
|
||||
logger.Writer
|
||||
}
|
||||
|
||||
// NewWriter writer 构造函数
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func NewWriter(w logger.Writer) *writer {
|
||||
return &writer{Writer: w}
|
||||
}
|
||||
|
||||
// Printf 格式化打印日志
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (w *writer) Printf(message string, data ...interface{}) {
|
||||
var logZap bool
|
||||
switch global.GVA_CONFIG.System.DbType {
|
||||
case "mysql":
|
||||
logZap = global.GVA_CONFIG.Mysql.LogZap
|
||||
case "pgsql":
|
||||
logZap = global.GVA_CONFIG.Pgsql.LogZap
|
||||
}
|
||||
if logZap {
|
||||
global.GVA_LOG.Info(fmt.Sprintf(message+"\n", data...))
|
||||
} else {
|
||||
w.Writer.Printf(message, data...)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/qiniu/qmgo/options"
|
||||
"go.mongodb.org/mongo-driver/event"
|
||||
opt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var Mongo = new(mongo)
|
||||
|
||||
type mongo struct{}
|
||||
|
||||
func (m *mongo) GetClientOptions() []options.ClientOptions {
|
||||
cmdMonitor := &event.CommandMonitor{
|
||||
Started: func(ctx context.Context, event *event.CommandStartedEvent) {
|
||||
zap.L().Info(fmt.Sprintf("[MongoDB][RequestID:%d][database:%s] %s\n", event.RequestID, event.DatabaseName, event.Command), zap.String("business", "mongo"))
|
||||
},
|
||||
Succeeded: func(ctx context.Context, event *event.CommandSucceededEvent) {
|
||||
zap.L().Info(fmt.Sprintf("[MongoDB][RequestID:%d] [%s] %s\n", event.RequestID, event.Duration.String(), event.Reply), zap.String("business", "mongo"))
|
||||
},
|
||||
Failed: func(ctx context.Context, event *event.CommandFailedEvent) {
|
||||
zap.L().Error(fmt.Sprintf("[MongoDB][RequestID:%d] [%s] %s\n", event.RequestID, event.Duration.String(), event.Failure), zap.String("business", "mongo"))
|
||||
},
|
||||
}
|
||||
return []options.ClientOptions{{ClientOptions: &opt.ClientOptions{Monitor: cmdMonitor}}}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user