mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-24 19:17:43 +00:00
Compare commits
1231
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
550e95ff43 | ||
|
|
060b6cfd64 | ||
|
|
d8529289cf | ||
|
|
4a8f97b1ee | ||
|
|
d54ac844ef | ||
|
|
379fba515f | ||
|
|
89a4738394 | ||
|
|
58105cb478 | ||
|
|
47af6f4306 | ||
|
|
7d29c9953a | ||
|
|
0729624c2f | ||
|
|
b3a740ab2a | ||
|
|
adf617f5d0 | ||
|
|
049a20cd04 | ||
|
|
be3c4452e3 | ||
|
|
5b01c9ada8 | ||
|
|
ffd82a6a10 | ||
|
|
dd8d89a990 | ||
|
|
2e5b23565e | ||
|
|
f4e3f04d30 | ||
|
|
954ebdc9eb | ||
|
|
2840010dfd | ||
|
|
b147d9b833 | ||
|
|
b3ecb81614 | ||
|
|
ce4581bb99 | ||
|
|
f406ca0160 | ||
|
|
39ea1f6aef | ||
|
|
b2053f507a | ||
|
|
9520117914 | ||
|
|
7a5fc7d440 | ||
|
|
bd5e83d464 | ||
|
|
63dd40a8d7 | ||
|
|
32bd88504d | ||
|
|
d70818a9db | ||
|
|
9d4a425fc0 | ||
|
|
4d6456a588 | ||
|
|
07ff92aa55 | ||
|
|
4156387eb9 | ||
|
|
34773a0a81 | ||
|
|
36a018400b | ||
|
|
7bb02c5f1d | ||
|
|
15fb128236 | ||
|
|
3581e060ec | ||
|
|
0604a29596 | ||
|
|
d8a2958797 | ||
|
|
ab28fa7bed | ||
|
|
e88d751039 | ||
|
|
b836945eea | ||
|
|
d7a8e66753 | ||
|
|
68780a845c | ||
|
|
487dc94a2e | ||
|
|
016e977776 | ||
|
|
dcfe512204 | ||
|
|
fe6ebfd47c | ||
|
|
eba5fba3da | ||
|
|
b7e9a79225 | ||
|
|
deffb19fd8 | ||
|
|
c858b322bd | ||
|
|
1b5b52f0f1 | ||
|
|
ecb31a158b | ||
|
|
1aecc140dc | ||
|
|
e464a4aedd | ||
|
|
595c4a6be5 | ||
|
|
d115c5299c | ||
|
|
9bd542bb59 | ||
|
|
0fa015b6d0 | ||
|
|
ec7d838ebd | ||
|
|
26e116c16c | ||
|
|
90d98893f5 | ||
|
|
10f162bf5d | ||
|
|
19909746f5 | ||
|
|
205febdb8a | ||
|
|
5aec4ba32b | ||
|
|
8f1ea50dfe | ||
|
|
1483ca401d | ||
|
|
ed74623a73 | ||
|
|
1bc2e22833 | ||
|
|
cd8edfa5d4 | ||
|
|
dcc2c8e175 | ||
|
|
d991a285ba | ||
|
|
76c9d1211e | ||
|
|
f5273f5a58 | ||
|
|
54ffaac9c5 | ||
|
|
484de2e698 | ||
|
|
d72ff76aad | ||
|
|
4a523bed92 | ||
|
|
55dc33b865 | ||
|
|
3d13f5856a | ||
|
|
aa2976ba17 | ||
|
|
8e141ff8a0 | ||
|
|
2628ab8e3e | ||
|
|
1b7dcd843c | ||
|
|
f0d91fb763 | ||
|
|
7238c6a26d | ||
|
|
0964cf98e2 | ||
|
|
04c6a081ae | ||
|
|
fcbd9ae02e | ||
|
|
d34d30a197 | ||
|
|
ecfea845c2 | ||
|
|
e05ff7c809 | ||
|
|
28a9626661 | ||
|
|
96b2cb3acf | ||
|
|
87fe6b7d9b | ||
|
|
722de8ea65 | ||
|
|
8ffde94433 | ||
|
|
08eef12bca | ||
|
|
ab0e8e6056 | ||
|
|
2cef52d906 | ||
|
|
95077b116b | ||
|
|
d4cf11d313 | ||
|
|
f201792d8f | ||
|
|
d16f5e7180 | ||
|
|
f8f697af2b | ||
|
|
eb8da38a46 | ||
|
|
f19568c69a | ||
|
|
91bf25e5fe | ||
|
|
85666f160f | ||
|
|
b2baf48dc6 | ||
|
|
c906e1d503 | ||
|
|
3b93ac19f8 | ||
|
|
9914373d45 | ||
|
|
4911730012 | ||
|
|
88bab51056 | ||
|
|
0fd4f68b6c | ||
|
|
c66cb5c6a8 | ||
|
|
b16ec0af77 | ||
|
|
82ea8539eb | ||
|
|
d17d5c1206 | ||
|
|
041d22d0d2 | ||
|
|
5864058a81 | ||
|
|
45035a16e4 | ||
|
|
f4d0108d49 | ||
|
|
b81611ba72 | ||
|
|
bb34108831 | ||
|
|
b7fd92f39b | ||
|
|
63b800a3ba | ||
|
|
ed9450a2d5 | ||
|
|
1d551a10ab | ||
|
|
4d7c9e5a12 | ||
|
|
d1f5fe5681 | ||
|
|
e8c2e0a966 | ||
|
|
cef0a19a9c | ||
|
|
c0e81363dc | ||
|
|
df2e4a2b48 | ||
|
|
9088ebc2e1 | ||
|
|
9f2dec3036 | ||
|
|
ea049f9b06 | ||
|
|
1f1349a685 | ||
|
|
dcef2df38e | ||
|
|
92834d6e39 | ||
|
|
f06540883b | ||
|
|
3c9ce5b6b0 | ||
|
|
ff8a59550a | ||
|
|
7cddef33a2 | ||
|
|
7013c2fa4a | ||
|
|
65bacacb38 | ||
|
|
45587028c3 | ||
|
|
887c9cca4b | ||
|
|
a6ddb113fc | ||
|
|
b83eef8670 | ||
|
|
43dcd61c51 | ||
|
|
1bd64d4562 | ||
|
|
44e81bc72f | ||
|
|
3312f8b7b9 | ||
|
|
d6a2272f9d | ||
|
|
a5cc0a9e29 | ||
|
|
3f995735e9 | ||
|
|
b65b74dee5 | ||
|
|
98cf3ad95a | ||
|
|
8649d8d791 | ||
|
|
817e34c6aa | ||
|
|
952cd92648 | ||
|
|
6b1e961a7f | ||
|
|
762eba5af7 | ||
|
|
e82128f679 | ||
|
|
8f8a197db1 | ||
|
|
364854eda0 | ||
|
|
b259e91f4d | ||
|
|
76411f80bc | ||
|
|
5494353229 | ||
|
|
afe5efbe36 | ||
|
|
db422785fc | ||
|
|
4ac68323da | ||
|
|
44002fcb11 | ||
|
|
5bbd919745 | ||
|
|
54dd3de5b6 | ||
|
|
9540fdfc30 | ||
|
|
937775e2a7 | ||
|
|
84721265dd | ||
|
|
3ab67dfa7d | ||
|
|
6a1941a820 | ||
|
|
3ae7c44585 | ||
|
|
9d809f6392 | ||
|
|
4b477b3103 | ||
|
|
e7ae2fe019 | ||
|
|
d5ba3d9770 | ||
|
|
f3d744f6f5 | ||
|
|
0315631b53 | ||
|
|
48e7ce88ff | ||
|
|
357db6b1c9 | ||
|
|
83e0531f43 | ||
|
|
898ba7d8eb | ||
|
|
8751f34539 | ||
|
|
4aa0068d2d | ||
|
|
b954a2f092 | ||
|
|
9227bd2be1 | ||
|
|
e70a0b1314 | ||
|
|
21c262a31e | ||
|
|
f30889bd19 | ||
|
|
23e519999e | ||
|
|
bedf064ace | ||
|
|
9a8e0cddde | ||
|
|
c0c16036d3 | ||
|
|
dd905a2bed | ||
|
|
2d76430f89 | ||
|
|
5dde1d2a00 | ||
|
|
93f25c6cdf | ||
|
|
d366df372d | ||
|
|
7281d05efc | ||
|
|
e6d6a65267 | ||
|
|
9ff094b6f5 | ||
|
|
fc9c253a9f | ||
|
|
9d735ed5aa | ||
|
|
d782b00117 | ||
|
|
239159dd2a | ||
|
|
0c1e91c3b5 | ||
|
|
c09347b387 | ||
|
|
e49e47c7a1 | ||
|
|
98b46535aa | ||
|
|
014a23aac3 | ||
|
|
f1dfba79e0 | ||
|
|
a282e44b1d | ||
|
|
d1279e67fb | ||
|
|
37a5963cd6 | ||
|
|
69df1b3d34 | ||
|
|
eae97f7a15 | ||
|
|
ce0b5ff7bf | ||
|
|
73118e49b9 | ||
|
|
7b43982595 | ||
|
|
31cd1ee768 | ||
|
|
8df7551946 | ||
|
|
79c1295a70 | ||
|
|
26d9a2e9e4 | ||
|
|
b09b7b6ccc | ||
|
|
ef6fdaa221 | ||
|
|
23c80f0217 | ||
|
|
95dc699e2e | ||
|
|
74b7d62c75 | ||
|
|
68accd5448 | ||
|
|
3edbef8696 | ||
|
|
5527f6386a | ||
|
|
c48f70a7c6 | ||
|
|
a078e31664 | ||
|
|
04d2d7dde1 | ||
|
|
b846053bea | ||
|
|
a1a5634c4e | ||
|
|
6036c6e4e3 | ||
|
|
7d74e6f325 | ||
|
|
1bf9f74bdf | ||
|
|
f61de5beeb | ||
|
|
79fb2d0bee | ||
|
|
c973d6819c | ||
|
|
3d8b879e64 | ||
|
|
ac971bda4b | ||
|
|
44f62abbca | ||
|
|
7d7d8484d4 | ||
|
|
c8b27492eb | ||
|
|
783f79dcb6 | ||
|
|
ddd97d5a9e | ||
|
|
fa73c3d6b1 | ||
|
|
5b2f3e9316 | ||
|
|
42f3025217 | ||
|
|
5df43b4d11 | ||
|
|
eac1bf197e | ||
|
|
5c834939af | ||
|
|
642e86951b | ||
|
|
bc42412e92 | ||
|
|
d9122d29cb | ||
|
|
b532ad994c | ||
|
|
b62fbc803c | ||
|
|
ed8bac8a1b | ||
|
|
d56142463a | ||
|
|
f795a356e7 | ||
|
|
a359c25e36 | ||
|
|
0ef422d309 | ||
|
|
e0519a4d7e | ||
|
|
5043ce5411 | ||
|
|
cc0cdc7d0e | ||
|
|
c004b3d333 | ||
|
|
453dd65cb1 | ||
|
|
b55cbe1992 | ||
|
|
b6e1b7b210 | ||
|
|
3685510d25 | ||
|
|
d7e685536c | ||
|
|
4df859a0ea | ||
|
|
7d1b84e837 | ||
|
|
be1f0be9b2 | ||
|
|
0f742900f5 | ||
|
|
e6f4fac859 | ||
|
|
d39faa1aca | ||
|
|
c3fe13d9dd | ||
|
|
90db381b5a | ||
|
|
508137da4b | ||
|
|
695ac7b29b | ||
|
|
c06df13a75 | ||
|
|
e663de3697 | ||
|
|
10b4f03ff5 | ||
|
|
c7a4434a0e | ||
|
|
1f8babd9e7 | ||
|
|
8e8fe906fd | ||
|
|
386d08a03f | ||
|
|
af38e1694b | ||
|
|
5609e004cc | ||
|
|
5be468308a | ||
|
|
39702abfba | ||
|
|
a97d86e801 | ||
|
|
75582539fe | ||
|
|
466723c55e | ||
|
|
6b3b2125df | ||
|
|
8c8d268708 | ||
|
|
73ac7273a0 | ||
|
|
dcafcdc6ce | ||
|
|
5b1405391e | ||
|
|
0993173b1f | ||
|
|
483ec2bf3e | ||
|
|
60fe272ba0 | ||
|
|
8df2e8190e | ||
|
|
66c8eb5ee8 | ||
|
|
bb65e76219 | ||
|
|
a585e29073 | ||
|
|
7655d0fd38 | ||
|
|
5bec640f20 | ||
|
|
62d9084ee8 | ||
|
|
df8ab39aa0 | ||
|
|
a7d3666811 | ||
|
|
ea9e3d2fe1 | ||
|
|
f14085f3ee | ||
|
|
e8b9db1df5 | ||
|
|
dc625997c4 | ||
|
|
4f458591e8 | ||
|
|
7a074d93cd | ||
|
|
9d1e1f6482 | ||
|
|
19153170bb | ||
|
|
6bf774c463 | ||
|
|
0de5ba77aa | ||
|
|
f4c0134d9c | ||
|
|
2c5c1b69b4 | ||
|
|
d34a33b691 | ||
|
|
869394c898 | ||
|
|
b97bde11b2 | ||
|
|
852cfa66e8 | ||
|
|
289fbba8e0 | ||
|
|
a6ffac657e | ||
|
|
096663ac91 | ||
|
|
d40a0c0837 | ||
|
|
f94b437852 | ||
|
|
7e571b038f | ||
|
|
56968c0bd2 | ||
|
|
ef6b85faec | ||
|
|
0d47fb4e68 | ||
|
|
8bfee8af16 | ||
|
|
0fc7276ccd | ||
|
|
9a596397ca | ||
|
|
1bea64bb6d | ||
|
|
09bfdc3d39 | ||
|
|
fe8b39691b | ||
|
|
1465bfdf15 | ||
|
|
bccbd67450 | ||
|
|
50a3b39666 | ||
|
|
087ba38c24 | ||
|
|
8a3c50ea1a | ||
|
|
86187e4c79 | ||
|
|
98c495abb1 | ||
|
|
c53c4fd9f8 | ||
|
|
b231705a67 | ||
|
|
86f94a2cb9 | ||
|
|
ef41e07550 | ||
|
|
255c72d3f1 | ||
|
|
260eedfcc6 | ||
|
|
f43cd117e3 | ||
|
|
83b219458f | ||
|
|
0f1b9369df | ||
|
|
b31e1c0d58 | ||
|
|
0122024789 | ||
|
|
f469536174 | ||
|
|
70bd8b26ad | ||
|
|
dabc4d88b3 | ||
|
|
214f90b366 | ||
|
|
3c4cc054df | ||
|
|
4d329287ce | ||
|
|
866714d75e | ||
|
|
b268d03e30 | ||
|
|
953d3b4135 | ||
|
|
0d6b347d7e | ||
|
|
a19622f6ac | ||
|
|
45c8737601 | ||
|
|
4925383f5e | ||
|
|
62ab985050 | ||
|
|
50f14f7658 | ||
|
|
1cd31079d7 | ||
|
|
46b62a9809 | ||
|
|
8f70a1dca4 | ||
|
|
937b62e641 | ||
|
|
3873342f6c | ||
|
|
717903a2b6 | ||
|
|
88030e301a | ||
|
|
cd0792e3d2 | ||
|
|
6ed2fcbf6c | ||
|
|
eb33515e29 | ||
|
|
bf93b86bd0 | ||
|
|
c41672c21a | ||
|
|
8716b073df | ||
|
|
638bab3c9d | ||
|
|
c5d7a8c740 | ||
|
|
b030be8f80 | ||
|
|
1fe19d1c3b | ||
|
|
1508e850fe | ||
|
|
81bc15d77c | ||
|
|
24adca55e4 | ||
|
|
9c8974a26e | ||
|
|
01e8984b79 | ||
|
|
7f1aa89539 | ||
|
|
4876fc0aa1 | ||
|
|
f998d20a86 | ||
|
|
cdb5faf043 | ||
|
|
dfaa2ff51e | ||
|
|
8754ff8147 | ||
|
|
cca84c3c21 | ||
|
|
72391f0201 | ||
|
|
39e26d738c | ||
|
|
f114424079 | ||
|
|
98b60f0564 | ||
|
|
d02b52f383 | ||
|
|
b52da434bb | ||
|
|
3cfa7a2767 | ||
|
|
054199d1e4 | ||
|
|
02b62a288d | ||
|
|
24908a8732 | ||
|
|
26ee7b7985 | ||
|
|
b76db48112 | ||
|
|
e2c5075319 | ||
|
|
e9f36e74ca | ||
|
|
0b73bd7b25 | ||
|
|
ccccab3104 | ||
|
|
ae8e32d806 | ||
|
|
fd2709affa | ||
|
|
34b3395d15 | ||
|
|
abdc80b756 | ||
|
|
492ac31973 | ||
|
|
3ac1878c3c | ||
|
|
0cc27355f7 | ||
|
|
562f761807 | ||
|
|
88e4b37c03 | ||
|
|
b73d88d6cf | ||
|
|
443c30d48c | ||
|
|
64cbf31184 | ||
|
|
ce9d9bd3ec | ||
|
|
57330784ac | ||
|
|
85e1c6fe54 | ||
|
|
7ca776bfab | ||
|
|
082c369d41 | ||
|
|
0ac9f41e1a | ||
|
|
16701d38a6 | ||
|
|
30eb280698 | ||
|
|
974a8096ca | ||
|
|
bb83a97613 | ||
|
|
8baae5e712 | ||
|
|
49e4c19cbb | ||
|
|
6f67628012 | ||
|
|
8a1573cc14 | ||
|
|
03b916ef8a | ||
|
|
9d25648c1f | ||
|
|
aa6c3df892 | ||
|
|
bdaa6e0db0 | ||
|
|
4740a39808 | ||
|
|
386c620b48 | ||
|
|
2441412714 | ||
|
|
9db940150a | ||
|
|
8c5639af53 | ||
|
|
b4a6f82f5f | ||
|
|
7dd62a4cf8 | ||
|
|
095ed7c2fd | ||
|
|
76567eea84 | ||
|
|
5a65fcd477 | ||
|
|
27b0e1a07a | ||
|
|
3e20e93797 | ||
|
|
5d3b1c3d0f | ||
|
|
b5a57e6dd9 | ||
|
|
2decf43b4c | ||
|
|
7dd3e2b27e | ||
|
|
325c91989c | ||
|
|
96250bafb1 | ||
|
|
fff795ce5a | ||
|
|
9887250407 | ||
|
|
e42191c6af | ||
|
|
adce44dc1f | ||
|
|
a70ee44466 | ||
|
|
fd0fa49f1c | ||
|
|
10491f9745 | ||
|
|
981313c0e2 | ||
|
|
6b88c9a004 | ||
|
|
25395b5006 | ||
|
|
2f516b49cf | ||
|
|
41ff26edc8 | ||
|
|
a1c6f586cf | ||
|
|
8b01126e0f | ||
|
|
e59d40af21 | ||
|
|
6b476bfab7 | ||
|
|
12429d4585 | ||
|
|
6fe2edbe89 | ||
|
|
cee6bd6abd | ||
|
|
4ac3350920 | ||
|
|
a45113258c | ||
|
|
4a2659573b | ||
|
|
c74080664f | ||
|
|
84e06395a5 | ||
|
|
411b85afcd | ||
|
|
57d128144d | ||
|
|
a7fa7e079b | ||
|
|
2781e413dc | ||
|
|
41d8daac97 | ||
|
|
dd22d55ee7 | ||
|
|
32a1bd2511 | ||
|
|
1130d20f14 | ||
|
|
90b17e995a | ||
|
|
1c41b0ec72 | ||
|
|
3f90605589 | ||
|
|
c1347fbb5d | ||
|
|
0d1cb2ee33 | ||
|
|
b6be297a1e | ||
|
|
b9b3cbee93 | ||
|
|
37d318b4d9 | ||
|
|
5368cfbcb8 | ||
|
|
72a4ba077c | ||
|
|
fba03625d0 | ||
|
|
007807e776 | ||
|
|
f2ae95d932 | ||
|
|
25d7323ed1 | ||
|
|
f7e737534d | ||
|
|
760c6b2814 | ||
|
|
d0f49ef8c7 | ||
|
|
1295b1fd35 | ||
|
|
eb8062d19b | ||
|
|
d82129d364 | ||
|
|
16923017b2 | ||
|
|
f4396e7e83 | ||
|
|
acee466fc8 | ||
|
|
862f24d8a7 | ||
|
|
e2cb033670 | ||
|
|
029c505501 | ||
|
|
dd5f0c52fb | ||
|
|
f80e64688e | ||
|
|
3fd34db3c1 | ||
|
|
0607d462d0 | ||
|
|
7f8c302405 | ||
|
|
c82c58443c | ||
|
|
9591be883f | ||
|
|
acdcd0c867 | ||
|
|
0f623521c4 | ||
|
|
91b93a3f48 | ||
|
|
32d123eb89 | ||
|
|
2025809d91 | ||
|
|
d552875e8a | ||
|
|
6994857f4b | ||
|
|
7ac941c1d0 | ||
|
|
af68778436 | ||
|
|
1ab11c46ba | ||
|
|
add355637d | ||
|
|
65380695f2 | ||
|
|
a6116ede02 | ||
|
|
dafa627e31 | ||
|
|
993115c8ae | ||
|
|
b97b3bc6d8 | ||
|
|
636294b6f5 | ||
|
|
37fe63126c | ||
|
|
9ace1d8201 | ||
|
|
25472887c9 | ||
|
|
5e7d2614b3 | ||
|
|
484246e146 | ||
|
|
65fb965f66 | ||
|
|
493d714723 | ||
|
|
a911e73238 | ||
|
|
dff133bbbc | ||
|
|
60d9c59544 | ||
|
|
185df737c8 | ||
|
|
a0b7d9969a | ||
|
|
3358eeb54c | ||
|
|
d32751ea5d | ||
|
|
8f637e02d0 | ||
|
|
e4ffd1df14 | ||
|
|
869bea2163 | ||
|
|
7e9919e3ae | ||
|
|
5afe67bd1b | ||
|
|
13f09d6059 | ||
|
|
1c9d2075d7 | ||
|
|
3a688c06a1 | ||
|
|
2d4b4d617d | ||
|
|
9fb23b45cd | ||
|
|
af7a3e99a8 | ||
|
|
992d892523 | ||
|
|
7699d8cf42 | ||
|
|
f81c9c2ca0 | ||
|
|
76351fe685 | ||
|
|
f0e8f18c6d | ||
|
|
880a3700d1 | ||
|
|
273f2ba8b0 | ||
|
|
a3507998f7 | ||
|
|
67d393a222 | ||
|
|
372248c819 | ||
|
|
b837489af0 | ||
|
|
e7223be040 | ||
|
|
e37743733d | ||
|
|
58a9b00120 | ||
|
|
d8a627f880 | ||
|
|
df46f86130 | ||
|
|
c98dd649eb | ||
|
|
da15a2d3bb | ||
|
|
00c87d7c80 | ||
|
|
7c8c285fb8 | ||
|
|
e0cf7af7a9 | ||
|
|
ed52efaa44 | ||
|
|
4a2aa02210 | ||
|
|
cc0dab3cd0 | ||
|
|
7b6e57b8dd | ||
|
|
325b2cc0a2 | ||
|
|
52f45e362b | ||
|
|
1391366ece | ||
|
|
40668581b8 | ||
|
|
261e448577 | ||
|
|
7c7cd7ef7e | ||
|
|
860226ab41 | ||
|
|
898b1ea1e6 | ||
|
|
f4d63c57e9 | ||
|
|
3aa64d107b | ||
|
|
0e3e733745 | ||
|
|
e6384de265 | ||
|
|
b9e8759ef1 | ||
|
|
ff5d498c53 | ||
|
|
a8820a0aac | ||
|
|
24c78519ed | ||
|
|
49c6febdf0 | ||
|
|
3d620d0149 | ||
|
|
352224b994 | ||
|
|
897b9e6919 | ||
|
|
1b579fb814 | ||
|
|
ab9020e7c7 | ||
|
|
54e116818b | ||
|
|
6a31ac916a | ||
|
|
f35a808975 | ||
|
|
b8ab38308d | ||
|
|
91d902389a | ||
|
|
5948c5c9a6 | ||
|
|
9a2c0729f6 | ||
|
|
c96d9c35a7 | ||
|
|
65e0b58be9 | ||
|
|
dad4b4cca8 | ||
|
|
3be02f1364 | ||
|
|
190dcfedc0 | ||
|
|
6e01c13e1c | ||
|
|
bb77450f2c | ||
|
|
4dfd4e4647 | ||
|
|
9a49700d09 | ||
|
|
107a8d8f90 | ||
|
|
b70b853016 | ||
|
|
dd2ed9f9a5 | ||
|
|
1a645f8521 | ||
|
|
c05cb63bc7 | ||
|
|
602ff26988 | ||
|
|
b7d6b6786c | ||
|
|
95c794c80d | ||
|
|
e59dd88f97 | ||
|
|
974d5b2889 | ||
|
|
fb42953fbe | ||
|
|
277129e830 | ||
|
|
cdc5c1246e | ||
|
|
c0f920d43c | ||
|
|
f31d1a137f | ||
|
|
b1d3924294 | ||
|
|
526a993698 | ||
|
|
f5d9a22f83 | ||
|
|
5ae3ef3a15 | ||
|
|
0773de2fc5 | ||
|
|
48a57c4244 | ||
|
|
9b1cba311e | ||
|
|
b5bfece9f1 | ||
|
|
06000a6dac | ||
|
|
a54d4ba0c1 | ||
|
|
a7cc943b8e | ||
|
|
2cb81654a6 | ||
|
|
0314991f9e | ||
|
|
da16b4af7a | ||
|
|
fda4a944fc | ||
|
|
9026da4a6d | ||
|
|
7d8c5a1ce7 | ||
|
|
75b10f0fb7 | ||
|
|
6f923d7ea7 | ||
|
|
bc2e68c288 | ||
|
|
a02f01453d | ||
|
|
c1b0e9ab8b | ||
|
|
ea4375b408 | ||
|
|
1c0060fd14 | ||
|
|
4c127c8c4b | ||
|
|
32d8f4384a | ||
|
|
5be2070cbc | ||
|
|
16d98cb386 | ||
|
|
f22c4e5dc8 | ||
|
|
d69b7807c1 | ||
|
|
dc32d92755 | ||
|
|
ca8fd2178d | ||
|
|
b565b72cd3 | ||
|
|
8d272e8862 | ||
|
|
dd7b50e0fa | ||
|
|
623e796c92 | ||
|
|
126f62ce59 | ||
|
|
77c149af04 | ||
|
|
b21f665760 | ||
|
|
be36c595c3 | ||
|
|
1ac6568f67 | ||
|
|
b902ab2a5f | ||
|
|
5069c3074b | ||
|
|
e0bd26d837 | ||
|
|
cc74686108 | ||
|
|
7880151ef2 | ||
|
|
cfa88b5ecf | ||
|
|
452a561309 | ||
|
|
7d7fc530d1 | ||
|
|
18d9b77cca | ||
|
|
7491ff4459 | ||
|
|
2ac8f925c9 | ||
|
|
c89b0ca8e1 | ||
|
|
a3de9cd569 | ||
|
|
5e10b631f6 | ||
|
|
d71ff80e12 | ||
|
|
1b2bf70df1 | ||
|
|
cbdfa61268 | ||
|
|
6cbded6241 | ||
|
|
f40b300c16 | ||
|
|
8a58fb5b3b | ||
|
|
e473f158e8 | ||
|
|
408dcc5057 | ||
|
|
765775c54d | ||
|
|
44545f3d5b | ||
|
|
e23a1f615f | ||
|
|
d3fa3ecf6c | ||
|
|
6b1c3d9226 | ||
|
|
70d4976595 | ||
|
|
b2345039e4 | ||
|
|
33275573f9 | ||
|
|
9fdde1a13b | ||
|
|
09cc51e5de | ||
|
|
20e7f79f4f | ||
|
|
b82c3d9f99 | ||
|
|
dfd460c50b | ||
|
|
eeaa05f5b4 | ||
|
|
4904c9ed20 | ||
|
|
9a6f93854b | ||
|
|
99699c0065 | ||
|
|
85685dbda1 | ||
|
|
3a4bfe915b | ||
|
|
8adc79a5f7 | ||
|
|
2580be08ab | ||
|
|
fd9df69d1f | ||
|
|
d0b2e8d03f | ||
|
|
70998c424d | ||
|
|
abcdf880fd | ||
|
|
fac18b28f0 | ||
|
|
ee15329bd2 | ||
|
|
98e0ee92a7 | ||
|
|
54158b8824 | ||
|
|
75cd2f374a | ||
|
|
a78cc33cd1 | ||
|
|
fea425c36c | ||
|
|
61c89bca8d | ||
|
|
76e83de8cd | ||
|
|
c9d9350476 | ||
|
|
34a1eea45e | ||
|
|
c505884cf2 | ||
|
|
b95f517a4c | ||
|
|
50da7825bb | ||
|
|
0a3e3be12c | ||
|
|
c13d13ccf4 | ||
|
|
0317c3aaf5 | ||
|
|
03eaad5b8b | ||
|
|
e091603bc8 | ||
|
|
cd29c1728d | ||
|
|
65b86353e3 | ||
|
|
5f594d6bae | ||
|
|
57067907d3 | ||
|
|
bde8148eb1 | ||
|
|
ae34ff8f82 | ||
|
|
d91ddf265d | ||
|
|
59e8645f56 | ||
|
|
ddc3a1d403 | ||
|
|
816b10681b | ||
|
|
ef33508e2b | ||
|
|
a8c5f19075 | ||
|
|
1c046d9ddd | ||
|
|
f4e07371c5 | ||
|
|
6a2557064c | ||
|
|
4a592f515a | ||
|
|
9bc2791b26 | ||
|
|
6222765e20 | ||
|
|
f56839926e | ||
|
|
c6a52134b3 | ||
|
|
7fffcffcdd | ||
|
|
e126fd38bc | ||
|
|
c6d94c03fb | ||
|
|
50a5d6f48d | ||
|
|
508a18a809 | ||
|
|
946a53cb2b | ||
|
|
c6ccd844f5 | ||
|
|
debe808f83 | ||
|
|
e219463505 | ||
|
|
009866ee31 | ||
|
|
b0225d9bab | ||
|
|
13ff9d9e63 | ||
|
|
5957fb280c | ||
|
|
44d196eaef | ||
|
|
bf7934c20a | ||
|
|
845558cc65 | ||
|
|
22250dbbba | ||
|
|
7290742ac0 | ||
|
|
97d9590d05 | ||
|
|
1a111f0666 | ||
|
|
e40047a88e | ||
|
|
3df83cb057 | ||
|
|
cfd284e42e | ||
|
|
3211b47d40 | ||
|
|
f21de0706a | ||
|
|
7abe98b723 | ||
|
|
413ca6d353 | ||
|
|
b69bc87468 | ||
|
|
a1a41f0a86 | ||
|
|
14db1e802c | ||
|
|
221a47423d | ||
|
|
daadb14618 | ||
|
|
40cd55da90 | ||
|
|
eddfe26308 | ||
|
|
e034e5b1ea | ||
|
|
36bdeb3faa | ||
|
|
7112323a5e | ||
|
|
ea4ffb8867 | ||
|
|
225970c005 | ||
|
|
24370899ab | ||
|
|
510bdd94f9 | ||
|
|
2a27c91656 | ||
|
|
3767274755 | ||
|
|
aefdb47c32 | ||
|
|
30feaae7e7 | ||
|
|
8557e1e160 | ||
|
|
dd16744676 | ||
|
|
495a9c54aa | ||
|
|
afab1ef7c9 | ||
|
|
5e99363e9a | ||
|
|
86c7c64a32 | ||
|
|
44b9666302 | ||
|
|
91c9a2c7f9 | ||
|
|
a65930b35b | ||
|
|
9023b620fb | ||
|
|
f188b34253 | ||
|
|
8faf6c9aba | ||
|
|
547a5eb8e4 | ||
|
|
2459c877e0 | ||
|
|
93625a6d9f | ||
|
|
58b439f553 | ||
|
|
78c67185fd | ||
|
|
265cc9350d | ||
|
|
4182bac3a2 | ||
|
|
12893fc09d | ||
|
|
6fb7da87a3 | ||
|
|
0ecf56cd8c | ||
|
|
0c802ad366 | ||
|
|
0be7735855 | ||
|
|
9c73558382 | ||
|
|
4e370665dd | ||
|
|
0194561056 | ||
|
|
5b69883a9f | ||
|
|
b1789e5785 | ||
|
|
5756286641 | ||
|
|
6746dbd9cf | ||
|
|
2134d9fa58 | ||
|
|
a8c00c5be1 | ||
|
|
51bc4423f7 | ||
|
|
55a85babf8 | ||
|
|
c9f705e4f5 | ||
|
|
9bf9330410 | ||
|
|
f53ee3008a | ||
|
|
31ec3880da | ||
|
|
ed492d5411 | ||
|
|
fa30b8db95 | ||
|
|
8a471e1db7 | ||
|
|
dbf4b36687 | ||
|
|
263355de1e | ||
|
|
3a570e50a1 | ||
|
|
e0767255f4 | ||
|
|
75b529d7b0 | ||
|
|
a43413d1ff | ||
|
|
7599a22470 | ||
|
|
b79747e551 | ||
|
|
4666025819 | ||
|
|
5353527e6c | ||
|
|
2bfd8c51b1 | ||
|
|
95b3b97597 | ||
|
|
31b36bc413 | ||
|
|
db7abdda33 | ||
|
|
25d8364bf4 | ||
|
|
c541d09076 | ||
|
|
c2ceaec3ea | ||
|
|
3e26bf136d | ||
|
|
21a81a534c | ||
|
|
00b46b79df | ||
|
|
9c7099db1f | ||
|
|
86700cfd52 | ||
|
|
a1b74e3f6a | ||
|
|
75711922b4 | ||
|
|
f02c178473 | ||
|
|
39dd1ee6f8 | ||
|
|
48f0cb46ec | ||
|
|
1105d98f27 | ||
|
|
c294bb1585 | ||
|
|
e2952fa393 | ||
|
|
12be09b4d5 | ||
|
|
d8ebaf540b | ||
|
|
b8ec705e0a | ||
|
|
19e96a01de | ||
|
|
734a48dbb8 | ||
|
|
78671ae99e | ||
|
|
7e7065a579 | ||
|
|
5ff282c23d | ||
|
|
117392bfe8 | ||
|
|
f22f654c91 | ||
|
|
561566f172 | ||
|
|
c3bb36c969 | ||
|
|
da347e095f | ||
|
|
e9617ec677 | ||
|
|
017d669a04 | ||
|
|
e184ad84cd | ||
|
|
deeb82048b | ||
|
|
4979fd659f | ||
|
|
e04b5559bd | ||
|
|
a4f8b11925 | ||
|
|
9186c6ce04 | ||
|
|
a24c31c6b9 | ||
|
|
981e992f28 | ||
|
|
7ce090af38 | ||
|
|
f67e653abe | ||
|
|
128739f097 | ||
|
|
792c8874aa | ||
|
|
ad42b6489a | ||
|
|
8a3659d949 | ||
|
|
a10bacc1f5 | ||
|
|
677e54631a | ||
|
|
c0f6fb6b41 | ||
|
|
24ab2d9cbe | ||
|
|
f768d69def | ||
|
|
305326d9ea | ||
|
|
249567d61d | ||
|
|
390f4ddfdd | ||
|
|
72d92b5232 | ||
|
|
82a09c4abd | ||
|
|
a7dafceb5b | ||
|
|
98d30f64f6 | ||
|
|
abe9c42aff | ||
|
|
0db58170ee | ||
|
|
e1f15bcff1 | ||
|
|
17032f29a0 | ||
|
|
0db2e8ef13 | ||
|
|
2c3dda58b4 | ||
|
|
eaa135e2ad | ||
|
|
46ea5f18aa | ||
|
|
ab7c34f817 | ||
|
|
746123c888 | ||
|
|
37a2c585e1 | ||
|
|
d5a94b8dc0 | ||
|
|
da96902ca4 | ||
|
|
d9347de392 | ||
|
|
9731b7baaf | ||
|
|
0c170fe1c6 | ||
|
|
5f873914e0 | ||
|
|
0810383afc | ||
|
|
2790abf1ad | ||
|
|
ddb3a45e1a | ||
|
|
83529dd6f5 | ||
|
|
b9e9d7178b | ||
|
|
ab547cc49b | ||
|
|
6b0b1bb836 | ||
|
|
9d6e3598c0 | ||
|
|
93c5511b55 | ||
|
|
3e7b3f5176 | ||
|
|
a958c879a1 | ||
|
|
a06da857f3 | ||
|
|
5714ffde7b | ||
|
|
f1e521522a | ||
|
|
52be3f6153 | ||
|
|
2399824d35 | ||
|
|
17b850d0a8 | ||
|
|
78db91785c | ||
|
|
411b9d3380 | ||
|
|
7382a44162 | ||
|
|
07cb061ea5 | ||
|
|
5078403569 | ||
|
|
5e3e16025c | ||
|
|
4b5b0507e9 | ||
|
|
ccd76445f2 | ||
|
|
706c72b973 | ||
|
|
22fb2b5d0b | ||
|
|
16775cee45 | ||
|
|
2195c89099 | ||
|
|
199fc180b7 | ||
|
|
55112db749 | ||
|
|
da4637d285 | ||
|
|
a68e58f93b | ||
|
|
c814439f93 | ||
|
|
cc34268791 | ||
|
|
b94acb62ca | ||
|
|
c851e82762 | ||
|
|
870b144557 | ||
|
|
ac5b10e6c0 | ||
|
|
e1965be2ab | ||
|
|
b931925f75 | ||
|
|
e53cc4a9b5 | ||
|
|
b40d376e25 | ||
|
|
52f42616a2 | ||
|
|
01c519e894 | ||
|
|
93feb4779e | ||
|
|
61bf06c49b | ||
|
|
8ef816c2a9 | ||
|
|
8705ad0436 | ||
|
|
c350c8e32a | ||
|
|
f2703f16d5 | ||
|
|
7a0fff117b | ||
|
|
209db87d05 | ||
|
|
d12b1b7384 | ||
|
|
21603d5a4c | ||
|
|
bd658d9f0b | ||
|
|
9e640cf422 | ||
|
|
d1d545b29c | ||
|
|
1e8544f1ce | ||
|
|
2ecabd1202 | ||
|
|
4741137c9a | ||
|
|
f4c5aacf32 | ||
|
|
72a95babe5 | ||
|
|
3b5ce7ffaf | ||
|
|
7db918c495 | ||
|
|
faa8de7dc2 | ||
|
|
68c51fb480 | ||
|
|
4fe68183ba | ||
|
|
918e5c2ce1 | ||
|
|
ce656bf7a1 | ||
|
|
4be26aa5d2 | ||
|
|
dd09f52343 | ||
|
|
606aa821d5 | ||
|
|
99acf0086f | ||
|
|
c9b073908e | ||
|
|
000cd1d07d | ||
|
|
ca8d1e7e33 | ||
|
|
bc648c3f2f | ||
|
|
677cdb7821 | ||
|
|
7e2e40df56 | ||
|
|
2c23ef8c6d | ||
|
|
dab93f16b0 | ||
|
|
43dd214a7e | ||
|
|
c0ac9a6397 | ||
|
|
78bb246390 | ||
|
|
029783a685 | ||
|
|
8460b3f385 | ||
|
|
f3e0e4dbc6 | ||
|
|
72a876f37a | ||
|
|
7d8a267da7 | ||
|
|
2fe2c96e82 | ||
|
|
fdbb76086e | ||
|
|
a8d5846483 | ||
|
|
a06bd289b3 | ||
|
|
6bc3e6c00b | ||
|
|
91f2adb340 | ||
|
|
21d217c42a | ||
|
|
50464dc6e6 | ||
|
|
bb07d90407 | ||
|
|
3d5a2ea9f7 | ||
|
|
ca4ed89536 | ||
|
|
9cab65af6a | ||
|
|
7bba9b44b5 | ||
|
|
fd27170946 | ||
|
|
c6735c4b1a | ||
|
|
106ca70acc | ||
|
|
e0f5a9ede2 | ||
|
|
ab28eea2f3 | ||
|
|
6f09ecca32 | ||
|
|
0b80ac635d | ||
|
|
31544ff1ae | ||
|
|
d4b87c78a7 | ||
|
|
b8f8bfd400 | ||
|
|
2d15f91f2e | ||
|
|
3a6a06f52a | ||
|
|
317d417824 | ||
|
|
8a7a9db7fd | ||
|
|
fa07a57d22 | ||
|
|
aaba20d42a | ||
|
|
4466482ad9 | ||
|
|
4140cad9dd | ||
|
|
0c64d8b927 | ||
|
|
3bd3656d15 | ||
|
|
77dfe418df | ||
|
|
8a3ee2e7af | ||
|
|
01ea376015 | ||
|
|
41b06c806a | ||
|
|
0ae41f247c | ||
|
|
560e650070 | ||
|
|
3a9831fc8c | ||
|
|
4b8c372038 | ||
|
|
d3622c93fc | ||
|
|
f0fde7f77c | ||
|
|
c93f3f01b3 | ||
|
|
4e39e915ea | ||
|
|
b98997a69f | ||
|
|
cb06f596e9 | ||
|
|
638e5b8311 | ||
|
|
b646ce9335 | ||
|
|
abc8b271c4 | ||
|
|
61451d149a | ||
|
|
671291d024 | ||
|
|
562e61cf4f | ||
|
|
d53ba4ed7a | ||
|
|
78417c2d33 | ||
|
|
5b9225ac3a | ||
|
|
05b53c0710 | ||
|
|
ca98fd873c | ||
|
|
55496078f0 | ||
|
|
c210d2301c | ||
|
|
77499b50cc | ||
|
|
3063386ec6 | ||
|
|
58c7b43759 | ||
|
|
ce80857d5f | ||
|
|
a1e0b6cc5e | ||
|
|
09e3c4f51d | ||
|
|
dc9bec2c22 | ||
|
|
8817f02038 | ||
|
|
33b462217c | ||
|
|
486a77762c | ||
|
|
87edf1099c | ||
|
|
1dffcb7199 | ||
|
|
c69a7d9fd9 | ||
|
|
5db1495985 | ||
|
|
65c4cfd684 | ||
|
|
de67c5dc46 | ||
|
|
6877e1324c | ||
|
|
60c7496f7b | ||
|
|
93cfe443d6 | ||
|
|
ffbacea589 | ||
|
|
7373536d36 | ||
|
|
9f0ceda347 | ||
|
|
22f5d39209 | ||
|
|
d3376acc34 | ||
|
|
aca9e19551 | ||
|
|
d27583421b | ||
|
|
7215f7c236 | ||
|
|
c2742e5f96 | ||
|
|
7b4ff44360 | ||
|
|
87aa013cf7 | ||
|
|
5a793b9e34 | ||
|
|
128e34ab04 | ||
|
|
b9d3483ba3 | ||
|
|
2c8c7688b4 | ||
|
|
a2f1721ee1 | ||
|
|
b26258c8ab | ||
|
|
242902d2bc | ||
|
|
2d11163ce5 | ||
|
|
c2f593de48 | ||
|
|
7140ff8a3a | ||
|
|
cc6c333372 | ||
|
|
77bfc07826 | ||
|
|
a33b0d036a | ||
|
|
729154212f | ||
|
|
e345ffe9af | ||
|
|
afb98f5e81 | ||
|
|
826eef40a3 | ||
|
|
589a9b7d9c | ||
|
|
7889e49633 | ||
|
|
ed238f0143 | ||
|
|
8ebcd772b2 | ||
|
|
e9ef8a2d12 | ||
|
|
509709f3fb | ||
|
|
4dbacc7a16 | ||
|
|
ee9f823bdd | ||
|
|
68eb8f1d51 | ||
|
|
fcd91d3f18 | ||
|
|
ec4bb6ee3a | ||
|
|
938c865089 | ||
|
|
2b794c03cc | ||
|
|
94aed14389 | ||
|
|
70e0141f64 | ||
|
|
6f8ad4efe4 | ||
|
|
5d88273910 | ||
|
|
45bc92eb46 | ||
|
|
65fd96b481 | ||
|
|
e37aaee861 | ||
|
|
dca1919c69 | ||
|
|
389d2970c0 | ||
|
|
c396f318ef | ||
|
|
54f48377f0 | ||
|
|
bdde879238 | ||
|
|
d2639ccf22 | ||
|
|
e0c8c8de6a | ||
|
|
b57af6c542 | ||
|
|
b9d30eee88 | ||
|
|
18a2aeadaa | ||
|
|
2ed4869c8b | ||
|
|
980d0977fc | ||
|
|
055a68de7f | ||
|
|
328f7d4f4f | ||
|
|
40492e56a0 | ||
|
|
e709ee6209 | ||
|
|
80ecefb9f7 | ||
|
|
e51b72d2e4 | ||
|
|
cb9ae9b8ec | ||
|
|
3316433f13 | ||
|
|
c98f38cab4 | ||
|
|
88a458a003 | ||
|
|
89fe63c915 | ||
|
|
80ef9e46ab | ||
|
|
259b77dc08 | ||
|
|
55b055b9a0 | ||
|
|
309be41b05 | ||
|
|
1a580366fc | ||
|
|
93ac874312 | ||
|
|
e5d22c78c8 | ||
|
|
8d7a6ef3bb | ||
|
|
f050924234 | ||
|
|
061cb61aa7 | ||
|
|
59e966ede6 | ||
|
|
697ec9fcd5 |
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: new-business-module
|
||||
description: Scaffold a new single-table CRUD business module end to end — migration, Actions-mode model/dto/router, and the sys_menu/sys_api/casbin seed data that makes it show up in the UI with working permissions. Use when the user wants to add a new business table/module to go-admin, not for cross-table or non-CRUD business logic.
|
||||
---
|
||||
|
||||
# 新增业务模块
|
||||
|
||||
给一张新的业务表配齐"能跑、能看见、能授权"的完整闭环:迁移 → 后端代码 → 菜单与权限种子数据。
|
||||
只适用于单表增删改查;跨表事务、外部调用、复杂校验等超出这个范围(见下方"何时不适用")。
|
||||
|
||||
开始前先读 `AGENTS.md`(分层边界、通用 Action 使用前提、命名规则)和 `app/demo/` 下的全部文件——
|
||||
这是可编译、有测试、CI 会跑的参照物,本文与它冲突时以它为准。
|
||||
|
||||
## 何时不适用
|
||||
|
||||
业务超出单表 CRUD(跨表事务、外部服务调用、复杂校验)时,不要用这个 skill 硬套——
|
||||
改成手写 Handler + Service,参照 `app/admin/apis/sys_post.go` 及其 Service,遵守
|
||||
`AGENTS.md` 的分层约束(Api 不碰 Orm,Service 不碰 `gin.Context`,一律用 `e.Orm`)。
|
||||
|
||||
## 步骤
|
||||
|
||||
### 1. 确认表结构
|
||||
|
||||
表结构需符合命名规范:`sys_`/业务前缀 + 下划线(如 `tb_article`)。核对字段是否已有
|
||||
`created_at`/`updated_at`/`deleted_at` 这类约定字段。
|
||||
|
||||
### 2. 写数据库迁移
|
||||
|
||||
放在 `cmd/migrate/migration/version/` 目录(**不是** `version-local/` —— 后者在
|
||||
`.gitignore` 中,提交时会被忽略,`git status` 也看不到)。
|
||||
|
||||
- 文件名前 13 位是时间戳版本号
|
||||
- 已执行过的迁移文件不可修改;需要修正时新增一个迁移
|
||||
- 包名为 `version`
|
||||
|
||||
### 3. 生成 model / dto / router 三个文件(Actions 模式)
|
||||
|
||||
不要手写 Api 与 Service。使用 `common/actions` 的通用 Action,一个模块只需
|
||||
model、dto、router 三个文件,完整写法照抄 `app/demo/` 的结构。
|
||||
|
||||
**关键正确性要求**(这三条是实际出问题最多的地方):
|
||||
|
||||
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`),
|
||||
`TableName()` 必须显式声明——GORM 配置了 `SingularTable`,不会自动推导
|
||||
- **`Generate()` 必须返回副本,不要就地返回**——Action 在并发请求间复用实例,
|
||||
就地返回会导致请求之间串数据;这个问题单人测试时几乎不出现,上线后才暴露
|
||||
- 完成后确认 `cmd/api/` 中已用 `_` 导入新包,否则路由不会被注册
|
||||
|
||||
### 4. 写菜单、接口与权限种子数据
|
||||
|
||||
这一步最容易被漏掉——代码能编译、接口能测通,但界面上看不到菜单、点了按钮说
|
||||
没权限,往往就是漏了这一步。结构参照 `cmd/migrate/migration/version/1786700001000_demo_menu.go`
|
||||
——它是可运行、幂等(用 `upsert`,重复跑不会报错)的真实例子。
|
||||
|
||||
:::danger
|
||||
**但不要照抄它的 import。** 那个文件用的是 `cmd/migrate/migration/models`,
|
||||
只因为它的版本号排在软删除转换(`1786700003000`)之前才是安全的。
|
||||
|
||||
**你新写的迁移版本号在转换之后,必须改用 `app/` 下的运行时模型**
|
||||
(`app/admin/models.SysApi`、`SysMenu`),否则第一条 insert 就会
|
||||
`NOT NULL constraint failed: sys_api.deleted_at`。
|
||||
`TestPostConversionMigrationsAvoidFrozenSeedModels` 会拦住这个错误。
|
||||
:::
|
||||
|
||||
一个模块要在界面上可用,需要四类数据,缺一样都不行:
|
||||
|
||||
| 表 | 作用 |
|
||||
|---|---|
|
||||
| `sys_api` | 后端路由登记,Casbin 据此判定权限 |
|
||||
| `sys_menu` | 侧边栏菜单(目录用 `M`、菜单用 `C`、按钮用 `F`) |
|
||||
| `sys_menu_api_rule` | 菜单与接口的多对多关联,角色保存时据此生成策略 |
|
||||
| `casbin_rule` | 实际生效的权限策略(**不是** `sys_casbin_rule`,那张表的唯一索引在 MySQL 下会超长,不要迁移它) |
|
||||
|
||||
必须核对的两处一致性——**错了不会报错,只会在界面上表现为"看不到/点不动"**:
|
||||
|
||||
- `sys_menu.menu_name` 必须与前端组件的 `defineOptions({ name: 'XxxManage' })` 一致,
|
||||
否则 `keep-alive` 缓存静默失效
|
||||
- 按钮级 `sys_menu.permission`(格式 `模块:资源:操作`)必须与前端
|
||||
`v-permisaction="['模块:资源:操作']"` 完全一致,否则按钮权限判断静默失效
|
||||
|
||||
### 5. 收尾检查
|
||||
|
||||
| 检查项 | 出错后果 |
|
||||
| --- | --- |
|
||||
| `Generate()` 是否返回副本 | 并发请求之间串数据 |
|
||||
| 是否使用 `e.Orm` 而非全局 DB | 多租户下拿到错误的数据库连接 |
|
||||
| `TableName()` 是否显式声明 | GORM 不会自动推导 |
|
||||
| 迁移文件是否放在 `version/` | 放进 `version-local/` 会被忽略,别人拉代码看不到 |
|
||||
| `sys_menu.menu_name` 是否与前端组件 `name` 一致 | keep-alive 缓存静默失效 |
|
||||
| `sys_menu.permission` 是否与前端 `v-permisaction` 一致 | 按钮权限静默失效 |
|
||||
|
||||
跑一遍 `go run -tags sqlite3 . migrate -c config/settings.sqlite.yml` 验证迁移可执行,
|
||||
再 `go run -tags sqlite3 . server -c config/settings.sqlite.yml` 启动服务,用 admin
|
||||
账号登录确认新菜单和按钮权限都出现了。
|
||||
|
||||
如果前端页面还没生成,下一步用 go-admin-ui 仓库里的 `new-list-page` skill——两边靠
|
||||
`sys_menu.permission` / `v-permisaction` 这个字符串对齐。
|
||||
|
||||
> 不要把 `config/settings.yml` 的真实内容贴给 AI 工具——`database.source` 含数据库
|
||||
> 账号密码,`jwt.secret` 泄露后可被用来伪造任意用户的 token。
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 🆕 Create new issue
|
||||
url: http://new-issue.go-admin.dev
|
||||
about: The issue which is not created via http://new-issue.go-admin.dev will be closed immediately.
|
||||
- name: 🆕 创建一个新 Issue
|
||||
url: http://new-issue.go-admin.dev
|
||||
about: 不是用 http://new-issue.go-admin.dev 创建的 issue 会被机器人自动关闭。
|
||||
@@ -0,0 +1,66 @@
|
||||
<!--
|
||||
First of all, thank you for your contribution! 😄
|
||||
|
||||
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
|
||||
|
||||
Before submitting your pull request, please make sure the checklist below is confirmed.
|
||||
|
||||
Your pull requests will be merged after one of the collaborators approve.
|
||||
|
||||
Thank you!
|
||||
|
||||
-->
|
||||
|
||||
[[中文版模板 / Chinese template](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
|
||||
|
||||
### 🤔 This is a ...
|
||||
|
||||
- [ ] New feature
|
||||
- [ ] Bug fix
|
||||
- [ ] Site / documentation update
|
||||
- [ ] Demo update
|
||||
- [ ] Component style update
|
||||
- [ ] TypeScript definition update
|
||||
- [ ] Bundle size optimization
|
||||
- [ ] Performance optimization
|
||||
- [ ] Enhancement feature
|
||||
- [ ] Internationalization
|
||||
- [ ] Refactoring
|
||||
- [ ] Code style optimization
|
||||
- [ ] Test Case
|
||||
- [ ] Branch merge
|
||||
- [ ] Other (about what?)
|
||||
|
||||
### 🔗 Related issue link
|
||||
|
||||
<!--
|
||||
1. Put the related issue or discussion links here.
|
||||
-->
|
||||
|
||||
### 💡 Background and solution
|
||||
|
||||
<!--
|
||||
1. Describe the problem and the scenario.
|
||||
2. GIF or snapshot should be provided if includes UI/interactive modification.
|
||||
3. How to fix the problem, and list the final API implementation and usage sample if that is a new feature.
|
||||
-->
|
||||
|
||||
### 📝 Changelog
|
||||
|
||||
<!--
|
||||
Describe changes from the user side, and list all potential break changes or other risks.
|
||||
--->
|
||||
|
||||
| Language | Changelog |
|
||||
| ---------- | --------- |
|
||||
| 🇺🇸 English | |
|
||||
| 🇨🇳 Chinese | |
|
||||
|
||||
### ☑️ Self-Check before Merge
|
||||
|
||||
⚠️ Please check all items below before review. ⚠️
|
||||
|
||||
- [ ] Doc is updated/provided or not needed
|
||||
- [ ] Demo is updated/provided or not needed
|
||||
- [ ] TypeScript's definition is updated/provided or not needed
|
||||
- [ ] Changelog is provided or not needed
|
||||
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
首先,感谢你的贡献!😄
|
||||
|
||||
新特性请提交至 feature 分支,其余可提交至 master 分支。
|
||||
在维护者审核通过后会合并。
|
||||
请确保填写以下 pull request 的信息,谢谢!~
|
||||
-->
|
||||
|
||||
[[English Template / 英文模板](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE.md)]
|
||||
|
||||
### 🤔 这个变动的性质是?
|
||||
|
||||
- [ ] 新特性提交
|
||||
- [ ] 日常 bug 修复
|
||||
- [ ] 站点、文档改进
|
||||
- [ ] 演示代码改进
|
||||
- [ ] 组件样式/交互改进
|
||||
- [ ] TypeScript 定义更新
|
||||
- [ ] 包体积优化
|
||||
- [ ] 性能优化
|
||||
- [ ] 功能增强
|
||||
- [ ] 国际化改进
|
||||
- [ ] 重构
|
||||
- [ ] 代码风格优化
|
||||
- [ ] 测试用例
|
||||
- [ ] 分支合并
|
||||
- [ ] 其他改动(是关于什么的改动?)
|
||||
|
||||
### 🔗 相关 Issue
|
||||
|
||||
<!--
|
||||
1. 描述相关需求的来源,如相关的 issue 讨论链接。
|
||||
-->
|
||||
|
||||
### 💡 需求背景和解决方案
|
||||
|
||||
<!--
|
||||
1. 要解决的具体问题。
|
||||
2. 列出最终的 API 实现和用法。
|
||||
3. 涉及UI/交互变动需要有截图或 GIF。
|
||||
-->
|
||||
|
||||
### 📝 更新日志
|
||||
|
||||
<!--
|
||||
从用户角度描述具体变化,以及可能的 breaking change 和其他风险。
|
||||
-->
|
||||
|
||||
| 语言 | 更新描述 |
|
||||
| ------- | -------- |
|
||||
| 🇺🇸 英文 | |
|
||||
| 🇨🇳 中文 | |
|
||||
|
||||
### ☑️ 请求合并前的自查清单
|
||||
|
||||
⚠️ 请自检并全部**勾选全部选项**。⚠️
|
||||
|
||||
- [ ] 文档已补充或无须补充
|
||||
- [ ] 代码演示已提供或无须提供
|
||||
- [ ] TypeScript 定义已补充或无须补充
|
||||
- [ ] Changelog 已提供或无须提供
|
||||
@@ -0,0 +1,146 @@
|
||||
name: Build
|
||||
|
||||
# Documentation-only changes skip this workflow entirely.
|
||||
#
|
||||
# A push to master here does not just build - it pushes an image, runs the
|
||||
# migrations and restarts the demo container, so the site takes a short outage.
|
||||
# Paying that for a README edit is waste at best; at worst a deploy fails for a
|
||||
# reason unrelated to anything in the change. Code coverage is unaffected,
|
||||
# because go.yml still builds every push and pull request.
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
- 'LICENSE*'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
- 'LICENSE*'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
|
||||
# One deploy at a time. Two merges seconds apart raced here: both runs did
|
||||
# docker rm -f then docker run, the second removed the container the first had
|
||||
# just created, and the first's docker run then failed on a name conflict -
|
||||
# leaving the demo on the older image with a red deploy.
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
IMAGE_NAME: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api # 镜像名称
|
||||
TAG: ${{ github.sha }}
|
||||
IMAGE_NAME_TAG: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api:${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: 1.26.5
|
||||
|
||||
- name: Tidy
|
||||
run: go mod tidy
|
||||
|
||||
- name: Build
|
||||
run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main .
|
||||
|
||||
# 以下推镜像与重启步骤仅在 master 收到 push 时执行。
|
||||
# pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的
|
||||
# PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务,
|
||||
# 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。
|
||||
- name: Build the Docker image and push
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
||||
echo "************ docker login end"
|
||||
docker build -t go-admin-api:latest .
|
||||
echo "************ docker build end"
|
||||
docker tag go-admin-api ${{ env.IMAGE_NAME_TAG }}
|
||||
echo "************ docker tag end"
|
||||
docker images
|
||||
echo "************ docker images end"
|
||||
docker push ${{ env.IMAGE_NAME_TAG }} # 推送
|
||||
echo "************ docker push end"
|
||||
|
||||
- name: Restart server # 第五步,重启服务
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
|
||||
env:
|
||||
GITHUB_SHA_X: ${GITHUB_SHA}
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
|
||||
username: ${{ secrets.SSH_USERNAME }}
|
||||
key: ${{ secrets.DEPLOY_KEY }}
|
||||
# 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器
|
||||
#
|
||||
# 配置从宿主机挂载,不使用镜像里的那份:演示站连的是托管数据库,
|
||||
# 而 config/settings.demo.yml 会随仓库公开、也会打进镜像,凭据不能写在那里。
|
||||
# 镜像里那份保持 sqlite,供 clone 仓库的人开箱即用。
|
||||
#
|
||||
# 路径本身走 secret:它不是凭据,但本仓库公开,没有理由把服务器的
|
||||
# 目录结构一并公布。DEMO_CONFIG_PATH 指向宿主机上那份配置。
|
||||
#
|
||||
# 顺序是有意的:迁移先跑,跑不过就保持现有版本不动;
|
||||
# 旧容器改名保留而不是删除,新容器不健康时能原样恢复。
|
||||
# 健康检查两条都要过——HTTP 活着不代表数据库通了。
|
||||
script: |
|
||||
set -u
|
||||
CFG="${{ secrets.DEMO_CONFIG_PATH }}"
|
||||
IMG="${{ env.IMAGE_NAME_TAG }}"
|
||||
NAME=go-admin-api
|
||||
PREV="$NAME-prev"
|
||||
|
||||
test -f "$CFG" || { echo "宿主机配置缺失,中止部署"; exit 1; }
|
||||
|
||||
sudo docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
||||
sudo docker pull "$IMG" || { echo "拉取镜像失败,中止部署"; exit 1; }
|
||||
|
||||
# 迁移用新镜像跑。失败时线上仍是旧版本配旧 schema,是自洽的;
|
||||
# 硬切过去才会得到代码与表对不上的服务。
|
||||
if ! sudo docker run --rm -v "$CFG":/config/settings.yml:ro "$IMG" \
|
||||
/main migrate -c /config/settings.yml; then
|
||||
echo "迁移失败,保持现有版本"; exit 1
|
||||
fi
|
||||
|
||||
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$NAME"; then
|
||||
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
||||
sudo docker rename "$NAME" "$PREV"
|
||||
sudo docker stop "$PREV" >/dev/null
|
||||
fi
|
||||
|
||||
sudo docker run -d -p 8000:8000 \
|
||||
-v "$CFG":/config/settings.yml:ro \
|
||||
--name "$NAME" "$IMG"
|
||||
|
||||
ok=0
|
||||
for i in $(seq 1 20); do
|
||||
sleep 3
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1:8000/api/v1/captcha 2>/dev/null || true)
|
||||
if [ "$code" = "200" ] && sudo docker logs "$NAME" 2>&1 | grep -q 'connect success'; then
|
||||
ok=1; echo "健康检查通过(第 $i 次探测)"; break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$ok" = "1" ]; then
|
||||
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
||||
else
|
||||
echo "健康检查失败,回滚到上一版本"
|
||||
sudo docker logs --tail 40 "$NAME" 2>&1 || true
|
||||
sudo docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$PREV"; then
|
||||
sudo docker rename "$PREV" "$NAME"
|
||||
sudo docker start "$NAME" >/dev/null
|
||||
echo "已恢复"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,51 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
|
||||
+59
-12
@@ -2,9 +2,13 @@ name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ master, dev ]
|
||||
tags: [ 'v*', '[0-9]*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -13,22 +17,65 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.13
|
||||
uses: actions/setup-go@v1
|
||||
- name: Set up Go 1.26
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: 1.13
|
||||
go-version: 1.26.5
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
run: go mod tidy
|
||||
|
||||
# go build does not compile _test.go, so building alone never ran a single
|
||||
# test. This is the only workflow that fires on every push and pull request,
|
||||
# which makes it the one place a test gate belongs.
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
run: make build
|
||||
|
||||
# Fails the build on the silent-failure classes listed in
|
||||
# tools/checksilent, one of which is the contract boundary: nothing under
|
||||
# common/ may import app/. A boundary that is only written down erodes; this
|
||||
# is what keeps it true.
|
||||
- name: Silent-failure checks
|
||||
run: make checksilent
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=schedule
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=,format=long,enable=true,priority=100
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
context: .
|
||||
file: scripts/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
+33
-3
@@ -1,12 +1,42 @@
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
static/uploadfile
|
||||
main.exe
|
||||
*.exe
|
||||
go-admin
|
||||
go-admin.exe
|
||||
# `go build ./tools/checksilent` drops the binary here, next to the one for the
|
||||
# server. Anchored with a leading slash: unanchored, the same pattern matches
|
||||
# tools/checksilent/ as well and the tool's own source never gets committed.
|
||||
/checksilent
|
||||
temp/
|
||||
!temp
|
||||
vendor
|
||||
config/settings.dev.yml
|
||||
go-admin
|
||||
middleware/demo.go
|
||||
config/settings.dev.b.yml
|
||||
config/settings.dev.*.yml
|
||||
config/settings.dev.*.yml.log
|
||||
temp/logs
|
||||
config/settings.dev.yml.log
|
||||
config/settings.b.dev.yml
|
||||
cmd/migrate/migration/version-local/*
|
||||
!cmd/migrate/migration/version-local/doc.go
|
||||
|
||||
config/settings.deva.yml
|
||||
go-admin-server
|
||||
CLAUDE.md
|
||||
# Everything under .claude is private by default. Skills meant for people using
|
||||
# go-admin are re-included one directory at a time, so a personal one dropped in
|
||||
# here is never committed by accident.
|
||||
.claude/*
|
||||
!.claude/skills/
|
||||
.claude/skills/*
|
||||
!.claude/skills/new-business-module/
|
||||
config/settings.local.dev.yml
|
||||
|
||||
# Go workspace files. They exist to point this module at a local checkout of
|
||||
# go-admin-core while the two are developed together, which is a private
|
||||
# arrangement between one machine's directories - committing one would break
|
||||
# the build for everyone else.
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
# AGENTS.md — go-admin 后端
|
||||
|
||||
> 给 AI 编码工具与新贡献者的约定。**只写"不遵守就会出错"的规则**;技术栈版本以
|
||||
> `go.mod` 为准,命令以 `Makefile` 为准,此处不复述,避免与代码脱节。
|
||||
>
|
||||
> 标准 CRUD 模块的完整写法见 **`app/demo/`** —— 那是可编译、有测试、CI 会跑的参照物。
|
||||
> 本文与它冲突时,以 `app/demo/` 为准。
|
||||
|
||||
## 分层
|
||||
|
||||
```
|
||||
Router → Api → Service → Model
|
||||
路由注册 参数绑定 业务逻辑 GORM 结构体
|
||||
中间件链 调用 Service 操作数据库 TableName()
|
||||
```
|
||||
|
||||
对应目录:`app/{模块}/router|apis|service|models`,DTO 位于 `service/dto`。
|
||||
|
||||
**不可跨层**:Api 不直接操作 `Orm`,Service 不接触 `gin.Context`。
|
||||
|
||||
## 优先使用通用 Action
|
||||
|
||||
单表 CRUD **不要手写 Handler 与 Service**。`common/actions` 提供的五个
|
||||
Action 已覆盖参数绑定、数据权限过滤、操作人注入、分页与错误响应:
|
||||
|
||||
```go
|
||||
r := v1.Group("/demo-product").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
m := &models.DemoProduct{}
|
||||
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
|
||||
list := make([]models.DemoProduct, 0); return &list
|
||||
}))
|
||||
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
|
||||
return &models.DemoProduct{}
|
||||
}))
|
||||
r.POST("", actions.CreateAction(new(dto.DemoProductControl)))
|
||||
r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl)))
|
||||
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById)))
|
||||
}
|
||||
```
|
||||
|
||||
这样一个模块只需 **model + dto + router** 三个文件,完整示例见 `app/demo/`。
|
||||
|
||||
使用通用 Action 的前提:
|
||||
|
||||
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`)
|
||||
- 列表 DTO 实现 `dto.Index`,增改删 DTO 实现 `dto.Control`
|
||||
- **所有 `Generate()` 必须返回副本** —— Action 在并发请求间复用实例,
|
||||
就地返回会串数据(`app/demo` 的测试锁定了这一点)
|
||||
- 详情/删除 DTO 内嵌 `dto.ObjectById` 即可继承 `Bind` 与 `GetId`,无需重写
|
||||
|
||||
仅当业务超出单表 CRUD(跨表事务、外部调用、复杂校验)时才自行编写 Handler
|
||||
与 Service,写法见下。
|
||||
|
||||
## Api 层(仅在通用 Action 不适用时)
|
||||
|
||||
结构体嵌入 `api.Api`,链式初始化后**必须检查 `Errors`**:
|
||||
|
||||
```go
|
||||
func (e SysPost) GetPage(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req := dto.SysPostPageReq{}
|
||||
err := e.MakeContext(c).MakeOrm().Bind(&req, binding.Form).MakeService(&s.Service).Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
// ... 调用 s.GetPage(...)
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
```
|
||||
|
||||
响应一律走 `e.OK` / `e.PageOK` / `e.Error`,不要自行 `c.JSON`。
|
||||
|
||||
## Service 层(仅在通用 Action 不适用时)
|
||||
|
||||
结构体嵌入 `service.Service`(持有 `Orm` 与 `Log`)。查询通过 Scopes 组合:
|
||||
|
||||
```go
|
||||
err = e.Orm.Model(&data).Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()), // 由 search tag 生成 WHERE
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
actions.Permission(data.TableName(), p), // 数据权限,列表/详情必须带
|
||||
).Find(list).Limit(-1).Offset(-1).Count(count).Error
|
||||
```
|
||||
|
||||
**遗漏 `actions.Permission` 会使数据权限配置静默失效** —— 这是最容易出的错。
|
||||
|
||||
错误一律 `return err` 向上传递,日志用 `e.Log.Errorf`,不使用 `panic`。
|
||||
|
||||
## DTO
|
||||
|
||||
搜索条件由 tag 声明,`MakeCondition` 据此拼 SQL:
|
||||
|
||||
```go
|
||||
type SysPostPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post"`
|
||||
}
|
||||
func (m *SysPostPageReq) GetNeedSearch() interface{} { return *m }
|
||||
```
|
||||
|
||||
`type` 可选:`exact` `iexact` `contains` `gt` `gte` `lt` `lte` `order` `left`(联表)。
|
||||
|
||||
## Model
|
||||
|
||||
```go
|
||||
type SysPost struct {
|
||||
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"`
|
||||
// ... 业务字段
|
||||
models.ControlBy // CreateBy / UpdateBy
|
||||
models.ModelTime // CreatedAt / UpdatedAt / DeletedAt
|
||||
}
|
||||
func (SysPost) TableName() string { return "sys_post" }
|
||||
```
|
||||
|
||||
`TableName()` 必须显式声明(GORM 配置了 `SingularTable`,不会自动推导复数)。
|
||||
|
||||
## 公共契约面
|
||||
|
||||
第三方应用(`app/` 下的业务模块)可以稳定依赖哪些包、路由与迁移怎么注册、
|
||||
哪些约束是硬的,见 `docs/contract.md`。
|
||||
|
||||
两条与主仓贡献者直接相关的:
|
||||
|
||||
- **`common/`、`core/` 不得 import `app/`** —— `make checksilent` 在 CI 里守着,违反即红。
|
||||
- **注册类 API(`AppRouters` / `sdk.Runtime.SetAppRouters` / `migration.ForApp`)
|
||||
只允许在 `init()` 中调用** —— 注册期靠 Go 的包初始化顺序保证无并发写,
|
||||
core 侧的 setter 没有加锁。
|
||||
|
||||
## 路由注册
|
||||
|
||||
通过 `init()` 自注册,不在中心文件手工添加:
|
||||
|
||||
```go
|
||||
func init() { routerCheckRole = append(routerCheckRole, registerSysPostRouter) }
|
||||
|
||||
func registerSysPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysPost{}
|
||||
r := v1.Group("/post").
|
||||
Use(authMiddleware.MiddlewareFunc()).
|
||||
Use(middleware.AuthCheckRole()). // Casbin 鉴权
|
||||
Use(actions.PermissionAction()) // 注入数据权限
|
||||
{ r.GET("", api.GetPage); r.POST("", api.Insert); /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
新增路由文件后,需确认 `cmd/api/` 中已用 `_` 导入该包。
|
||||
|
||||
## 命名
|
||||
|
||||
| 对象 | 规则 | 示例 |
|
||||
|---|---|---|
|
||||
| 数据表 | `sys_` 前缀 + 下划线 | `sys_post` |
|
||||
| API 路径 | `/api/v1/` + kebab-case | `/api/v1/sys-user` |
|
||||
| DTO | `{Model}{Action}Req` | `SysPostPageReq` |
|
||||
| 权限标识 | `模块:资源:操作` | `admin:sysPost:add` |
|
||||
|
||||
权限标识需与前端 `v-permisaction` 一致,并写入 `sys_menu` 种子数据——完整可运行的
|
||||
参照见 `cmd/migrate/migration/version/1786700001000_demo_menu.go`(sys_api /
|
||||
sys_menu / sys_menu_api_rule / casbin_rule 四张表如何配齐,用的是幂等 upsert,
|
||||
可以直接照抄结构)。
|
||||
|
||||
## Swagger
|
||||
|
||||
Handler 必须带完整注解,`go generate` 会据此生成文档:
|
||||
|
||||
```go
|
||||
// @Summary 岗位列表
|
||||
// @Tags 岗位
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/v1/post [get]
|
||||
// @Security Bearer
|
||||
```
|
||||
|
||||
## 本地运行
|
||||
|
||||
**配置 `driver: sqlite3` 时必须带构建标签**,否则启动即 panic:
|
||||
|
||||
```bash
|
||||
go run -tags sqlite3 . migrate -c config/settings.sqlite.yml
|
||||
go run -tags sqlite3 . server -c config/settings.sqlite.yml
|
||||
```
|
||||
|
||||
原因:`common/database/open.go` 带 `//go:build !sqlite3`,不加标签时编进的是
|
||||
不含 sqlite3 的版本,`opens["sqlite3"]` 为 nil,调用时在 nil 函数上崩溃。
|
||||
报错信息不会提到构建标签,容易误判成环境损坏。MySQL / PostgreSQL 无此问题。
|
||||
|
||||
对应 `Makefile` 的 `build-sqlite` 目标。
|
||||
|
||||
## 数据库迁移
|
||||
|
||||
文件名前 13 位为时间戳版本号。**已执行过的迁移文件不可修改** ——
|
||||
`sys_migration` 表按版本号去重,改动不会重跑,只能新增一个迁移来修正。
|
||||
|
||||
放哪个目录取决于身份:
|
||||
|
||||
| 目录 | 用途 | 是否入库 |
|
||||
|---|---|---|
|
||||
| `version/` | 框架自带迁移,随仓库分发给所有使用者 | 是 |
|
||||
| `version-local/` | 使用者自己项目的迁移 | 否(已在 `.gitignore`) |
|
||||
|
||||
**向本仓库提交迁移必须放 `version/`** —— 放进 `version-local/` 会被忽略掉,
|
||||
`git status` 看不到,PR 里也不会出现。两个目录的包名分别是 `version` 与
|
||||
`version_local`(后者与目录名不一致,因为标识符不能含连字符)。
|
||||
|
||||
### 写种子数据用哪个 models 包
|
||||
|
||||
`1786700003000` 之后新增的迁移,**种子数据要用 `app/` 下的运行时模型**
|
||||
(如 `app/admin/models.SysApi`、`SysMenu`),**不要用 `cmd/migrate/migration/models`**。
|
||||
|
||||
后者的 `ModelTime` 声明的是可空的 `gorm.DeletedAt`,这对它之前的迁移是对的(那正是
|
||||
当时列的形状),转换之后就不再成立,两个方向都会出问题:
|
||||
|
||||
- **写**:往 NOT NULL 列里塞 NULL,第一条 insert 就 `NOT NULL constraint failed`
|
||||
- **读**:GORM 拼 `WHERE deleted_at IS NULL`,而活跃行存的是 `0`,静默查不到——
|
||||
照抄 `demo_menu.go` 的授权段落会因此跳过授权,菜单建好、权限没授、迁移仍记为成功
|
||||
|
||||
干净库跑不出这个问题,今天所有用该包的迁移都排在转换之前。完整推导见
|
||||
`schema_coverage_test.go` 里 `TestPostConversionMigrationsAvoidFrozenSeedModels`
|
||||
的注释,那个测试也守着这条边界。
|
||||
|
||||
## 静默失败校验
|
||||
|
||||
`make checksilent` 检查六类**不报错、不记日志、行为悄悄变得不对**的问题,
|
||||
CI 会跑,命中 ERROR 即失败:
|
||||
|
||||
| 检查 | 级别 | 静默后果 |
|
||||
|---|---|---|
|
||||
| `modeltime-mix` | ERROR | 两个 `ModelTime` 混用,整张表查不到数据 |
|
||||
| `menu-sort-overflow` | ERROR | 菜单 `sort` 超 127,MySQL tinyint 拒绝写入,迁移中断 |
|
||||
| `config-value-truncation` | ERROR | `sys_config.config_value` 超 255 字符被静默截断 |
|
||||
| `menu-id-collision` | ERROR | 两个模块硬编码同一菜单 ID,互相覆盖 |
|
||||
| `contract-import-boundary` | ERROR | 契约包 import `app/`,应用无法独立编译 |
|
||||
| `menu-name-mismatch` | WARN | 菜单名与前端组件 `name` 不一致,keep-alive 缓存静默失效 |
|
||||
|
||||
最后一条要跨仓库比对,只能做正则启发式,因此是 WARN,**不影响退出码**,
|
||||
且默认跳过;要跑它得指定前端目录:
|
||||
|
||||
```bash
|
||||
make checksilent UI_DIR=../go-admin-ui/src
|
||||
```
|
||||
|
||||
升级门槛:连续 2 个发版周期零误报后转为 ERROR。
|
||||
|
||||
## 提交规范
|
||||
|
||||
格式 `type+emoji: 描述`:
|
||||
|
||||
`feat✨` `fix🐛` `style💄` `docs📝` `perf👌` `test✅` `refactor🎨` `chore🔧`
|
||||
|
||||
一个提交只做一件事。改动跨越多个语义时拆分提交,不要混在一起。
|
||||
|
||||
## 红线
|
||||
|
||||
- 不使用全局 DB 变量,一律用 `e.Orm`(来自请求上下文,多租户依赖它)
|
||||
- 不在 Service 中引用 `gin.Context`
|
||||
- 生产部署前确认 `mode: prod` 且已修改 `jwt.secret`(dev 模式下 token 几乎不过期)
|
||||
- 不提交 `config/settings.yml` 中的真实凭据
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
FROM alpine
|
||||
|
||||
# ENV GOPROXY https://goproxy.cn/
|
||||
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
|
||||
RUN apk update --no-cache
|
||||
RUN apk add --update gcc g++ libc6-compat
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ Asia/Shanghai
|
||||
|
||||
COPY ./main /main
|
||||
COPY ./config/settings.demo.yml /config/settings.yml
|
||||
COPY ./go-admin-db.db /go-admin-db.db
|
||||
EXPOSE 8000
|
||||
RUN chmod +x /main
|
||||
CMD ["/main","server","-c", "/config/settings.yml"]
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 wenjianzhang
|
||||
Copyright (c) 2026 go-admin-team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -2,13 +2,71 @@ PROJECT:=go-admin
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
CGO_ENABLED=0 go build -o go-admin main.go
|
||||
CGO_ENABLED=0 go build -ldflags="-w -s" -a -installsuffix "" -o go-admin .
|
||||
|
||||
# make build-linux
|
||||
build-linux:
|
||||
@docker build -t go-admin:latest .
|
||||
@echo "build successful"
|
||||
|
||||
build-sqlite:
|
||||
go build -tags sqlite3 -o go-admin main.go
|
||||
#.PHONY: test
|
||||
#test:
|
||||
# go test -v ./... -cover
|
||||
go build -tags sqlite3 -ldflags="-w -s" -a -installsuffix -o go-admin .
|
||||
|
||||
# make run
|
||||
run:
|
||||
# delete go-admin-api container
|
||||
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
|
||||
|
||||
# 启动方法一 run go-admin-api container docker-compose 启动方式
|
||||
# 进入到项目根目录 执行 make run 命令
|
||||
@docker-compose up -d
|
||||
|
||||
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际绝对路径
|
||||
#@docker run --name=go-admin -p 8000:8000 -v /home/code/go/src/go-admin/go-admin/config:/go-admin-api/config -v /home/code/go/src/go-admin/go-admin-api/static:/go-admin/static -v /home/code/go/src/go-admin/go-admin/temp:/go-admin-api/temp -d --restart=always go-admin:latest
|
||||
|
||||
@echo "go-admin service is running..."
|
||||
|
||||
# delete Tag=<none> 的镜像
|
||||
@docker image prune -f
|
||||
@docker ps -a | grep "go-admin"
|
||||
|
||||
stop:
|
||||
# delete go-admin-api container
|
||||
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker-compose down; fi
|
||||
#@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
|
||||
#@echo "go-admin stop success"
|
||||
|
||||
|
||||
# -race is worth the extra minute here: common/actions reuses model instances
|
||||
# across concurrent requests, so a Generate() that returns in place instead of
|
||||
# a copy leaks data between them - and that is invisible to a single-threaded
|
||||
# test run.
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race -cover ./...
|
||||
|
||||
# Reports the failures that do not announce themselves - see
|
||||
# tools/checksilent. Exits non-zero on an ERROR; the one WARN-level check
|
||||
# prints and does not fail the build.
|
||||
#
|
||||
# Pass UI_DIR to enable the cross-repository menu-name check, which is skipped
|
||||
# without it: make checksilent UI_DIR=../go-admin-ui/src
|
||||
.PHONY: checksilent
|
||||
checksilent:
|
||||
ifdef UI_DIR
|
||||
go run ./tools/checksilent -ui-dir $(UI_DIR)
|
||||
else
|
||||
go run ./tools/checksilent
|
||||
endif
|
||||
|
||||
#.PHONY: docker
|
||||
#docker:
|
||||
# docker build . -t go-admin:latest
|
||||
|
||||
# make deploy
|
||||
deploy:
|
||||
|
||||
#@git checkout master
|
||||
#@git pull origin master
|
||||
make build-linux
|
||||
make run
|
||||
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
# go-admin
|
||||
|
||||
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
|
||||
|
||||
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
[](https://github.com/go-admin-team/go-admin/releases)
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
|
||||
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | 简体中文 | [繁體中文](https://github.com/go-admin-team/go-admin/blob/master/README.zh-TW.md) | [日本語](https://github.com/go-admin-team/go-admin/blob/master/README.ja-JP.md)
|
||||
|
||||
基于Gin + Vue + Element UI OR Arco Design OR Ant Design的前后端分离权限管理系统,系统初始化极度简单,只需要配置文件中,修改数据库连接,系统支持多指令操作,迁移指令可以让初始化数据库信息变得更简单,服务指令可以很简单的启动api服务
|
||||
|
||||
[在线文档](https://www.go-admin.pro)
|
||||
|
||||
[前端项目](https://github.com/go-admin-team/go-admin-ui)
|
||||
|
||||
[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
|
||||
## 🎬 在线体验
|
||||
|
||||
Element Plus vue3 体验:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
antd 体验(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 遵循 RESTful API 设计规范
|
||||
|
||||
- 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证、跨域、访问日志、追踪ID等)
|
||||
|
||||
- 基于Casbin的 RBAC 访问控制模型
|
||||
|
||||
- JWT 认证
|
||||
|
||||
- 支持 Swagger 文档(基于swaggo)
|
||||
|
||||
- 基于 GORM 的数据库存储,可扩展多种类型数据库
|
||||
|
||||
- 配置文件简单的模型映射,快速能够得到想要的配置
|
||||
|
||||
- 代码生成工具
|
||||
|
||||
- 表单构建工具
|
||||
|
||||
- 多指令模式
|
||||
|
||||
- 多租户的支持
|
||||
|
||||
- TODO: 单元测试
|
||||
|
||||
## 🎁 内置
|
||||
|
||||
1. 多租户:系统默认支持多租户,按库分离,一个库一个租户。
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识,接口权限等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
9. 登录日志:系统登录日志记录查询包含登录异常。
|
||||
1. 接口文档:根据业务代码自动生成相关的api接口文档。
|
||||
1. 代码生成:根据数据表结构生成对应的增删改查相对应业务,全程可视化操作,让基本业务可以零代码实现。
|
||||
1. 表单构建:自定义页面样式,拖拉拽实现页面布局。
|
||||
1. 服务监控:查看一些服务器的基本信息。
|
||||
1. 内容管理:demo功能,下设分类管理、内容管理。可以参考使用方便快速入门。
|
||||
1. 定时任务:自动化任务,目前支持接口调用和函数调用。
|
||||
|
||||
## 准备工作
|
||||
|
||||
你需要在本地安装 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
|
||||
|
||||
同时配套了系列教程包含视频和文档,如何从下载完成到熟练使用,强烈建议大家先看完这些教程再来实践本项目!!!
|
||||
|
||||
### 轻松实现go-admin写出第一个应用 - 文档教程
|
||||
|
||||
[步骤一 - 基础内容介绍](https://www.go-admin.pro/guide/intro/tutorial01.html)
|
||||
|
||||
[步骤二 - 实际应用 - 编写增删改查](https://www.go-admin.pro/guide/intro/tutorial02.html)
|
||||
|
||||
### 手把手教你从入门到放弃 - 视频教程
|
||||
|
||||
[如何启动go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
|
||||
|
||||
[使用生成工具轻松实现业务](https://www.bilibili.com/video/BV1Dg4y1i79D)
|
||||
|
||||
[v1.1.0版本代码生成工具-释放双手](https://www.bilibili.com/video/BV1N54y1i71P) [进阶]
|
||||
|
||||
[多命令启动方式讲解以及IDE配置](https://www.bilibili.com/video/BV1Fg4y1q7ph)
|
||||
|
||||
[go-admin菜单的配置说明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
|
||||
|
||||
[如何配置菜单信息以及接口信息](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
|
||||
|
||||
[go-admin权限配置使用说明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
|
||||
|
||||
[go-admin数据权限使用说明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
|
||||
|
||||
**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr ,视频教程和文档持续更新中**
|
||||
|
||||
## 📦 本地开发
|
||||
|
||||
### 环境要求
|
||||
|
||||
go 1.26.5
|
||||
|
||||
node版本: v22+(推荐 v24 LTS)
|
||||
|
||||
包管理器: pnpm v9+(UI 项目使用 pnpm)
|
||||
|
||||
### 开发目录创建
|
||||
|
||||
```bash
|
||||
|
||||
# 创建开发目录
|
||||
mkdir goadmin
|
||||
cd goadmin
|
||||
```
|
||||
|
||||
### 获取代码
|
||||
|
||||
> 重点注意:两个项目必须放在同一文件夹下;
|
||||
|
||||
```bash
|
||||
# 获取后端代码
|
||||
git clone https://github.com/go-admin-team/go-admin.git
|
||||
|
||||
# 获取前端代码
|
||||
git clone https://github.com/go-admin-team/go-admin-ui.git
|
||||
|
||||
```
|
||||
|
||||
### 启动说明
|
||||
|
||||
#### 服务端启动说明
|
||||
|
||||
```bash
|
||||
# 进入 go-admin 后端项目
|
||||
cd ./go-admin
|
||||
|
||||
# 更新整理依赖
|
||||
go mod tidy
|
||||
|
||||
# 编译项目
|
||||
go build
|
||||
|
||||
# 修改配置
|
||||
# 文件路径 go-admin/config/settings.yml
|
||||
vi ./config/settings.yml
|
||||
|
||||
# 1. 配置文件中修改数据库信息
|
||||
# 注意: settings.database 下对应的配置数据
|
||||
# 2. 确认log路径
|
||||
```
|
||||
|
||||
⚠️注意 在windows环境如果没有安装中CGO,会出现这个问题;
|
||||
|
||||
```bash
|
||||
E:\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
D:\Code\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
|
||||
```
|
||||
|
||||
[解决cgo问题进入](https://www.go-admin.pro/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
|
||||
|
||||
|
||||
#### 初始化数据库,以及服务启动
|
||||
|
||||
``` bash
|
||||
# 首次配置需要初始化数据库资源信息
|
||||
# macOS or linux 下使用
|
||||
$ ./go-admin migrate -c config/settings.dev.yml
|
||||
|
||||
# ⚠️注意:windows 下使用
|
||||
$ go-admin.exe migrate -c config/settings.dev.yml
|
||||
|
||||
|
||||
# 启动项目,也可以用IDE进行调试
|
||||
# macOS or linux 下使用
|
||||
$ ./go-admin server -c config/settings.yml
|
||||
|
||||
|
||||
# ⚠️注意:windows 下使用
|
||||
$ go-admin.exe server -c config/settings.yml
|
||||
```
|
||||
|
||||
#### sys_api 表的数据如何添加
|
||||
|
||||
在项目启动时,使用`-a true` 系统会自动添加缺少的接口数据
|
||||
```bash
|
||||
./go-admin server -c config/settings.yml -a true
|
||||
```
|
||||
|
||||
#### 使用docker 编译启动
|
||||
|
||||
```shell
|
||||
# 编译镜像
|
||||
docker build -t go-admin .
|
||||
|
||||
# 启动容器,第一个go-admin是容器名字,第二个go-admin是镜像名称
|
||||
# -v 映射配置文件 本地路径:容器路径
|
||||
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
|
||||
```
|
||||
|
||||
#### 文档生成
|
||||
|
||||
```bash
|
||||
go generate
|
||||
```
|
||||
|
||||
#### 交叉编译
|
||||
|
||||
```bash
|
||||
# windows
|
||||
env GOOS=windows GOARCH=amd64 go build main.go
|
||||
|
||||
# or
|
||||
# linux
|
||||
env GOOS=linux GOARCH=amd64 go build main.go
|
||||
```
|
||||
|
||||
### UI交互端启动说明
|
||||
|
||||
```bash
|
||||
# 安装 pnpm(若未安装)
|
||||
npm install -g pnpm
|
||||
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
|
||||
# 国内网络可指定镜像源加速
|
||||
pnpm install --registry=https://registry.npmmirror.com
|
||||
|
||||
# 启动服务
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 📨 互动
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
|
||||
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
|
||||
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>微信</td>
|
||||
<td>公众号🔥🔥🔥</td>
|
||||
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
|
||||
<td>哔哩哔哩🔥🔥🔥</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 💎 贡献者
|
||||
|
||||
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
|
||||
## JetBrains 开源证书支持
|
||||
|
||||
`go-admin` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
|
||||
|
||||
## 🤝 特别感谢
|
||||
|
||||
1. [ant-design](https://github.com/ant-design/ant-design)
|
||||
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
|
||||
2. [arco-design](https://github.com/arco-design/arco-design)
|
||||
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
|
||||
4. [gin](https://github.com/gin-gonic/gin)
|
||||
5. [casbin](https://github.com/casbin/casbin)
|
||||
6. [spf13/viper](https://github.com/spf13/viper)
|
||||
7. [gorm](https://github.com/go-gorm/gorm)
|
||||
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
9. [golang-jwt](https://github.com/golang-jwt/jwt)
|
||||
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
12. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
|
||||
|
||||
## 🤟 打赏
|
||||
|
||||
> 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
|
||||
|
||||
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
|
||||
|
||||
## 🤝 链接
|
||||
|
||||
- [mss-boot-io](https://docs.mss-boot-io.top/)
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2026 wenjianzhang
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
# go-admin
|
||||
|
||||
 
|
||||
|
||||
|
||||
English | [简体中文](./README.zh-CN.md)
|
||||
|
||||
##### Gin + Vue + Element UI based scaffolding for front and back separation management system
|
||||
|
||||
|
||||
## ✨ Feature
|
||||
|
||||
- Follow RESTful API design specifications
|
||||
|
||||
- Provides rich middleware support based on GIN WEB API framework (user authentication, cross domain, access log, tracking ID, etc.)
|
||||
|
||||
- Casbin-based RBAC access control model
|
||||
|
||||
- JWT certification
|
||||
|
||||
- Support Swagger documentation (based on swaggo)
|
||||
|
||||
- GORM-based database storage that can expand many types of databases
|
||||
|
||||
- Simple model mapping of configuration files to quickly get the desired configuration
|
||||
|
||||
- TODO: unit test
|
||||
|
||||
|
||||
## 🎁 Built-in functions
|
||||
|
||||
1. User management: The user is the system operator. This function mainly completes the system user configuration.
|
||||
2. Department management: configure the system organization (company, department, group), and display the tree structure to support data permissions.
|
||||
3. Post management: Configure system users to hold positions.
|
||||
4. Menu management: configure system menus, operation permissions, button permission labels, etc.
|
||||
5. Role management: role menu permissions assignment, setting roles to divide data range permissions by organization.
|
||||
6. Dictionary management: to maintain some fixed data often used in the system.
|
||||
7. Parameter management: Dynamically configure common parameters for the system.
|
||||
8. Operation log: system normal operation log record and query; system exception information log record and query.
|
||||
9. Login log: The system login log record query contains login exceptions.
|
||||
10. System interface: Automatically generate related api interface documents according to business code.
|
||||
|
||||
## Configuration details
|
||||
|
||||
1. Configuration file description
|
||||
```yml
|
||||
settings:
|
||||
application:
|
||||
# Project launch environment
|
||||
env: dev
|
||||
# When env: demo, prompts for request operations other than GET
|
||||
envmsg: "谢谢您的参与,但为了大家更好的体验,所以本次提交就算了吧!"
|
||||
# Host IP or domain name, default 0.0.0.0
|
||||
host: 0.0.0.0
|
||||
# Whether to initialize the database structure and basic data; true: required; false: not required
|
||||
isinit: false
|
||||
# JWT encrypted string
|
||||
jwtsecret: 123abc
|
||||
# log storage path
|
||||
logpath: temp/logs/log.log
|
||||
# application name
|
||||
name: go-admin
|
||||
# application port
|
||||
port: 8000
|
||||
readtimeout: 1
|
||||
writertimeout: 2
|
||||
database:
|
||||
# database name
|
||||
database: dbname
|
||||
# database type
|
||||
dbtype: mysql
|
||||
# database host
|
||||
host: 127.0.0.1
|
||||
# database password
|
||||
password: password
|
||||
# database port
|
||||
port: 3306
|
||||
# database username
|
||||
username: root
|
||||
redis:
|
||||
# redis addresss
|
||||
addr: 0.0.0.0:6379
|
||||
# db
|
||||
db: 0
|
||||
# password
|
||||
password: password
|
||||
# read timeout
|
||||
readtimeout: 50
|
||||
```
|
||||
|
||||
2. file path go-admin/config/settings.yml
|
||||
|
||||
|
||||
## 📦 evelopment
|
||||
|
||||
|
||||
First start instructions
|
||||
|
||||
```bash
|
||||
# Get the code
|
||||
git clone https://github.com/wenjianzhang/go-admin.git
|
||||
|
||||
# Enter working path
|
||||
cd ./go-admin
|
||||
|
||||
# Build the project
|
||||
go build
|
||||
|
||||
# Change setting
|
||||
vi ./config/setting.yml (Note: Change isinit and database connection)
|
||||
|
||||
# 1. Database information in the configuration file
|
||||
# Note: the corresponding configuration data under settings.database
|
||||
# 2. Confirm database initialization parameters
|
||||
# Note: If this is the first time settings.application.isinit is set, please set the current value to true, the system will automatically initialize the database structure and basic data information;
|
||||
# 3. Confirm the log path
|
||||
|
||||
|
||||
# Start the project or debug with the IDE
|
||||
./go-admin
|
||||
|
||||
# See also instructions in WIKI
|
||||
```
|
||||
|
||||
|
||||
Document generation
|
||||
```bash
|
||||
swag init
|
||||
```
|
||||
|
||||
If there is no `swag` command go get installed
|
||||
```bash
|
||||
go get -u github.com/swaggo/swag/cmd/swag
|
||||
```
|
||||
|
||||
|
||||
Cross compilation
|
||||
```bash
|
||||
env GOOS=windows GOARCH=amd64 go build main.go
|
||||
|
||||
# or
|
||||
|
||||
env GOOS=linux GOARCH=amd64 go build main.go
|
||||
```
|
||||
|
||||
|
||||
## 🔗 Online Demo
|
||||
> admin / 123456
|
||||
|
||||
Demo address:[http://www.zhangwj.com](http://www.zhangwj.com/#/login)
|
||||
|
||||
|
||||
## 🤝 Open source projects used
|
||||
[gin](https://github.com/gin-gonic/gin)
|
||||
|
||||
[casbin](https://github.com/casbin/casbin)
|
||||
|
||||
[spf13/viper](https://github.com/spf13/viper)
|
||||
|
||||
[gorm](https://github.com/jinzhu/gorm)
|
||||
|
||||
[gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
|
||||
[jwt-go](https://github.com/dgrijalva/jwt-go)
|
||||
|
||||
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
|
||||
[ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
|
||||
|
||||
|
||||
## Version
|
||||
|
||||
#### 2020-03-15 New Features and Optimization
|
||||
|
||||
1. Add user avatar upload
|
||||
2. Add user password modification
|
||||
3. Operation log page adjustment
|
||||
4. Optimize captcha background color
|
||||
|
||||
I saw a lot of friends who experience the wrong verification code, so I adjusted the contrast for everyone to experience!
|
||||
|
||||
|
||||
## 🤝 Thanks
|
||||
[chengxiao](https://github.com/chengxiao)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/wenjianzhang/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2020 wenjianzhang
|
||||
|
||||
[中文]qq technical exchange group: 74520518
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
# go-admin
|
||||
|
||||
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
|
||||
|
||||
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
[](https://github.com/go-admin-team/go-admin/releases)
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
|
||||
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) | [繁體中文](https://github.com/go-admin-team/go-admin/blob/master/README.zh-TW.md) | 日本語
|
||||
|
||||
Gin + Vue + Element UI / Arco Design / Ant Design による、フロントエンドとバックエンドを分離した権限管理システムです。初期化は非常に簡単で、設定ファイルのデータベース接続情報を変更するだけで動作します。複数のコマンドに対応しており、マイグレーションコマンドでデータベースの初期化が容易になり、サーバーコマンドで API を手軽に起動できます。
|
||||
|
||||
[オンラインドキュメント](https://www.go-admin.pro)
|
||||
|
||||
[フロントエンドプロジェクト](https://github.com/go-admin-team/go-admin-ui)
|
||||
|
||||
[動画チュートリアル](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
|
||||
## 🎬 オンラインデモ
|
||||
|
||||
Element Plus vue3 デモ:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> ⚠️⚠️⚠️ アカウント / パスワード: admin / 123456
|
||||
|
||||
antd デモ(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> ⚠️⚠️⚠️ アカウント / パスワード: admin / 123456
|
||||
|
||||
## ✨ 特徴
|
||||
|
||||
- RESTful API の設計規約に準拠
|
||||
|
||||
- GIN WEB API フレームワークをベースに、豊富なミドルウェアを提供(ユーザー認証、CORS、アクセスログ、トレース ID など)
|
||||
|
||||
- Casbin による RBAC アクセス制御モデル
|
||||
|
||||
- JWT 認証
|
||||
|
||||
- Swagger ドキュメントに対応(swaggo ベース)
|
||||
|
||||
- GORM によるデータベース永続化、複数種類のデータベースに拡張可能
|
||||
|
||||
- 設定ファイルからモデルへの単純なマッピングで、必要な設定をすぐに取得
|
||||
|
||||
- コード生成ツール
|
||||
|
||||
- フォームビルダー
|
||||
|
||||
- マルチコマンド方式
|
||||
|
||||
- マルチテナント対応
|
||||
|
||||
- TODO: ユニットテスト
|
||||
|
||||
## 🎁 標準機能
|
||||
|
||||
1. マルチテナント:デフォルトで対応。データベース単位で分離し、1 データベースにつき 1 テナント。
|
||||
1. ユーザー管理:システムの操作者であるユーザーの設定を行います。
|
||||
2. 部門管理:組織構造(会社・部門・グループ)を設定します。ツリー構造で表示し、データ権限に対応します。
|
||||
3. 役職管理:ユーザーが担当する職務を設定します。
|
||||
4. メニュー管理:メニュー、操作権限、ボタン権限識別子、API 権限などを設定します。
|
||||
5. ロール管理:ロールへのメニュー権限の割り当て、および組織単位でのデータ範囲権限の設定を行います。
|
||||
6. 辞書管理:システム内で頻繁に使う固定的なデータを管理します。
|
||||
7. パラメータ管理:よく使うパラメータを動的に設定します。
|
||||
8. 操作ログ:正常系の操作ログと異常情報のログを記録・検索します。
|
||||
9. ログインログ:ログイン履歴を記録・検索します。ログイン異常も含みます。
|
||||
1. API ドキュメント:業務コードから API ドキュメントを自動生成します。
|
||||
1. コード生成:テーブル定義から CRUD 業務を生成します。すべて画面上で操作でき、基本的な業務をコードなしで実現できます。
|
||||
1. フォームビルダー:ページのスタイルをカスタマイズし、ドラッグ&ドロップでレイアウトを作成します。
|
||||
1. サービス監視:サーバーの基本情報を確認します。
|
||||
1. コンテンツ管理:デモ機能。カテゴリ管理とコンテンツ管理を含み、入門用の参考実装として利用できます。
|
||||
1. スケジュールタスク:自動実行タスク。現在は API 呼び出しと関数呼び出しに対応しています。
|
||||
|
||||
## 事前準備
|
||||
|
||||
ローカルに [go] [gin] [node](http://nodejs.org/) と [git](https://git-scm.com/) をインストールしてください。
|
||||
|
||||
ダウンロードから使いこなすまでを解説した動画とドキュメントのチュートリアルを用意しています。本プロジェクトを試す前に、まずこれらに目を通すことを強くおすすめします。
|
||||
|
||||
### go-admin で最初のアプリケーションを作る - ドキュメント
|
||||
|
||||
[ステップ 1 - 基礎の紹介](https://www.go-admin.pro/guide/intro/tutorial01.html)
|
||||
|
||||
[ステップ 2 - 実践 - CRUD を書く](https://www.go-admin.pro/guide/intro/tutorial02.html)
|
||||
|
||||
### 動画チュートリアル
|
||||
|
||||
[go-admin の起動方法](https://www.bilibili.com/video/BV1z5411x7JG)
|
||||
|
||||
[生成ツールで業務を手軽に実装する](https://www.bilibili.com/video/BV1Dg4y1i79D)
|
||||
|
||||
[v1.1.0 のコード生成ツール](https://www.bilibili.com/video/BV1N54y1i71P) [応用]
|
||||
|
||||
[マルチコマンドでの起動方法と IDE 設定](https://www.bilibili.com/video/BV1Fg4y1q7ph)
|
||||
|
||||
[go-admin のメニュー設定](https://www.bilibili.com/video/BV1Wp4y1D715) [必見]
|
||||
|
||||
[メニュー情報と API 情報の設定方法](https://www.bilibili.com/video/BV1zv411B7nG) [必見]
|
||||
|
||||
[go-admin の権限設定](https://www.bilibili.com/video/BV1rt4y197d3) [必見]
|
||||
|
||||
[go-admin のデータ権限](https://www.bilibili.com/video/BV1LK4y1s71e) [必見]
|
||||
|
||||
**不明点はまず上記のドキュメントと記事をご確認ください。解決しない場合は issue や pr をお寄せください。動画とドキュメントは継続的に更新しています**
|
||||
|
||||
## 📦 ローカル開発
|
||||
|
||||
### 動作要件
|
||||
|
||||
go 1.26.5
|
||||
|
||||
node バージョン: v22 以上(v24 LTS 推奨)
|
||||
|
||||
パッケージマネージャー: pnpm v9 以上(UI プロジェクトは pnpm を使用)
|
||||
|
||||
### 開発ディレクトリの作成
|
||||
|
||||
```bash
|
||||
|
||||
# 開発ディレクトリを作成
|
||||
mkdir goadmin
|
||||
cd goadmin
|
||||
```
|
||||
|
||||
### コードの取得
|
||||
|
||||
> 重要:2 つのプロジェクトは同じディレクトリに配置してください。
|
||||
|
||||
```bash
|
||||
# バックエンドのコードを取得
|
||||
git clone https://github.com/go-admin-team/go-admin.git
|
||||
|
||||
# フロントエンドのコードを取得
|
||||
git clone https://github.com/go-admin-team/go-admin-ui.git
|
||||
|
||||
```
|
||||
|
||||
### 起動方法
|
||||
|
||||
#### サーバーの起動
|
||||
|
||||
```bash
|
||||
# go-admin バックエンドプロジェクトへ移動
|
||||
cd ./go-admin
|
||||
|
||||
# 依存関係を整理
|
||||
go mod tidy
|
||||
|
||||
# ビルド
|
||||
go build
|
||||
|
||||
# 設定を変更
|
||||
# ファイルパス go-admin/config/settings.yml
|
||||
vi ./config/settings.yml
|
||||
|
||||
# 1. 設定ファイル内のデータベース情報を変更
|
||||
# 注意: settings.database 配下の設定項目
|
||||
# 2. log のパスを確認
|
||||
```
|
||||
|
||||
⚠️注意 Windows 環境で CGO が未導入の場合、次のエラーが発生します。
|
||||
|
||||
```bash
|
||||
E:\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
D:\Code\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
|
||||
```
|
||||
|
||||
[cgo の問題の解決方法はこちら](https://www.go-admin.pro/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
|
||||
|
||||
|
||||
#### データベースの初期化とサービス起動
|
||||
|
||||
``` bash
|
||||
# 初回はデータベースのリソース情報を初期化する必要があります
|
||||
# macOS または linux の場合
|
||||
$ ./go-admin migrate -c config/settings.dev.yml
|
||||
|
||||
# ⚠️注意: windows の場合
|
||||
$ go-admin.exe migrate -c config/settings.dev.yml
|
||||
|
||||
|
||||
# プロジェクトを起動します。IDE からデバッグ実行することもできます
|
||||
# macOS または linux の場合
|
||||
$ ./go-admin server -c config/settings.yml
|
||||
|
||||
|
||||
# ⚠️注意: windows の場合
|
||||
$ go-admin.exe server -c config/settings.yml
|
||||
```
|
||||
|
||||
#### sys_api テーブルへのデータ追加方法
|
||||
|
||||
起動時に `-a true` を付けると、不足している API データが自動的に追加されます。
|
||||
```bash
|
||||
./go-admin server -c config/settings.yml -a true
|
||||
```
|
||||
|
||||
#### docker でのビルドと起動
|
||||
|
||||
```shell
|
||||
# イメージをビルド
|
||||
docker build -t go-admin .
|
||||
|
||||
# コンテナを起動します。1 つ目の go-admin はコンテナ名、2 つ目はイメージ名です
|
||||
# -v は設定ファイルのマウント ローカルパス:コンテナ内パス
|
||||
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
|
||||
```
|
||||
|
||||
#### ドキュメント生成
|
||||
|
||||
```bash
|
||||
go generate
|
||||
```
|
||||
|
||||
#### クロスコンパイル
|
||||
|
||||
```bash
|
||||
# windows
|
||||
env GOOS=windows GOARCH=amd64 go build main.go
|
||||
|
||||
# or
|
||||
# linux
|
||||
env GOOS=linux GOARCH=amd64 go build main.go
|
||||
```
|
||||
|
||||
### UI 側の起動方法
|
||||
|
||||
```bash
|
||||
# pnpm をインストール(未導入の場合)
|
||||
npm install -g pnpm
|
||||
|
||||
# 依存関係をインストール
|
||||
pnpm install
|
||||
|
||||
# 中国本土のネットワークではミラーを指定すると高速化できます
|
||||
pnpm install --registry=https://registry.npmmirror.com
|
||||
|
||||
# 開発サーバーを起動
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 📨 コミュニティ
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
|
||||
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
|
||||
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>微信</td>
|
||||
<td>公众号🔥🔥🔥</td>
|
||||
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
|
||||
<td>哔哩哔哩🔥🔥🔥</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 💎 コントリビューター
|
||||
|
||||
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
## JetBrains のオープンソースライセンス支援
|
||||
|
||||
`go-admin` は一貫して JetBrains 社の GoLand 統合開発環境で開発されています。**free JetBrains Open Source license(s)** による正規の無償ライセンス提供に、この場を借りて感謝を申し上げます。
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
|
||||
|
||||
## 🤝 謝辞
|
||||
|
||||
1. [ant-design](https://github.com/ant-design/ant-design)
|
||||
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
|
||||
2. [arco-design](https://github.com/arco-design/arco-design)
|
||||
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
|
||||
4. [gin](https://github.com/gin-gonic/gin)
|
||||
5. [casbin](https://github.com/casbin/casbin)
|
||||
6. [spf13/viper](https://github.com/spf13/viper)
|
||||
7. [gorm](https://github.com/go-gorm/gorm)
|
||||
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
9. [golang-jwt](https://github.com/golang-jwt/jwt)
|
||||
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
12. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
|
||||
## 🤟 支援
|
||||
|
||||
> このプロジェクトがお役に立ちましたら、作者にジュースを一杯おごる形で応援いただけます :tropical_drink:
|
||||
|
||||
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
|
||||
|
||||
## 🤝 関連リンク
|
||||
|
||||
- [mss-boot-io](https://docs.mss-boot-io.top/)
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2026 wenjianzhang
|
||||
@@ -1,248 +1,339 @@
|
||||
<p align="center">
|
||||
<img width="320" src="https://gitee.com/mydearzwj/image/raw/master/img/go-admin.svg">
|
||||
</p>
|
||||
|
||||
# go-admin
|
||||
|
||||
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/203c5930b9ed08d5cf2fcb4516b85e412f8e0e60/img/go-admin.svg">
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/wenjianzhang/go-admin">
|
||||
<img src="https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg" alt="go-admin">
|
||||
</a>
|
||||
<a href="https://github.com/wenjianzhang/go-admin">
|
||||
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
||||
</a>
|
||||
<a href="http://doc.zhangwj.com/go-admin-site/donate/">
|
||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
|
||||
</a>
|
||||
</p>
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
[](https://github.com/go-admin-team/go-admin/releases)
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
|
||||
English | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) | [繁體中文](https://github.com/go-admin-team/go-admin/blob/master/README.zh-TW.md) | [日本語](https://github.com/go-admin-team/go-admin/blob/master/README.ja-JP.md)
|
||||
|
||||
The front-end and back-end separation authority management system based on Gin + Vue + Element UI OR Arco Design OR Ant Design is extremely simple to initialize the system. You only need to modify the database connection in the configuration file. The system supports multi-instruction operations. Migration instructions can make it easier to initialize database information. Service instructions It's easy to start the api service.
|
||||
|
||||
[documentation](https://www.go-admin.pro)
|
||||
|
||||
[Front-end project](https://github.com/go-admin-team/go-admin-ui)
|
||||
|
||||
[Video tutorial](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
|
||||
## 🎬 Online Demo
|
||||
|
||||
Element Plus vue3 demo:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> Account / Password: admin / 123456
|
||||
|
||||
antd demo (go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> Account / Password: admin / 123456
|
||||
>
|
||||
## ✨ Feature
|
||||
|
||||
- Follow RESTful API design specifications
|
||||
|
||||
- Based on the GIN WEB API framework, it provides rich middleware support (user authentication, cross-domain, access log, tracking ID, etc.)
|
||||
|
||||
- RBAC access control model based on Casbin
|
||||
|
||||
- JWT authentication
|
||||
|
||||
- Support Swagger documents (based on swaggo)
|
||||
|
||||
- Database storage based on GORM, which can expand multiple types of databases
|
||||
|
||||
- Simple model mapping of configuration files to quickly get the desired configuration
|
||||
|
||||
- Code generation tool
|
||||
|
||||
- Form builder
|
||||
|
||||
- Multi-command mode
|
||||
|
||||
- TODO: unit test
|
||||
|
||||
|
||||
[English](https://github.com/wenjianzhang/go-admin/blob/master/README.en.md) | 简体中文
|
||||
|
||||
## 🎁 Internal
|
||||
|
||||
##### 基于Gin + Vue + Element UI的前后端分离权限管理系统
|
||||
1. User management: The user is the system operator, this function mainly completes the system user configuration.
|
||||
2. Department management: configure the system organization (company, department, group), and display the tree structure to support data permissions.
|
||||
3. Position management: configure the positions of system users.
|
||||
4. Menu management: configure the system menu, operation authority, button authority identification, interface authority, etc.
|
||||
5. Role management: Role menu permission assignment and role setting are divided into data scope permissions by organization.
|
||||
6. Dictionary management: Maintain some relatively fixed data frequently used in the system.
|
||||
7. Parameter management: dynamically configure common parameters for the system.
|
||||
8. Operation log: system normal operation log record and query; system abnormal information log record and query.
|
||||
9. Login log: The system login log record query contains login exceptions.
|
||||
1. Interface documentation: Automatically generate related api interface documents according to the business code.
|
||||
1. Code generation: According to the data table structure, generate the corresponding addition, deletion, modification, and check corresponding business, and the whole process of visual operation, so that the basic business can be implemented with zero code.
|
||||
1. Form construction: Customize the page style, drag and drop to realize the page layout.
|
||||
1. Service monitoring: View the basic information of some servers.
|
||||
1. Content management: demo function, including classification management and content management. You can refer to the easy to use quick start.
|
||||
|
||||
系统初始化极度简单,只需要配置文件中,修改数据库连接,系统启动后会自动初始化数据库信息以及必须的基础数据
|
||||
## Ready to work
|
||||
|
||||
[在线文档国际](https://wenjianzhang.github.io/go-admin-site)
|
||||
[在线文档国内](http://mydearzwj.gitee.io/go-admin-site/)
|
||||
You need to install locally [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
|
||||
|
||||
[前端项目](https://github.com/wenjianzhang/go-admin-ui)
|
||||
At the same time, a series of tutorials including videos and documents are provided. How to complete the downloading to the proficient use, it is strongly recommended that you read these tutorials before you practice this project! ! !
|
||||
|
||||
[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
### Easily implement go-admin to write the first application-documentation tutorial
|
||||
|
||||
## ✨ 特性
|
||||
[Step 1 - basic content introduction](https://www.go-admin.pro/guide/intro/tutorial01.html)
|
||||
|
||||
- 遵循 RESTful API 设计规范
|
||||
[Step 2 - Practical application - writing database operations](https://www.go-admin.pro/guide/intro/tutorial02.html)
|
||||
|
||||
- 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证、跨域、访问日志、追踪ID等)
|
||||
### Teach you from getting started to giving up-video tutorial
|
||||
|
||||
- 基于Casbin的 RBAC 访问控制模型
|
||||
[How to start go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
|
||||
|
||||
- JWT 认证
|
||||
[Easily implement business using build tools](https://www.bilibili.com/video/BV1Dg4y1i79D)
|
||||
|
||||
- 支持 Swagger 文档(基于swaggo)
|
||||
[v1.1.0 version code generation tool-free your hands](https://www.bilibili.com/video/BV1N54y1i71P) [Advanced]
|
||||
|
||||
- 基于 GORM 的数据库存储,可扩展多种类型数据库
|
||||
[Explanation of multi-command startup mode and IDE configuration](https://www.bilibili.com/video/BV1Fg4y1q7ph)
|
||||
|
||||
- 配置文件简单的模型映射,快速能够得到想要的配置
|
||||
[Configuration instructions for go-admin menu](https://www.bilibili.com/video/BV1Wp4y1D715) [Must see]
|
||||
|
||||
- 代码生成工具
|
||||
[How to configure menu information and interface information](https://www.bilibili.com/video/BV1zv411B7nG) [Must see]
|
||||
|
||||
- 表单构建工具
|
||||
[go-admin permission configuration instructions](https://www.bilibili.com/video/BV1rt4y197d3) [Must see]
|
||||
|
||||
- 多命令模式
|
||||
[Instructions for use of go-admin data permissions](https://www.bilibili.com/video/BV1LK4y1s71e) [Must see]
|
||||
|
||||
- TODO: 单元测试
|
||||
**If you have any questions, please read the above-mentioned usage documents and articles first. If you are not satisfied, welcome to issue and pr. Video tutorials and documents are being updated continuously.**
|
||||
|
||||
## 📦 Local development
|
||||
|
||||
## 🎁 内置
|
||||
### Environmental requirements
|
||||
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
9. 登录日志:系统登录日志记录查询包含登录异常。
|
||||
10. 系统接口:根据业务代码自动生成相关的api接口文档。
|
||||
11. 代码生成:根据数据表结构生成对应的增删改查相对应业务,全部可视化编程,基本业务可以0代码实现。
|
||||
12. 表单构建:自定义页面样式,拖拉拽实现页面布局。
|
||||
13. 服务监控:查看一些服务器的基本信息。
|
||||
go 1.26.5
|
||||
|
||||
## 准备工作
|
||||
nodejs: v22+ (v24 LTS recommended)
|
||||
|
||||
你需要在本地安装 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
|
||||
package manager: pnpm v9+ (the UI project uses pnpm)
|
||||
|
||||
同时配套了系列教程包含视频和文档,如何从下载完成到熟练使用,强烈建议大家先看完这些教程再来实践本项目!!!
|
||||
|
||||
### 轻松实现go-admin写出第一个应用 - 文档教程
|
||||
|
||||
[步骤一 - 基础内容介绍](http://doc.zhangwj.com/go-admin-site/guide/intro/tutorial01.html)
|
||||
|
||||
[步骤二 - 实际应用 - 编写增删改查](http://doc.zhangwj.com/go-admin-site/guide/intro/tutorial02.html)
|
||||
|
||||
### 手把手教你从入门到放弃 - 视频教程
|
||||
|
||||
[如何启动go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
|
||||
|
||||
[使用生成工具轻松实现业务](https://www.bilibili.com/video/BV1Dg4y1i79D)
|
||||
|
||||
[多命令启动方式讲解以及IDE配置](https://www.bilibili.com/video/BV1Fg4y1q7ph)
|
||||
|
||||
[go-admin菜单的配置说明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
|
||||
|
||||
[如何配置菜单信息以及接口信息](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
|
||||
|
||||
[go-admin权限配置使用说明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
|
||||
|
||||
[go-admin数据权限使用说明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
|
||||
|
||||
|
||||
**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr ,视频教程和文档持续更新中**
|
||||
|
||||
## 🗞 系统架构
|
||||
|
||||
<p align="center">
|
||||
<img src="https://gitee.com/mydearzwj/image/raw/d9f59ea603e3c8a3977491a1bfa8f122e1a80824/img/go-admin-system.png" width="936px" height="491px">
|
||||
</p>
|
||||
|
||||
## 📦 本地开发
|
||||
|
||||
### 开发目录创建
|
||||
### Development directory creation
|
||||
|
||||
```bash
|
||||
|
||||
# 创建开发目录
|
||||
# Create a development directory
|
||||
mkdir goadmin
|
||||
cd goadmin
|
||||
```
|
||||
|
||||
### 获取代码
|
||||
### Get the code
|
||||
|
||||
> 重点注意:两个项目必须放在同一文件夹下;
|
||||
> Important note: the two projects must be placed in the same folder;
|
||||
|
||||
```bash
|
||||
# 获取后端代码
|
||||
git clone https://github.com/wenjianzhang/go-admin.git
|
||||
# Get backend code
|
||||
git clone https://github.com/go-admin-team/go-admin.git
|
||||
|
||||
# 获取前端代码
|
||||
git clone https://github.com/wenjianzhang/go-admin-ui.git
|
||||
# Get the front-end code
|
||||
git clone https://github.com/go-admin-team/go-admin-ui.git
|
||||
|
||||
```
|
||||
|
||||
### Startup instructions
|
||||
|
||||
### 启动说明
|
||||
|
||||
#### 服务端启动说明
|
||||
#### Server startup instructions
|
||||
|
||||
```bash
|
||||
# 进入 go-admin 后端项目
|
||||
# Enter the go-admin backend project
|
||||
cd ./go-admin
|
||||
|
||||
# 编译项目
|
||||
# Update dependencies
|
||||
go mod tidy
|
||||
|
||||
# Compile the project
|
||||
go build
|
||||
|
||||
# 修改配置
|
||||
# 文件路径 go-admin/config/settings.yml
|
||||
vi ./config/setting.yml
|
||||
# Change setting
|
||||
# File path go-admin/config/settings.yml
|
||||
vi ./config/settings.yml
|
||||
|
||||
# 1. 配置文件中修改数据库信息
|
||||
# 注意: settings.database 下对应的配置数据
|
||||
# 2. 确认log路径
|
||||
# 1. Modify the database information in the configuration file
|
||||
# Note: The corresponding configuration data under settings.database
|
||||
# 2. Confirm the log path
|
||||
```
|
||||
|
||||
#### 初始化数据库,以及服务启动
|
||||
```
|
||||
# 首次配置需要初始化数据库资源信息
|
||||
./go-admin init -c config/settings.yml -m dev
|
||||
⚠️ Note that this problem will occur if CGO is not installed in the windows10+ environment;
|
||||
|
||||
|
||||
# 启动项目,也可以用IDE进行调试
|
||||
./go-admin server -c config/settings.yml -p 8000 -m dev
|
||||
|
||||
```
|
||||
|
||||
#### 文档生成
|
||||
```bash
|
||||
swag init
|
||||
|
||||
# 如果没有swag命令 go get安装一下即可
|
||||
go get -u github.com/swaggo/swag/cmd/swag
|
||||
E:\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
|
||||
```
|
||||
|
||||
#### 交叉编译
|
||||
or
|
||||
|
||||
```bash
|
||||
D:\Code\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
|
||||
```
|
||||
|
||||
[Solve the cgo problem and enter](https://www.go-admin.pro/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
|
||||
|
||||
#### Initialize the database, and start the service
|
||||
|
||||
``` bash
|
||||
# The first configuration needs to initialize the database resource information
|
||||
# Use under macOS or linux
|
||||
$ ./go-admin migrate -c config/settings.dev.yml
|
||||
|
||||
# ⚠️Note: Use under windows
|
||||
$ go-admin.exe migrate -c config/settings.dev.yml
|
||||
|
||||
# Start the project, you can also use the IDE for debugging
|
||||
# Use under macOS or linux
|
||||
$ ./go-admin server -c config/settings.yml
|
||||
|
||||
# ⚠️Note: Use under windows
|
||||
$ go-admin.exe server -c config/settings.yml
|
||||
```
|
||||
|
||||
#### Use docker to compile and start
|
||||
|
||||
```shell
|
||||
# Compile the image
|
||||
docker build -t go-admin .
|
||||
|
||||
|
||||
# Start the container, the first go-admin is the container name, and the second go-admin is the image name
|
||||
# -v Mapping configuration file Local path: container path
|
||||
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### Generation Document
|
||||
|
||||
```bash
|
||||
go generate
|
||||
```
|
||||
|
||||
#### Cross compile
|
||||
```bash
|
||||
# windows
|
||||
env GOOS=windows GOARCH=amd64 go build main.go
|
||||
|
||||
# or
|
||||
|
||||
# linux
|
||||
env GOOS=linux GOARCH=amd64 go build main.go
|
||||
```
|
||||
|
||||
### UI交互端启动说明
|
||||
### UI interactive terminal startup instructions
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
# Install pnpm if you don't have it
|
||||
npm install -g pnpm
|
||||
|
||||
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
# Installation dependencies
|
||||
pnpm install
|
||||
|
||||
# 启动服务
|
||||
npm run dev
|
||||
# Start service
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 🎬 在线体验
|
||||
> admin / 123456
|
||||
|
||||
演示地址:[http://www.zhangwj.com](http://www.zhangwj.com/#/login)
|
||||
|
||||
|
||||
## 📨 互动
|
||||
## 📨 Interactive
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq.png" width="200px"></td>
|
||||
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
|
||||
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>微信</td>
|
||||
<td>此群已满</td>
|
||||
<td>Wechat</td>
|
||||
<td>Wechat公众号🔥🔥🔥</td>
|
||||
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
|
||||
<td>bilibili🔥🔥🔥</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## 🤝 特别感谢
|
||||
[chengxiao](https://github.com/chengxiao)
|
||||
[gin](https://github.com/gin-gonic/gin)
|
||||
[casbin](https://github.com/casbin/casbin)
|
||||
[spf13/viper](https://github.com/spf13/viper)
|
||||
[gorm](https://github.com/jinzhu/gorm)
|
||||
[gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
[jwt-go](https://github.com/dgrijalva/jwt-go)
|
||||
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
[ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
## 💎 Contributors
|
||||
|
||||
## 🤟 打赏
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
|
||||
> 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
|
||||
|
||||
|
||||
## JetBrains open source certificate support
|
||||
|
||||
The `go-admin` project has always been developed in the GoLand integrated development environment under JetBrains, based on the **free JetBrains Open Source license(s)** genuine free license. I would like to express my gratitude.
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
|
||||
|
||||
|
||||
## 🤝 Thanks
|
||||
|
||||
1. [ant-design](https://github.com/ant-design/ant-design)
|
||||
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
|
||||
2. [arco-design](https://github.com/arco-design/arco-design)
|
||||
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
|
||||
2. [gin](https://github.com/gin-gonic/gin)
|
||||
2. [casbin](https://github.com/casbin/casbin)
|
||||
2. [spf13/viper](https://github.com/spf13/viper)
|
||||
2. [gorm](https://github.com/go-gorm/gorm)
|
||||
2. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
2. [golang-jwt](https://github.com/golang-jwt/jwt)
|
||||
2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
2. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
2. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
|
||||
## 🤟 Sponsor Us
|
||||
|
||||
> If you think this project helped you, you can buy a glass of juice for the author to show encouragement :tropical_drink:
|
||||
|
||||
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
|
||||
|
||||
## ❤️ 赞助者
|
||||
|
||||
> 有部分是微信名称
|
||||
|
||||
zhuqiyun LLL狐 星星之火 cjj770 Sam 唐*i 晓聪 aLong *渊 海马 魏镇坪 + 111 *哥 我的宇哥哥 *声 *节
|
||||
## 🤝 Link
|
||||
- [mss-boot-io](https://docs.mss-boot-io.top/)
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](https://github.com/wenjianzhang/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2020 wenjianzhang
|
||||
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2026 wenjianzhang
|
||||
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
# go-admin
|
||||
|
||||
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
|
||||
|
||||
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
[](https://github.com/go-admin-team/go-admin/releases)
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
|
||||
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) | 繁體中文 | [日本語](https://github.com/go-admin-team/go-admin/blob/master/README.ja-JP.md)
|
||||
|
||||
基於 Gin + Vue + Element UI OR Arco Design OR Ant Design 的前後端分離權限管理系統。系統初始化極為簡單,只需在設定檔中修改資料庫連線資訊即可。系統支援多指令操作:遷移指令讓資料庫初始化變得更簡單,服務指令則能輕鬆啟動 API 服務。
|
||||
|
||||
[線上文件](https://www.go-admin.pro)
|
||||
|
||||
[前端專案](https://github.com/go-admin-team/go-admin-ui)
|
||||
|
||||
[影片教學](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
|
||||
## 🎬 線上體驗
|
||||
|
||||
Element Plus vue3 體驗:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> ⚠️⚠️⚠️ 帳號 / 密碼: admin / 123456
|
||||
|
||||
antd 體驗(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> ⚠️⚠️⚠️ 帳號 / 密碼: admin / 123456
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 遵循 RESTful API 設計規範
|
||||
|
||||
- 基於 GIN WEB API 框架,提供豐富的中介軟體支援(使用者認證、跨域、存取日誌、追蹤 ID 等)
|
||||
|
||||
- 基於 Casbin 的 RBAC 存取控制模型
|
||||
|
||||
- JWT 認證
|
||||
|
||||
- 支援 Swagger 文件(基於 swaggo)
|
||||
|
||||
- 基於 GORM 的資料庫儲存,可擴充多種類型資料庫
|
||||
|
||||
- 設定檔簡單的模型映射,快速取得所需設定
|
||||
|
||||
- 程式碼產生工具
|
||||
|
||||
- 表單建構工具
|
||||
|
||||
- 多指令模式
|
||||
|
||||
- 多租戶的支援
|
||||
|
||||
- TODO: 單元測試
|
||||
|
||||
## 🎁 內建
|
||||
|
||||
1. 多租戶:系統預設支援多租戶,按資料庫分離,一個資料庫一個租戶。
|
||||
1. 使用者管理:使用者是系統操作者,該功能主要完成系統使用者設定。
|
||||
2. 部門管理:設定系統組織架構(公司、部門、小組),以樹狀結構呈現並支援資料權限。
|
||||
3. 職位管理:設定系統使用者所擔任的職務。
|
||||
4. 選單管理:設定系統選單、操作權限、按鈕權限標識、介面權限等。
|
||||
5. 角色管理:角色選單權限分配、設定角色按機構進行資料範圍權限劃分。
|
||||
6. 字典管理:對系統中經常使用且較為固定的資料進行維護。
|
||||
7. 參數管理:對系統動態設定常用參數。
|
||||
8. 操作日誌:系統正常操作的日誌記錄與查詢;系統異常資訊的日誌記錄與查詢。
|
||||
9. 登入日誌:系統登入日誌記錄查詢,包含登入異常。
|
||||
1. 介面文件:根據業務程式碼自動產生相關的 API 介面文件。
|
||||
1. 程式碼產生:根據資料表結構產生對應的增刪改查業務,全程視覺化操作,讓基本業務可以零程式碼實現。
|
||||
1. 表單建構:自訂頁面樣式,拖拉放實現頁面佈局。
|
||||
1. 服務監控:檢視伺服器的基本資訊。
|
||||
1. 內容管理:demo 功能,下設分類管理、內容管理,可參考使用以快速入門。
|
||||
1. 排程任務:自動化任務,目前支援介面呼叫與函式呼叫。
|
||||
|
||||
## 準備工作
|
||||
|
||||
你需要在本機安裝 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
|
||||
|
||||
同時配套了系列教學(含影片與文件),說明如何從下載到熟練使用。強烈建議先看完這些教學再來實作本專案!!!
|
||||
|
||||
### 輕鬆用 go-admin 寫出第一個應用 - 文件教學
|
||||
|
||||
[步驟一 - 基礎內容介紹](https://www.go-admin.pro/guide/intro/tutorial01.html)
|
||||
|
||||
[步驟二 - 實際應用 - 撰寫增刪改查](https://www.go-admin.pro/guide/intro/tutorial02.html)
|
||||
|
||||
### 手把手教你從入門到放棄 - 影片教學
|
||||
|
||||
[如何啟動 go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
|
||||
|
||||
[使用產生工具輕鬆實現業務](https://www.bilibili.com/video/BV1Dg4y1i79D)
|
||||
|
||||
[v1.1.0 版本程式碼產生工具 - 釋放雙手](https://www.bilibili.com/video/BV1N54y1i71P) [進階]
|
||||
|
||||
[多指令啟動方式講解以及 IDE 設定](https://www.bilibili.com/video/BV1Fg4y1q7ph)
|
||||
|
||||
[go-admin 選單的設定說明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
|
||||
|
||||
[如何設定選單資訊以及介面資訊](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
|
||||
|
||||
[go-admin 權限設定使用說明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
|
||||
|
||||
[go-admin 資料權限使用說明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
|
||||
|
||||
**如有問題請先參閱上述文件與文章,若仍無法解決,歡迎提出 issue 與 pr。影片教學與文件持續更新中**
|
||||
|
||||
## 📦 本機開發
|
||||
|
||||
### 環境需求
|
||||
|
||||
go 1.26.5
|
||||
|
||||
node 版本: v22+(建議 v24 LTS)
|
||||
|
||||
套件管理器: pnpm v9+(UI 專案使用 pnpm)
|
||||
|
||||
### 建立開發目錄
|
||||
|
||||
```bash
|
||||
|
||||
# 建立開發目錄
|
||||
mkdir goadmin
|
||||
cd goadmin
|
||||
```
|
||||
|
||||
### 取得程式碼
|
||||
|
||||
> 重點注意:兩個專案必須放在同一資料夾下;
|
||||
|
||||
```bash
|
||||
# 取得後端程式碼
|
||||
git clone https://github.com/go-admin-team/go-admin.git
|
||||
|
||||
# 取得前端程式碼
|
||||
git clone https://github.com/go-admin-team/go-admin-ui.git
|
||||
|
||||
```
|
||||
|
||||
### 啟動說明
|
||||
|
||||
#### 伺服器端啟動說明
|
||||
|
||||
```bash
|
||||
# 進入 go-admin 後端專案
|
||||
cd ./go-admin
|
||||
|
||||
# 更新整理相依套件
|
||||
go mod tidy
|
||||
|
||||
# 編譯專案
|
||||
go build
|
||||
|
||||
# 修改設定
|
||||
# 檔案路徑 go-admin/config/settings.yml
|
||||
vi ./config/settings.yml
|
||||
|
||||
# 1. 在設定檔中修改資料庫資訊
|
||||
# 注意: settings.database 下對應的設定資料
|
||||
# 2. 確認 log 路徑
|
||||
```
|
||||
|
||||
⚠️注意 在 Windows 環境若未安裝 CGO,會出現這個問題;
|
||||
|
||||
```bash
|
||||
E:\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
D:\Code\go-admin>go build
|
||||
# github.com/mattn/go-sqlite3
|
||||
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
|
||||
```
|
||||
|
||||
[解決 cgo 問題請進入](https://www.go-admin.pro/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
|
||||
|
||||
|
||||
#### 初始化資料庫,以及服務啟動
|
||||
|
||||
``` bash
|
||||
# 首次設定需要初始化資料庫資源資訊
|
||||
# macOS or linux 下使用
|
||||
$ ./go-admin migrate -c config/settings.dev.yml
|
||||
|
||||
# ⚠️注意:windows 下使用
|
||||
$ go-admin.exe migrate -c config/settings.dev.yml
|
||||
|
||||
|
||||
# 啟動專案,也可以用 IDE 進行除錯
|
||||
# macOS or linux 下使用
|
||||
$ ./go-admin server -c config/settings.yml
|
||||
|
||||
|
||||
# ⚠️注意:windows 下使用
|
||||
$ go-admin.exe server -c config/settings.yml
|
||||
```
|
||||
|
||||
#### sys_api 表的資料如何新增
|
||||
|
||||
在專案啟動時,使用 `-a true` 系統會自動新增缺少的介面資料
|
||||
```bash
|
||||
./go-admin server -c config/settings.yml -a true
|
||||
```
|
||||
|
||||
#### 使用 docker 編譯啟動
|
||||
|
||||
```shell
|
||||
# 編譯映像檔
|
||||
docker build -t go-admin .
|
||||
|
||||
# 啟動容器,第一個 go-admin 是容器名稱,第二個 go-admin 是映像檔名稱
|
||||
# -v 映射設定檔 本機路徑:容器路徑
|
||||
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
|
||||
```
|
||||
|
||||
#### 文件產生
|
||||
|
||||
```bash
|
||||
go generate
|
||||
```
|
||||
|
||||
#### 交叉編譯
|
||||
|
||||
```bash
|
||||
# windows
|
||||
env GOOS=windows GOARCH=amd64 go build main.go
|
||||
|
||||
# or
|
||||
# linux
|
||||
env GOOS=linux GOARCH=amd64 go build main.go
|
||||
```
|
||||
|
||||
### UI 互動端啟動說明
|
||||
|
||||
```bash
|
||||
# 安裝 pnpm(若未安裝)
|
||||
npm install -g pnpm
|
||||
|
||||
# 安裝相依套件
|
||||
pnpm install
|
||||
|
||||
# 中國大陸網路可指定鏡像來源加速
|
||||
pnpm install --registry=https://registry.npmmirror.com
|
||||
|
||||
# 啟動服務
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 📨 互動
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
|
||||
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
|
||||
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>微信</td>
|
||||
<td>公众号🔥🔥🔥</td>
|
||||
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
|
||||
<td>哔哩哔哩🔥🔥🔥</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 💎 貢獻者
|
||||
|
||||
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
## JetBrains 開源證書支援
|
||||
|
||||
`go-admin` 專案一直以來都是在 JetBrains 公司旗下的 GoLand 整合開發環境中進行開發,基於 **free JetBrains Open Source license(s)** 正版免費授權,在此表達我的謝意。
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
|
||||
|
||||
## 🤝 特別感謝
|
||||
|
||||
1. [ant-design](https://github.com/ant-design/ant-design)
|
||||
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
|
||||
2. [arco-design](https://github.com/arco-design/arco-design)
|
||||
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
|
||||
4. [gin](https://github.com/gin-gonic/gin)
|
||||
5. [casbin](https://github.com/casbin/casbin)
|
||||
6. [spf13/viper](https://github.com/spf13/viper)
|
||||
7. [gorm](https://github.com/go-gorm/gorm)
|
||||
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
9. [golang-jwt](https://github.com/golang-jwt/jwt)
|
||||
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
12. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
|
||||
## 🤟 贊助
|
||||
|
||||
> 如果你覺得這個專案幫助到了你,可以幫作者買一杯果汁表示鼓勵 :tropical_drink:
|
||||
|
||||
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
|
||||
|
||||
## 🤝 連結
|
||||
|
||||
- [mss-boot-io](https://docs.mss-boot-io.top/)
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2026 wenjianzhang
|
||||
@@ -1,132 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 登录日志列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Param status query string false "status"
|
||||
// @Param dictCode query string false "dictCode"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/loginloglist [get]
|
||||
// @Security Bearer
|
||||
func GetLoginLogList(c *gin.Context) {
|
||||
var data models.LoginLog
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
size := c.Request.FormValue("pageSize")
|
||||
if size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
index := c.Request.FormValue("pageIndex")
|
||||
if index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.Username = c.Request.FormValue("username")
|
||||
data.Status = c.Request.FormValue("status")
|
||||
data.Ipaddr = c.Request.FormValue("ipaddr")
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 通过编码获取登录日志
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Param infoId path int true "infoId"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/loginlog/{infoId} [get]
|
||||
// @Security Bearer
|
||||
func GetLoginLog(c *gin.Context) {
|
||||
var LoginLog models.LoginLog
|
||||
LoginLog.InfoId, _ = tools.StringToInt(c.Param("infoId"))
|
||||
result, err := LoginLog.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 添加登录日志
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.LoginLog true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/loginlog [post]
|
||||
// @Security Bearer
|
||||
func InsertLoginLog(c *gin.Context) {
|
||||
var data models.LoginLog
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "", 500)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 修改登录日志
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.LoginLog true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/loginlog [put]
|
||||
// @Security Bearer
|
||||
func UpdateLoginLog(c *gin.Context) {
|
||||
var data models.LoginLog
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "", -1)
|
||||
result, err := data.Update(data.InfoId)
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 批量删除登录日志
|
||||
// @Description 删除数据
|
||||
// @Tags 登录日志
|
||||
// @Param infoId path string true "以逗号(,)分割的infoId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/loginlog/{infoId} [delete]
|
||||
func DeleteLoginLog(c *gin.Context) {
|
||||
var data models.LoginLog
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("infoId", c)
|
||||
_, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "修改失败", 500)
|
||||
var res app.Response
|
||||
res.Msg = "删除成功"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 登录日志列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Param status query string false "status"
|
||||
// @Param dictCode query string false "dictCode"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/operloglist [get]
|
||||
// @Security Bearer
|
||||
func GetOperLogList(c *gin.Context) {
|
||||
var data models.SysOperLog
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
size := c.Request.FormValue("pageSize")
|
||||
if size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
index := c.Request.FormValue("pageIndex")
|
||||
if index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.OperName = c.Request.FormValue("operName")
|
||||
data.Status = c.Request.FormValue("status")
|
||||
data.OperIp = c.Request.FormValue("operIp")
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 通过编码获取登录日志
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Param infoId path int true "infoId"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/operlog/{infoId} [get]
|
||||
// @Security Bearer
|
||||
func GetOperLog(c *gin.Context) {
|
||||
var OperLog models.SysOperLog
|
||||
OperLog.OperId, _ = tools.StringToInt(c.Param("operId"))
|
||||
result, err := OperLog.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 添加操作日志
|
||||
// @Description 获取JSON
|
||||
// @Tags 操作日志
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysOperLog true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/operlog [post]
|
||||
// @Security Bearer
|
||||
func InsertOperLog(c *gin.Context) {
|
||||
var data models.SysOperLog
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "", 500)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 批量删除操作日志
|
||||
// @Description 删除数据
|
||||
// @Tags 操作日志
|
||||
// @Param operId path string true "以逗号(,)分割的operId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/operlog/{operId} [delete]
|
||||
func DeleteOperLog(c *gin.Context) {
|
||||
var data models.SysOperLog
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("operId", c)
|
||||
_, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "删除失败", 500)
|
||||
var res app.Response
|
||||
res.Msg = "删除成功"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"go-admin/tools/app"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
B = 1
|
||||
KB = 1024 * B
|
||||
MB = 1024 * KB
|
||||
GB = 1024 * MB
|
||||
)
|
||||
|
||||
func ServerInfo(c *gin.Context) {
|
||||
|
||||
osDic := make(map[string]interface{}, 0)
|
||||
osDic["goOs"] = runtime.GOOS
|
||||
osDic["arch"] = runtime.GOARCH
|
||||
osDic["mem"] = runtime.MemProfileRate
|
||||
osDic["compiler"] = runtime.Compiler
|
||||
osDic["version"] = runtime.Version()
|
||||
osDic["numGoroutine"] = runtime.NumGoroutine()
|
||||
|
||||
dis, _ := disk.Usage("/")
|
||||
diskTotalGB := int(dis.Total) / GB
|
||||
diskFreeGB := int(dis.Free) / GB
|
||||
diskDic := make(map[string]interface{}, 0)
|
||||
diskDic["total"] = diskTotalGB
|
||||
diskDic["free"] = diskFreeGB
|
||||
|
||||
mem, _ := mem.VirtualMemory()
|
||||
memUsedMB := int(mem.Used) / GB
|
||||
memTotalMB := int(mem.Total) / GB
|
||||
memFreeMB := int(mem.Free) / GB
|
||||
memUsedPercent := int(mem.UsedPercent)
|
||||
memDic := make(map[string]interface{}, 0)
|
||||
memDic["total"] = memTotalMB
|
||||
memDic["used"] = memUsedMB
|
||||
memDic["free"] = memFreeMB
|
||||
memDic["usage"] = memUsedPercent
|
||||
|
||||
cpuDic := make(map[string]interface{}, 0)
|
||||
cpuDic["cpuNum"], _ = cpu.Counts(false)
|
||||
|
||||
app.Custum(c, gin.H{
|
||||
"code": 200,
|
||||
"os": osDic,
|
||||
"mem": memDic,
|
||||
"cpu": cpuDic,
|
||||
"disk": diskDic,
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"go-admin/tools/captcha"
|
||||
)
|
||||
|
||||
func GenerateCaptchaHandler(c *gin.Context) {
|
||||
id, b64s, err := captcha.DriverDigitFunc()
|
||||
tools.HasError(err, "验证码获取失败", 500)
|
||||
app.Custum(c, gin.H{
|
||||
"code": 200,
|
||||
"data": b64s,
|
||||
"id": id,
|
||||
"msg": "success",
|
||||
})
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"go-admin/tools/app/msg"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 配置列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 配置
|
||||
// @Param configKey query string false "configKey"
|
||||
// @Param configName query string false "configName"
|
||||
// @Param configType query string false "configType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/configList [get]
|
||||
// @Security Bearer
|
||||
func GetConfigList(c *gin.Context) {
|
||||
var data models.SysConfig
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.ConfigKey = c.Request.FormValue("configKey")
|
||||
data.ConfigName = c.Request.FormValue("configName")
|
||||
data.ConfigType = c.Request.FormValue("configType")
|
||||
data.DataScope = tools.GetUserIdStr(c)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 获取配置
|
||||
// @Description 获取JSON
|
||||
// @Tags 配置
|
||||
// @Param configId path int true "配置编码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/config/{configId} [get]
|
||||
// @Security Bearer
|
||||
func GetConfig(c *gin.Context) {
|
||||
var Config models.SysConfig
|
||||
Config.ConfigId, _ = tools.StringToInt(c.Param("configId"))
|
||||
result, err := Config.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 获取配置
|
||||
// @Description 获取JSON
|
||||
// @Tags 配置
|
||||
// @Param configKey path int true "configKey"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/configKey/{configKey} [get]
|
||||
// @Security Bearer
|
||||
func GetConfigByConfigKey(c *gin.Context) {
|
||||
var Config models.SysConfig
|
||||
Config.ConfigKey = c.Param("configKey")
|
||||
result, err := Config.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
|
||||
app.OK(c, result, result.ConfigValue)
|
||||
}
|
||||
|
||||
// @Summary 添加配置
|
||||
// @Description 获取JSON
|
||||
// @Tags 配置
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysConfig true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dict/data [post]
|
||||
// @Security Bearer
|
||||
func InsertConfig(c *gin.Context) {
|
||||
var data models.SysConfig
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", 500)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 修改配置
|
||||
// @Description 获取JSON
|
||||
// @Tags 配置
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysConfig true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/config [put]
|
||||
// @Security Bearer
|
||||
func UpdateConfig(c *gin.Context) {
|
||||
var data models.SysConfig
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "数据解析失败", -1)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
result, err := data.Update(data.ConfigId)
|
||||
tools.HasError(err, "", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 删除配置
|
||||
// @Description 删除数据
|
||||
// @Tags 配置
|
||||
// @Param configId path int true "configId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/config/{configId} [delete]
|
||||
func DeleteConfig(c *gin.Context) {
|
||||
var data models.SysConfig
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("configId", c)
|
||||
result, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "修改失败", 500)
|
||||
app.OK(c, result, msg.DeletedSuccess)
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"go-admin/tools/app/msg"
|
||||
)
|
||||
|
||||
// @Summary 分页部门列表数据
|
||||
// @Description 分页列表
|
||||
// @Tags 部门
|
||||
// @Param name query string false "name"
|
||||
// @Param id query string false "id"
|
||||
// @Param position query string false "position"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/deptList [get]
|
||||
// @Security Bearer
|
||||
func GetDeptList(c *gin.Context) {
|
||||
var Dept models.SysDept
|
||||
Dept.DeptName = c.Request.FormValue("deptName")
|
||||
Dept.Status = c.Request.FormValue("status")
|
||||
Dept.DeptId, _ = tools.StringToInt(c.Request.FormValue("deptId"))
|
||||
Dept.DataScope = tools.GetUserIdStr(c)
|
||||
result, err := Dept.SetDept(true)
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
func GetDeptTree(c *gin.Context) {
|
||||
var Dept models.SysDept
|
||||
Dept.DeptName = c.Request.FormValue("deptName")
|
||||
Dept.Status = c.Request.FormValue("status")
|
||||
Dept.DeptId, _ = tools.StringToInt(c.Request.FormValue("deptId"))
|
||||
result, err := Dept.SetDept(false)
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 部门列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 部门
|
||||
// @Param deptId path string false "deptId"
|
||||
// @Param position query string false "position"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dept/{deptId} [get]
|
||||
// @Security Bearer
|
||||
func GetDept(c *gin.Context) {
|
||||
var Dept models.SysDept
|
||||
Dept.DeptId, _ = tools.StringToInt(c.Param("deptId"))
|
||||
Dept.DataScope = tools.GetUserIdStr(c)
|
||||
result, err := Dept.Get()
|
||||
tools.HasError(err, msg.NotFound, 404)
|
||||
app.OK(c, result, msg.GetSuccess)
|
||||
}
|
||||
|
||||
// @Summary 添加部门
|
||||
// @Description 获取JSON
|
||||
// @Tags 部门
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysDept true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dept [post]
|
||||
// @Security Bearer
|
||||
func InsertDept(c *gin.Context) {
|
||||
var data models.SysDept
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "", 500)
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
app.OK(c, result, msg.CreatedSuccess)
|
||||
}
|
||||
|
||||
// @Summary 修改部门
|
||||
// @Description 获取JSON
|
||||
// @Tags 部门
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param id path int true "id"
|
||||
// @Param data body models.SysDept true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dept [put]
|
||||
// @Security Bearer
|
||||
func UpdateDept(c *gin.Context) {
|
||||
var data models.SysDept
|
||||
err := c.BindJSON(&data)
|
||||
tools.HasError(err, "", -1)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
result, err := data.Update(data.DeptId)
|
||||
tools.HasError(err, "", -1)
|
||||
app.OK(c, result, msg.UpdatedSuccess)
|
||||
}
|
||||
|
||||
// @Summary 删除部门
|
||||
// @Description 删除数据
|
||||
// @Tags 部门
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/dept/{id} [delete]
|
||||
func DeleteDept(c *gin.Context) {
|
||||
var data models.SysDept
|
||||
id, err := tools.StringToInt(c.Param("id"))
|
||||
_, err = data.Delete(id)
|
||||
tools.HasError(err, "删除失败", 500)
|
||||
app.OK(c, "", msg.DeletedSuccess)
|
||||
}
|
||||
|
||||
func GetDeptTreeRoleselect(c *gin.Context) {
|
||||
var Dept models.SysDept
|
||||
var SysRole models.SysRole
|
||||
id, err := tools.StringToInt(c.Param("roleId"))
|
||||
SysRole.RoleId = id
|
||||
result, err := Dept.SetDeptLable()
|
||||
tools.HasError(err, msg.NotFound, -1)
|
||||
menuIds := make([]int, 0)
|
||||
if id != 0 {
|
||||
menuIds, err = SysRole.GetRoleDeptId()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
}
|
||||
app.Custum(c, gin.H{
|
||||
"code": 200,
|
||||
"depts": result,
|
||||
"checkedKeys": menuIds,
|
||||
})
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
package dict
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 字典数据列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Param status query string false "status"
|
||||
// @Param dictCode query string false "dictCode"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/data/list [get]
|
||||
// @Security Bearer
|
||||
func GetDictDataList(c *gin.Context) {
|
||||
var data models.DictData
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.DictLabel = c.Request.FormValue("dictLabel")
|
||||
data.Status = c.Request.FormValue("status")
|
||||
data.DictType = c.Request.FormValue("dictType")
|
||||
id := c.Request.FormValue("dictCode")
|
||||
data.DictCode, _ = tools.StringToInt(id)
|
||||
data.DataScope = tools.GetUserIdStr(c)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 通过编码获取字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Param dictCode path int true "字典编码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/data/{dictCode} [get]
|
||||
// @Security Bearer
|
||||
func GetDictData(c *gin.Context) {
|
||||
var DictData models.DictData
|
||||
DictData.DictLabel = c.Request.FormValue("dictLabel")
|
||||
DictData.DictCode, _ = tools.StringToInt(c.Param("dictCode"))
|
||||
result, err := DictData.GetByCode()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 通过字典类型获取字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Param dictType path int true "dictType"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/databyType/{dictType} [get]
|
||||
// @Security Bearer
|
||||
func GetDictDataByDictType(c *gin.Context) {
|
||||
var DictData models.DictData
|
||||
DictData.DictType = c.Param("dictType")
|
||||
result, err := DictData.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 添加字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.DictType true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dict/data [post]
|
||||
// @Security Bearer
|
||||
func InsertDictData(c *gin.Context) {
|
||||
var data models.DictData
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", 500)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 修改字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.DictType true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dict/data [put]
|
||||
// @Security Bearer
|
||||
func UpdateDictData(c *gin.Context) {
|
||||
var data models.DictData
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", -1)
|
||||
result, err := data.Update(data.DictCode)
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 删除字典数据
|
||||
// @Description 删除数据
|
||||
// @Tags 字典数据
|
||||
// @Param dictCode path int true "dictCode"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/dict/data/{dictCode} [delete]
|
||||
func DeleteDictData(c *gin.Context) {
|
||||
var data models.DictData
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("dictCode", c)
|
||||
result, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "修改失败", 500)
|
||||
app.OK(c, result, "删除成功")
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package dict
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 字典类型列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Param dictName query string false "dictName"
|
||||
// @Param dictId query string false "dictId"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type/list [get]
|
||||
// @Security Bearer
|
||||
func GetDictTypeList(c *gin.Context) {
|
||||
var data models.DictType
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.DictName = c.Request.FormValue("dictName")
|
||||
id := c.Request.FormValue("dictId")
|
||||
data.DictId, _ = tools.StringToInt(id)
|
||||
data.DictType = c.Request.FormValue("dictType")
|
||||
data.DataScope = tools.GetUserIdStr(c)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 通过字典id获取字典类型
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Param dictId path int true "字典类型编码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type/{dictId} [get]
|
||||
// @Security Bearer
|
||||
func GetDictType(c *gin.Context) {
|
||||
var DictType models.DictType
|
||||
DictType.DictName = c.Request.FormValue("dictName")
|
||||
DictType.DictId, _ = tools.StringToInt(c.Param("dictId"))
|
||||
result, err := DictType.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
func GetDictTypeOptionSelect(c *gin.Context) {
|
||||
var DictType models.DictType
|
||||
DictType.DictName = c.Request.FormValue("dictName")
|
||||
DictType.DictId, _ = tools.StringToInt(c.Param("dictId"))
|
||||
result, err := DictType.GetList()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 添加字典类型
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.DictType true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dict/type [post]
|
||||
// @Security Bearer
|
||||
func InsertDictType(c *gin.Context) {
|
||||
var data models.DictType
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", 500)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 修改字典类型
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.DictType true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dict/type [put]
|
||||
// @Security Bearer
|
||||
func UpdateDictType(c *gin.Context) {
|
||||
var data models.DictType
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", -1)
|
||||
result, err := data.Update(data.DictId)
|
||||
tools.HasError(err, "", -1)
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 删除字典类型
|
||||
// @Description 删除数据
|
||||
// @Tags 字典类型
|
||||
// @Param dictId path int true "dictId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/dict/type/{dictId} [delete]
|
||||
func DeleteDictType(c *gin.Context) {
|
||||
var data models.DictType
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("dictId", c)
|
||||
result, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "修改失败", 500)
|
||||
app.OK(c, result, "删除成功")
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
)
|
||||
|
||||
func GetInfo(c *gin.Context) {
|
||||
|
||||
var roles = make([]string, 1)
|
||||
roles[0] = tools.GetRoleName(c)
|
||||
|
||||
var permissions = make([]string, 1)
|
||||
permissions[0] = "*:*:*"
|
||||
|
||||
var buttons = make([]string, 1)
|
||||
buttons[0] = "*:*:*"
|
||||
|
||||
RoleMenu := models.RoleMenu{}
|
||||
RoleMenu.RoleId = tools.GetRoleId(c)
|
||||
|
||||
var mp = make(map[string]interface{})
|
||||
mp["roles"] = roles
|
||||
if tools.GetRoleName(c) == "admin" || tools.GetRoleName(c) == "系统管理员" {
|
||||
mp["permissions"] = permissions
|
||||
mp["buttons"] = buttons
|
||||
} else {
|
||||
list, _ := RoleMenu.GetPermis()
|
||||
mp["permissions"] = list
|
||||
mp["buttons"] = list
|
||||
}
|
||||
|
||||
sysuser := models.SysUser{}
|
||||
sysuser.UserId = tools.GetUserId(c)
|
||||
user, err := sysuser.Get()
|
||||
tools.HasError(err, "", 500)
|
||||
|
||||
mp["introduction"] = " am a super administrator"
|
||||
|
||||
mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
|
||||
if user.Avatar != "" {
|
||||
mp["avatar"] = user.Avatar
|
||||
}
|
||||
mp["userName"] = user.NickName
|
||||
mp["userId"] = user.UserId
|
||||
mp["deptId"] = user.DeptId
|
||||
mp["name"] = user.NickName
|
||||
|
||||
app.OK(c, mp, "")
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
)
|
||||
|
||||
// @Summary Menu列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Param menuName query string false "menuName"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/menulist [get]
|
||||
// @Security Bearer
|
||||
func GetMenuList(c *gin.Context) {
|
||||
var Menu models.Menu
|
||||
Menu.MenuName = c.Request.FormValue("menuName")
|
||||
Menu.Visible = c.Request.FormValue("visible")
|
||||
Menu.Title = c.Request.FormValue("title")
|
||||
Menu.DataScope = tools.GetUserIdStr(c)
|
||||
result, err := Menu.SetMenu()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary Menu列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Param menuName query string false "menuName"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/menu [get]
|
||||
// @Security Bearer
|
||||
func GetMenu(c *gin.Context) {
|
||||
var data models.Menu
|
||||
id, err := tools.StringToInt(c.Param("id"))
|
||||
data.MenuId = id
|
||||
result, err := data.GetByMenuId()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
func GetMenuTreeRoleselect(c *gin.Context) {
|
||||
var Menu models.Menu
|
||||
var SysRole models.SysRole
|
||||
id, err := tools.StringToInt(c.Param("roleId"))
|
||||
SysRole.RoleId = id
|
||||
result, err := Menu.SetMenuLable()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
menuIds := make([]int, 0)
|
||||
if id != 0 {
|
||||
menuIds, err = SysRole.GetRoleMeunId()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
}
|
||||
app.Custum(c, gin.H{
|
||||
"code": 200,
|
||||
"menus": result,
|
||||
"checkedKeys": menuIds,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取菜单树
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Product application/x-www-form-urlencoded
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/menuTreeselect [get]
|
||||
// @Security Bearer
|
||||
func GetMenuTreeelect(c *gin.Context) {
|
||||
var data models.Menu
|
||||
result, err := data.SetMenuLable()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 创建菜单
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Product application/x-www-form-urlencoded
|
||||
// @Param menuName formData string true "menuName"
|
||||
// @Param Path formData string false "Path"
|
||||
// @Param Action formData string true "Action"
|
||||
// @Param Permission formData string true "Permission"
|
||||
// @Param ParentId formData string true "ParentId"
|
||||
// @Param IsDel formData string true "IsDel"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/menu [post]
|
||||
// @Security Bearer
|
||||
func InsertMenu(c *gin.Context) {
|
||||
var data models.Menu
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 修改菜单
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Product application/x-www-form-urlencoded
|
||||
// @Param id path int true "id"
|
||||
// @Param data body models.Menu true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "修改成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "修改失败"}"
|
||||
// @Router /api/v1/menu/{id} [put]
|
||||
// @Security Bearer
|
||||
func UpdateMenu(c *gin.Context) {
|
||||
var data models.Menu
|
||||
err2 := c.BindWith(&data, binding.JSON)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err2, "修改失败", -1)
|
||||
_, err := data.Update(data.MenuId)
|
||||
tools.HasError(err, "", 501)
|
||||
app.OK(c, "", "修改成功")
|
||||
|
||||
}
|
||||
|
||||
// @Summary 删除菜单
|
||||
// @Description 删除数据
|
||||
// @Tags 菜单
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/menu/{id} [delete]
|
||||
func DeleteMenu(c *gin.Context) {
|
||||
var data models.Menu
|
||||
id, err := tools.StringToInt(c.Param("id"))
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
_, err = data.Delete(id)
|
||||
tools.HasError(err, "删除失败", 500)
|
||||
app.OK(c, "", "删除成功")
|
||||
}
|
||||
|
||||
// @Summary 根据角色名称获取菜单列表数据(左菜单使用)
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/menurole [get]
|
||||
// @Security Bearer
|
||||
func GetMenuRole(c *gin.Context) {
|
||||
var Menu models.Menu
|
||||
result, err := Menu.SetMenuRole(tools.GetRoleName(c))
|
||||
tools.HasError(err, "获取失败", 500)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 获取角色对应的菜单id数组
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/menuids/{id} [get]
|
||||
// @Security Bearer
|
||||
func GetMenuIDS(c *gin.Context) {
|
||||
var data models.RoleMenu
|
||||
data.RoleName = c.GetString("role")
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
result, err := data.GetIDS()
|
||||
tools.HasError(err, "获取失败", 500)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
)
|
||||
|
||||
// @Summary 岗位列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Param postName query string false "postName"
|
||||
// @Param postCode query string false "postCode"
|
||||
// @Param postId query string false "postId"
|
||||
// @Param status query string false "status"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post [get]
|
||||
// @Security Bearer
|
||||
func GetPostList(c *gin.Context) {
|
||||
var data models.Post
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
id := c.Request.FormValue("postId")
|
||||
data.PostId, _ = tools.StringToInt(id)
|
||||
|
||||
data.PostCode = c.Request.FormValue("postCode")
|
||||
data.PostName = c.Request.FormValue("postName")
|
||||
data.Status = c.Request.FormValue("status")
|
||||
|
||||
data.DataScope = tools.GetUserIdStr(c)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
app.PageOK(c, result, count, pageIndex, pageSize, "")
|
||||
}
|
||||
|
||||
// @Summary 获取岗位信息
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Param postId path int true "postId"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post/{postId} [get]
|
||||
// @Security Bearer
|
||||
func GetPost(c *gin.Context) {
|
||||
var Post models.Post
|
||||
Post.PostId, _ = tools.StringToInt(c.Param("postId"))
|
||||
result, err := Post.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 添加岗位
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.Post true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/post [post]
|
||||
// @Security Bearer
|
||||
func InsertPost(c *gin.Context) {
|
||||
var data models.Post
|
||||
err := c.Bind(&data)
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", 500)
|
||||
result, err := data.Create()
|
||||
tools.HasError(err, "", -1)
|
||||
app.OK(c, result, "")
|
||||
}
|
||||
|
||||
// @Summary 修改岗位
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.Post true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/post/ [put]
|
||||
// @Security Bearer
|
||||
func UpdatePost(c *gin.Context) {
|
||||
var data models.Post
|
||||
|
||||
err := c.Bind(&data)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
tools.HasError(err, "", -1)
|
||||
result, err := data.Update(data.PostId)
|
||||
tools.HasError(err, "", -1)
|
||||
app.OK(c, result, "修改成功")
|
||||
}
|
||||
|
||||
// @Summary 删除岗位
|
||||
// @Description 删除数据
|
||||
// @Tags 岗位
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 500 {string} string "{"code": 500, "message": "删除失败"}"
|
||||
// @Router /api/v1/post/{postId} [delete]
|
||||
func DeletePost(c *gin.Context) {
|
||||
var data models.Post
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("postId", c)
|
||||
result, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "删除失败", 500)
|
||||
app.OK(c, result, "删除成功")
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
)
|
||||
|
||||
// @Summary 角色列表数据
|
||||
// @Description Get JSON
|
||||
// @Tags 角色/Role
|
||||
// @Param roleName query string false "roleName"
|
||||
// @Param status query string false "status"
|
||||
// @Param roleKey query string false "roleKey"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/rolelist [get]
|
||||
// @Security Bearer
|
||||
func GetRoleList(c *gin.Context) {
|
||||
var data models.SysRole
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.RoleKey = c.Request.FormValue("roleKey")
|
||||
data.RoleName = c.Request.FormValue("roleName")
|
||||
data.Status = c.Request.FormValue("status")
|
||||
data.DataScope = tools.GetUserIdStr(c)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
app.PageOK(c, result, count, pageIndex, pageSize, "")
|
||||
}
|
||||
|
||||
// @Summary 获取Role数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Param roleId path string false "roleId"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/role [get]
|
||||
// @Security Bearer
|
||||
func GetRole(c *gin.Context) {
|
||||
var Role models.SysRole
|
||||
Role.RoleId, _ = tools.StringToInt(c.Param("roleId"))
|
||||
result, err := Role.Get()
|
||||
menuIds := make([]int, 0)
|
||||
menuIds, err = Role.GetRoleMeunId()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
result.MenuIds = menuIds
|
||||
app.OK(c, result, "")
|
||||
|
||||
}
|
||||
|
||||
// @Summary 创建角色
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysRole true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/role [post]
|
||||
func InsertRole(c *gin.Context) {
|
||||
var data models.SysRole
|
||||
data.CreateBy = tools.GetUserIdStr(c)
|
||||
err := c.BindWith(&data, binding.JSON)
|
||||
tools.HasError(err, "", 500)
|
||||
id, err := data.Insert()
|
||||
data.RoleId = id
|
||||
tools.HasError(err, "", -1)
|
||||
var t models.RoleMenu
|
||||
_, err = t.Insert(id, data.MenuIds)
|
||||
tools.HasError(err, "", -1)
|
||||
app.OK(c, data, "添加成功")
|
||||
}
|
||||
|
||||
// @Summary 修改用户角色
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysRole true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "修改成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "修改失败"}"
|
||||
// @Router /api/v1/role [put]
|
||||
func UpdateRole(c *gin.Context) {
|
||||
var data models.SysRole
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
err := c.Bind(&data)
|
||||
tools.HasError(err, "数据解析失败", -1)
|
||||
result, err := data.Update(data.RoleId)
|
||||
tools.HasError(err, "", -1)
|
||||
var t models.RoleMenu
|
||||
_, err = t.DeleteRoleMenu(data.RoleId)
|
||||
tools.HasError(err, "添加失败1", -1)
|
||||
_, err2 := t.Insert(data.RoleId, data.MenuIds)
|
||||
tools.HasError(err2, "添加失败2", -1)
|
||||
|
||||
app.OK(c, result, "修改成功")
|
||||
}
|
||||
|
||||
func UpdateRoleDataScope(c *gin.Context) {
|
||||
var data models.SysRole
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
err := c.Bind(&data)
|
||||
tools.HasError(err, "数据解析失败", -1)
|
||||
result, err := data.Update(data.RoleId)
|
||||
|
||||
var t models.SysRoleDept
|
||||
_, err = t.DeleteRoleDept(data.RoleId)
|
||||
tools.HasError(err, "添加失败1", -1)
|
||||
if data.DataScope == "2" {
|
||||
_, err2 := t.Insert(data.RoleId, data.DeptIds)
|
||||
tools.HasError(err2, "添加失败2", -1)
|
||||
}
|
||||
app.OK(c, result, "修改成功")
|
||||
}
|
||||
|
||||
// @Summary 删除用户角色
|
||||
// @Description 删除数据
|
||||
// @Tags 角色/Role
|
||||
// @Param roleId path int true "roleId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/role/{roleId} [delete]
|
||||
func DeleteRole(c *gin.Context) {
|
||||
var Role models.SysRole
|
||||
Role.UpdateBy = tools.GetUserIdStr(c)
|
||||
|
||||
IDS := tools.IdsStrToIdsIntGroup("roleId", c)
|
||||
_, err := Role.BatchDelete(IDS)
|
||||
tools.HasError(err, "删除失败1", -1)
|
||||
var t models.RoleMenu
|
||||
_, err = t.BatchDeleteRoleMenu(IDS)
|
||||
tools.HasError(err, "删除失败1", -1)
|
||||
app.OK(c, "", "删除成功")
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/models"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary RoleMenu列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色菜单
|
||||
// @Param RoleId query string false "RoleId"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/rolemenu [get]
|
||||
// @Security Bearer
|
||||
func GetRoleMenu(c *gin.Context) {
|
||||
var Rm models.RoleMenu
|
||||
err := c.ShouldBind(&Rm)
|
||||
result, err := Rm.Get()
|
||||
var res app.Response
|
||||
if err != nil {
|
||||
res.Msg = "抱歉未找到相关信息"
|
||||
c.JSON(http.StatusOK, res.ReturnError(200))
|
||||
return
|
||||
}
|
||||
res.Data = result
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
type RoleMenuPost struct {
|
||||
RoleId string
|
||||
RoleMenu []models.RoleMenu
|
||||
}
|
||||
|
||||
func InsertRoleMenu(c *gin.Context) {
|
||||
|
||||
var res app.Response
|
||||
res.Msg = "添加成功"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// @Summary 删除用户菜单数据
|
||||
// @Description 删除数据
|
||||
// @Tags 角色菜单
|
||||
// @Param id path string true "id"
|
||||
// @Param menu_id query string false "menu_id"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/rolemenu/{id} [delete]
|
||||
func DeleteRoleMenu(c *gin.Context) {
|
||||
var t models.RoleMenu
|
||||
id := c.Param("id")
|
||||
menuId := c.Request.FormValue("menu_id")
|
||||
fmt.Println(menuId)
|
||||
_, err := t.Delete(id, menuId)
|
||||
if err != nil {
|
||||
var res app.Response
|
||||
res.Msg = "删除失败"
|
||||
c.JSON(http.StatusOK, res.ReturnError(200))
|
||||
return
|
||||
}
|
||||
var res app.Response
|
||||
res.Msg = "删除成功"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
return
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/google/uuid"
|
||||
"go-admin/global"
|
||||
"go-admin/models"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
)
|
||||
|
||||
// @Summary 列表用户信息数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Param username query string false "username"
|
||||
// @Success 200 {string} string "{"code": 200, "data": [...]}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
|
||||
// @Router /api/v1/sysUserList [get]
|
||||
// @Security Bearer
|
||||
func GetSysUserList(c *gin.Context) {
|
||||
var data models.SysUser
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
size := c.Request.FormValue("pageSize")
|
||||
if size != "" {
|
||||
pageSize = tools.StrToInt(err, size)
|
||||
}
|
||||
|
||||
index := c.Request.FormValue("pageIndex")
|
||||
if index != "" {
|
||||
pageIndex = tools.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.Username = c.Request.FormValue("username")
|
||||
data.Status = c.Request.FormValue("status")
|
||||
data.Phone = c.Request.FormValue("phone")
|
||||
|
||||
postId := c.Request.FormValue("postId")
|
||||
data.PostId, _ = tools.StringToInt(postId)
|
||||
|
||||
deptId := c.Request.FormValue("deptId")
|
||||
data.DeptId, _ = tools.StringToInt(deptId)
|
||||
|
||||
data.DataScope = tools.GetUserIdStr(c)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools.HasError(err, "", -1)
|
||||
|
||||
app.PageOK(c, result, count, pageIndex, pageSize, "")
|
||||
}
|
||||
|
||||
// @Summary 获取用户
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Param userId path int true "用户编码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sysUser/{userId} [get]
|
||||
// @Security Bearer
|
||||
func GetSysUser(c *gin.Context) {
|
||||
var SysUser models.SysUser
|
||||
SysUser.UserId, _ = tools.StringToInt(c.Param("userId"))
|
||||
result, err := SysUser.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
var SysRole models.SysRole
|
||||
var Post models.Post
|
||||
roles, err := SysRole.GetList()
|
||||
posts, err := Post.GetList()
|
||||
|
||||
postIds := make([]int, 0)
|
||||
postIds = append(postIds, result.PostId)
|
||||
|
||||
roleIds := make([]int, 0)
|
||||
roleIds = append(roleIds, result.RoleId)
|
||||
app.Custum(c, gin.H{
|
||||
"code": 200,
|
||||
"data": result,
|
||||
"postIds": postIds,
|
||||
"roleIds": roleIds,
|
||||
"roles": roles,
|
||||
"posts": posts,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取个人中心用户
|
||||
// @Description 获取JSON
|
||||
// @Tags 个人中心
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/user/profile [get]
|
||||
// @Security Bearer
|
||||
func GetSysUserProfile(c *gin.Context) {
|
||||
var SysUser models.SysUser
|
||||
userId := tools.GetUserIdStr(c)
|
||||
SysUser.UserId, _ = tools.StringToInt(userId)
|
||||
result, err := SysUser.Get()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
var SysRole models.SysRole
|
||||
var Post models.Post
|
||||
var Dept models.SysDept
|
||||
//获取角色列表
|
||||
roles, err := SysRole.GetList()
|
||||
//获取职位列表
|
||||
posts, err := Post.GetList()
|
||||
//获取部门列表
|
||||
Dept.DeptId = result.DeptId
|
||||
dept, err := Dept.Get()
|
||||
|
||||
postIds := make([]int, 0)
|
||||
postIds = append(postIds, result.PostId)
|
||||
|
||||
roleIds := make([]int, 0)
|
||||
roleIds = append(roleIds, result.RoleId)
|
||||
|
||||
app.Custum(c, gin.H{
|
||||
"code": 200,
|
||||
"data": result,
|
||||
"postIds": postIds,
|
||||
"roleIds": roleIds,
|
||||
"roles": roles,
|
||||
"posts": posts,
|
||||
"dept": dept,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取用户角色和职位
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sysUser [get]
|
||||
// @Security Bearer
|
||||
func GetSysUserInit(c *gin.Context) {
|
||||
var SysRole models.SysRole
|
||||
var Post models.Post
|
||||
roles, err := SysRole.GetList()
|
||||
posts, err := Post.GetList()
|
||||
tools.HasError(err, "抱歉未找到相关信息", -1)
|
||||
mp := make(map[string]interface{}, 2)
|
||||
mp["roles"] = roles
|
||||
mp["posts"] = posts
|
||||
app.OK(c, mp, "")
|
||||
}
|
||||
|
||||
// @Summary 创建用户
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysUser true "用户数据"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/sysUser [post]
|
||||
func InsertSysUser(c *gin.Context) {
|
||||
var sysuser models.SysUser
|
||||
err := c.BindWith(&sysuser, binding.JSON)
|
||||
tools.HasError(err, "非法数据格式", 500)
|
||||
|
||||
sysuser.CreateBy = tools.GetUserIdStr(c)
|
||||
id, err := sysuser.Insert()
|
||||
tools.HasError(err, "添加失败", 500)
|
||||
app.OK(c, id, "添加成功")
|
||||
}
|
||||
|
||||
// @Summary 修改用户数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body models.SysUser true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "修改成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "修改失败"}"
|
||||
// @Router /api/v1/sysuser/{userId} [put]
|
||||
func UpdateSysUser(c *gin.Context) {
|
||||
var data models.SysUser
|
||||
err := c.Bind(&data)
|
||||
tools.HasError(err, "数据解析失败", -1)
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
result, err := data.Update(data.UserId)
|
||||
tools.HasError(err, "修改失败", 500)
|
||||
app.OK(c, result, "修改成功")
|
||||
}
|
||||
|
||||
// @Summary 删除用户数据
|
||||
// @Description 删除数据
|
||||
// @Tags 用户
|
||||
// @Param userId path int true "userId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/sysuser/{userId} [delete]
|
||||
func DeleteSysUser(c *gin.Context) {
|
||||
var data models.SysUser
|
||||
data.UpdateBy = tools.GetUserIdStr(c)
|
||||
IDS := tools.IdsStrToIdsIntGroup("userId", c)
|
||||
result, err := data.BatchDelete(IDS)
|
||||
tools.HasError(err, "删除失败", 500)
|
||||
app.OK(c, result, "删除成功")
|
||||
}
|
||||
|
||||
// @Summary 修改头像
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept multipart/form-data
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/user/profileAvatar [post]
|
||||
func InsetSysUserAvatar(c *gin.Context) {
|
||||
form, _ := c.MultipartForm()
|
||||
files := form.File["upload[]"]
|
||||
guid := uuid.New().String()
|
||||
filPath := "static/uploadfile/" + guid + ".jpg"
|
||||
for _, file := range files {
|
||||
global.Logger.Debug(file.Filename)
|
||||
// 上传文件至指定目录
|
||||
_ = c.SaveUploadedFile(file, filPath)
|
||||
}
|
||||
sysuser := models.SysUser{}
|
||||
sysuser.UserId = tools.GetUserId(c)
|
||||
sysuser.Avatar = "/" + filPath
|
||||
sysuser.UpdateBy = tools.GetUserIdStr(c)
|
||||
sysuser.Update(sysuser.UserId)
|
||||
app.OK(c, filPath, "修改成功")
|
||||
}
|
||||
|
||||
func SysUserUpdatePwd(c *gin.Context) {
|
||||
var pwd models.SysUserPwd
|
||||
err := c.Bind(&pwd)
|
||||
tools.HasError(err, "数据解析失败", 500)
|
||||
sysuser := models.SysUser{}
|
||||
sysuser.UserId = tools.GetUserId(c)
|
||||
sysuser.SetPwd(pwd)
|
||||
app.OK(c, "", "密码修改成功")
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/models/tools"
|
||||
tools2 "go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 分页列表数据 / page list data
|
||||
// @Description 数据库表列分页列表 / database table column page list
|
||||
// @Tags 工具 / Tools
|
||||
// @Param tableName query string false "tableName / 数据表名称"
|
||||
// @Param pageSize query int false "pageSize / 页条数"
|
||||
// @Param pageIndex query int false "pageIndex / 页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/db/columns/page [get]
|
||||
func GetDBColumnList(c *gin.Context) {
|
||||
var data tools.DBColumns
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools2.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools2.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.TableName = c.Request.FormValue("tableName")
|
||||
tools2.Assert(data.TableName == "", "table name cannot be empty!", 500)
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools2.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/models/tools"
|
||||
tools2 "go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"go-admin/tools/config"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// @Summary 分页列表数据 / page list data
|
||||
// @Description 数据库表分页列表 / database table page list
|
||||
// @Tags 工具 / Tools
|
||||
// @Param tableName query string false "tableName / 数据表名称"
|
||||
// @Param pageSize query int false "pageSize / 页条数"
|
||||
// @Param pageIndex query int false "pageIndex / 页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/db/tables/page [get]
|
||||
func GetDBTableList(c *gin.Context) {
|
||||
var res app.Response
|
||||
var data tools.DBTables
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
if config.DatabaseConfig.Driver == "sqlite3" || config.DatabaseConfig.Driver == "postgres" {
|
||||
res.Msg = "对不起,sqlite3 或 postgres 不支持代码生成!"
|
||||
c.JSON(http.StatusOK, res.ReturnError(500))
|
||||
return
|
||||
}
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize = tools2.StrToInt(err, size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex = tools2.StrToInt(err, index)
|
||||
}
|
||||
|
||||
data.TableName = c.Request.FormValue("tableName")
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools2.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/global"
|
||||
"go-admin/models"
|
||||
"go-admin/models/tools"
|
||||
tools2 "go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"go-admin/tools/config"
|
||||
"net/http"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func Preview(c *gin.Context) {
|
||||
table := tools.SysTables{}
|
||||
id, err := tools2.StringToInt(c.Param("tableId"))
|
||||
tools2.HasError(err, "", -1)
|
||||
table.TableId = id
|
||||
t1, err := template.ParseFiles("template/model.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t2, err := template.ParseFiles("template/api.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t3, err := template.ParseFiles("template/js.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t4, err := template.ParseFiles("template/vue.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t5, err := template.ParseFiles("template/router.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
tab, _ := table.Get()
|
||||
var b1 bytes.Buffer
|
||||
err = t1.Execute(&b1, tab)
|
||||
var b2 bytes.Buffer
|
||||
err = t2.Execute(&b2, tab)
|
||||
var b3 bytes.Buffer
|
||||
err = t3.Execute(&b3, tab)
|
||||
var b4 bytes.Buffer
|
||||
err = t4.Execute(&b4, tab)
|
||||
var b5 bytes.Buffer
|
||||
err = t5.Execute(&b5, tab)
|
||||
|
||||
mp := make(map[string]interface{})
|
||||
mp["template/model.go.template"] = b1.String()
|
||||
mp["template/api.go.template"] = b2.String()
|
||||
mp["template/js.go.template"] = b3.String()
|
||||
mp["template/vue.go.template"] = b4.String()
|
||||
mp["template/router.go.template"] = b5.String()
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
func GenCode(c *gin.Context) {
|
||||
table := tools.SysTables{}
|
||||
id, err := tools2.StringToInt(c.Param("tableId"))
|
||||
tools2.HasError(err, "", -1)
|
||||
table.TableId = id
|
||||
tab, _ := table.Get()
|
||||
ischeckrole := true
|
||||
|
||||
rouyerfile := "template/routercheckrole.go.template"
|
||||
|
||||
if c.Param("ischeckrole") != "" {
|
||||
ischeckrole, err = tools2.StringToBool(c.Query("ischeckrole"))
|
||||
if err != nil {
|
||||
ischeckrole = true
|
||||
}
|
||||
}
|
||||
|
||||
oldTest := "// {{认证路由自动补充在此处请勿删除}}"
|
||||
newText := "// {{认证路由自动补充在此处请勿删除}} \r\n register" + tab.ClassName + "Router(v1,authMiddleware)"
|
||||
|
||||
if !ischeckrole {
|
||||
oldTest = "// {{无需认证路由自动补充在此处请勿删除}}"
|
||||
newText = "// {{无需认证路由自动补充在此处请勿删除}} \r\n register" + tab.ClassName + "Router(v1)"
|
||||
rouyerfile = "template/routernocheckrole.go.template"
|
||||
}
|
||||
|
||||
t1, err := template.ParseFiles("template/model.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t2, err := template.ParseFiles("template/api.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t3, err := template.ParseFiles(rouyerfile)
|
||||
tools2.HasError(err, "", -1)
|
||||
t4, err := template.ParseFiles("template/js.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
t5, err := template.ParseFiles("template/vue.go.template")
|
||||
tools2.HasError(err, "", -1)
|
||||
|
||||
_ = tools2.PathCreate("./apis/" + tab.ModuleName + "/")
|
||||
_ = tools2.PathCreate("./models/")
|
||||
_ = tools2.PathCreate("./router/")
|
||||
_ = tools2.PathCreate(config.GenConfig.FrontPath + "/api/")
|
||||
_ = tools2.PathCreate(config.GenConfig.FrontPath + "/views/" + tab.PackageName)
|
||||
|
||||
var b1 bytes.Buffer
|
||||
err = t1.Execute(&b1, tab)
|
||||
var b2 bytes.Buffer
|
||||
err = t2.Execute(&b2, tab)
|
||||
var b3 bytes.Buffer
|
||||
err = t3.Execute(&b3, tab)
|
||||
var b4 bytes.Buffer
|
||||
err = t4.Execute(&b4, tab)
|
||||
var b5 bytes.Buffer
|
||||
err = t5.Execute(&b5, tab)
|
||||
tools2.FileCreate(b1, "./models/"+tab.PackageName+".go")
|
||||
tools2.FileCreate(b2, "./apis/"+tab.ModuleName+"/"+tab.PackageName+".go")
|
||||
tools2.FileCreate(b3, "./router/"+tab.PackageName+".go")
|
||||
tools2.FileCreate(b4, config.GenConfig.FrontPath+"/api/"+tab.PackageName+".js")
|
||||
tools2.FileCreate(b5, config.GenConfig.FrontPath+"/views/"+tab.PackageName+"/index.vue")
|
||||
|
||||
helper := tools2.ReplaceHelper{
|
||||
Root: "./router/router.go",
|
||||
OldText: oldTest,
|
||||
NewText: newText,
|
||||
}
|
||||
if helper.OldText == helper.NewText {
|
||||
global.Logger.Println("error !! the NewText isEqual the OldText")
|
||||
return
|
||||
}
|
||||
if err := helper.DoWrok(); err != nil {
|
||||
global.Logger.Print("error:", err.Error())
|
||||
|
||||
} else {
|
||||
global.Logger.Print("done!")
|
||||
}
|
||||
|
||||
app.OK(c, "", "Code generated successfully!")
|
||||
}
|
||||
|
||||
func GenMenuAndApi(c *gin.Context) {
|
||||
|
||||
table := tools.SysTables{}
|
||||
timeNow := tools2.GetCurrentTime()
|
||||
id, err := tools2.StringToInt(c.Param("tableId"))
|
||||
tools2.HasError(err, "", -1)
|
||||
table.TableId = id
|
||||
tab, _ := table.Get()
|
||||
Mmenu := models.Menu{}
|
||||
Mmenu.MenuName = tab.TBName + "管理"
|
||||
Mmenu.Title = tab.TableComment
|
||||
Mmenu.Icon = "pass"
|
||||
Mmenu.Path = "/" + tab.TBName
|
||||
Mmenu.MenuType = "M"
|
||||
Mmenu.Action = "无"
|
||||
Mmenu.ParentId = 0
|
||||
Mmenu.NoCache = false
|
||||
Mmenu.Component = "Layout"
|
||||
Mmenu.Sort = 0
|
||||
Mmenu.Visible = "0"
|
||||
Mmenu.IsFrame = "0"
|
||||
Mmenu.CreateBy = "1"
|
||||
Mmenu.UpdateBy = "1"
|
||||
Mmenu.CreatedAt = timeNow
|
||||
Mmenu.UpdatedAt = timeNow
|
||||
Mmenu.MenuId, err = Mmenu.Create()
|
||||
|
||||
Cmenu := models.Menu{}
|
||||
Cmenu.MenuName = tab.TBName + "管理"
|
||||
Cmenu.Title = tab.TableComment
|
||||
Cmenu.Icon = "pass"
|
||||
Cmenu.Path = tab.TBName
|
||||
Cmenu.MenuType = "C"
|
||||
Cmenu.Action = "无"
|
||||
Cmenu.Permission = tab.PackageName + ":" + tab.ModuleName + ":list"
|
||||
Cmenu.ParentId = Mmenu.MenuId
|
||||
Cmenu.NoCache = false
|
||||
Cmenu.Component = "/" + tab.ModuleName + "/index"
|
||||
Cmenu.Sort = 0
|
||||
Cmenu.Visible = "0"
|
||||
Cmenu.IsFrame = "0"
|
||||
Cmenu.CreateBy = "1"
|
||||
Cmenu.UpdateBy = "1"
|
||||
Cmenu.CreatedAt = timeNow
|
||||
Cmenu.UpdatedAt = timeNow
|
||||
Cmenu.MenuId, err = Cmenu.Create()
|
||||
|
||||
MList := models.Menu{}
|
||||
MList.MenuName = tab.TBName
|
||||
MList.Title = "分页获取" + tab.TableComment
|
||||
MList.Icon = "pass"
|
||||
MList.Path = tab.TBName
|
||||
MList.MenuType = "F"
|
||||
MList.Action = "无"
|
||||
MList.Permission = tab.PackageName + ":" + tab.ModuleName + ":query"
|
||||
MList.ParentId = Cmenu.MenuId
|
||||
MList.NoCache = false
|
||||
MList.Sort = 0
|
||||
MList.Visible = "0"
|
||||
MList.IsFrame = "0"
|
||||
MList.CreateBy = "1"
|
||||
MList.UpdateBy = "1"
|
||||
MList.CreatedAt = timeNow
|
||||
MList.UpdatedAt = timeNow
|
||||
MList.MenuId, err = MList.Create()
|
||||
|
||||
MCreate := models.Menu{}
|
||||
MCreate.MenuName = tab.TBName
|
||||
MCreate.Title = "创建" + tab.TableComment
|
||||
MCreate.Icon = "pass"
|
||||
MCreate.Path = tab.TBName
|
||||
MCreate.MenuType = "F"
|
||||
MCreate.Action = "无"
|
||||
MCreate.Permission = tab.PackageName + ":" + tab.ModuleName + ":add"
|
||||
MCreate.ParentId = Cmenu.MenuId
|
||||
MCreate.NoCache = false
|
||||
MCreate.Sort = 0
|
||||
MCreate.Visible = "0"
|
||||
MCreate.IsFrame = "0"
|
||||
MCreate.CreateBy = "1"
|
||||
MCreate.UpdateBy = "1"
|
||||
MCreate.CreatedAt = timeNow
|
||||
MCreate.UpdatedAt = timeNow
|
||||
MCreate.MenuId, err = MCreate.Create()
|
||||
|
||||
MUpdate := models.Menu{}
|
||||
MUpdate.MenuName = tab.TBName
|
||||
MUpdate.Title = "修改" + tab.TableComment
|
||||
MUpdate.Icon = "pass"
|
||||
MUpdate.Path = tab.TBName
|
||||
MUpdate.MenuType = "F"
|
||||
MUpdate.Action = "无"
|
||||
MUpdate.Permission = tab.PackageName + ":" + tab.ModuleName + ":edit"
|
||||
MUpdate.ParentId = Cmenu.MenuId
|
||||
MUpdate.NoCache = false
|
||||
MUpdate.Sort = 0
|
||||
MUpdate.Visible = "0"
|
||||
MUpdate.IsFrame = "0"
|
||||
MUpdate.CreateBy = "1"
|
||||
MUpdate.UpdateBy = "1"
|
||||
MUpdate.CreatedAt = timeNow
|
||||
MUpdate.UpdatedAt = timeNow
|
||||
MUpdate.MenuId, err = MUpdate.Create()
|
||||
|
||||
MDelete := models.Menu{}
|
||||
MDelete.MenuName = tab.TBName
|
||||
MDelete.Title = "删除" + tab.TableComment
|
||||
MDelete.Icon = "pass"
|
||||
MDelete.Path = tab.TBName
|
||||
MDelete.MenuType = "F"
|
||||
MDelete.Action = "无"
|
||||
MDelete.Permission = tab.PackageName + ":" + tab.ModuleName + ":remove"
|
||||
MDelete.ParentId = Cmenu.MenuId
|
||||
MDelete.NoCache = false
|
||||
MDelete.Sort = 0
|
||||
MDelete.Visible = "0"
|
||||
MDelete.IsFrame = "0"
|
||||
MDelete.CreateBy = "1"
|
||||
MDelete.UpdateBy = "1"
|
||||
MDelete.CreatedAt = timeNow
|
||||
MDelete.UpdatedAt = timeNow
|
||||
MDelete.MenuId, err = MDelete.Create()
|
||||
|
||||
var InterfaceId = 63
|
||||
Amenu := models.Menu{}
|
||||
Amenu.MenuName = tab.TBName
|
||||
Amenu.Title = tab.TableComment
|
||||
Amenu.Icon = "bug"
|
||||
Amenu.Path = tab.TBName
|
||||
Amenu.MenuType = "M"
|
||||
Amenu.Action = "无"
|
||||
Amenu.ParentId = InterfaceId
|
||||
Amenu.NoCache = false
|
||||
Amenu.Sort = 0
|
||||
Amenu.Visible = "1"
|
||||
Amenu.IsFrame = "0"
|
||||
Amenu.CreateBy = "1"
|
||||
Amenu.UpdateBy = "1"
|
||||
Amenu.CreatedAt = timeNow
|
||||
Amenu.UpdatedAt = timeNow
|
||||
Amenu.MenuId, err = Amenu.Create()
|
||||
|
||||
AList := models.Menu{}
|
||||
AList.MenuName = tab.TBName
|
||||
AList.Title = "分页获取" + tab.TableComment
|
||||
AList.Icon = "bug"
|
||||
AList.Path = "/api/v1/" + tab.ModuleName + "List"
|
||||
AList.MenuType = "A"
|
||||
AList.Action = "GET"
|
||||
AList.ParentId = Amenu.MenuId
|
||||
AList.NoCache = false
|
||||
AList.Sort = 0
|
||||
AList.Visible = "1"
|
||||
AList.IsFrame = "0"
|
||||
AList.CreateBy = "1"
|
||||
AList.UpdateBy = "1"
|
||||
AList.CreatedAt = timeNow
|
||||
AList.UpdatedAt = timeNow
|
||||
AList.MenuId, err = AList.Create()
|
||||
|
||||
AGet := models.Menu{}
|
||||
AGet.MenuName = tab.TBName
|
||||
AGet.Title = "根据id获取" + tab.TableComment
|
||||
AGet.Icon = "bug"
|
||||
AGet.Path = "/api/v1/" + tab.ModuleName + "/:id"
|
||||
AGet.MenuType = "A"
|
||||
AGet.Action = "GET"
|
||||
AGet.ParentId = Amenu.MenuId
|
||||
AGet.NoCache = false
|
||||
AGet.Sort = 0
|
||||
AGet.Visible = "1"
|
||||
AGet.IsFrame = "0"
|
||||
AGet.CreateBy = "1"
|
||||
AGet.UpdateBy = "1"
|
||||
AGet.CreatedAt = timeNow
|
||||
AGet.UpdatedAt = timeNow
|
||||
AGet.MenuId, err = AGet.Create()
|
||||
|
||||
ACreate := models.Menu{}
|
||||
ACreate.MenuName = tab.TBName
|
||||
ACreate.Title = "创建" + tab.TableComment
|
||||
ACreate.Icon = "bug"
|
||||
ACreate.Path = "/api/v1/" + tab.ModuleName
|
||||
ACreate.MenuType = "A"
|
||||
ACreate.Action = "POST"
|
||||
ACreate.ParentId = Amenu.MenuId
|
||||
ACreate.NoCache = false
|
||||
ACreate.Sort = 0
|
||||
ACreate.Visible = "1"
|
||||
ACreate.IsFrame = "0"
|
||||
ACreate.CreateBy = "1"
|
||||
ACreate.UpdateBy = "1"
|
||||
ACreate.CreatedAt = timeNow
|
||||
ACreate.UpdatedAt = timeNow
|
||||
ACreate.MenuId, err = ACreate.Create()
|
||||
|
||||
AUpdate := models.Menu{}
|
||||
AUpdate.MenuName = tab.TBName
|
||||
AUpdate.Title = "修改" + tab.TableComment
|
||||
AUpdate.Icon = "bug"
|
||||
AUpdate.Path = "/api/v1/" + tab.ModuleName
|
||||
AUpdate.MenuType = "A"
|
||||
AUpdate.Action = "PUT"
|
||||
AUpdate.ParentId = Amenu.MenuId
|
||||
AUpdate.NoCache = false
|
||||
AUpdate.Sort = 0
|
||||
AUpdate.Visible = "1"
|
||||
AUpdate.IsFrame = "0"
|
||||
AUpdate.CreateBy = "1"
|
||||
AUpdate.UpdateBy = "1"
|
||||
AUpdate.CreatedAt = timeNow
|
||||
AUpdate.UpdatedAt = timeNow
|
||||
AUpdate.MenuId, err = AUpdate.Create()
|
||||
|
||||
ADelete := models.Menu{}
|
||||
ADelete.MenuName = tab.TBName
|
||||
ADelete.Title = "删除" + tab.TableComment
|
||||
ADelete.Icon = "bug"
|
||||
ADelete.Path = "/api/v1/" + tab.ModuleName + "/:id"
|
||||
ADelete.MenuType = "A"
|
||||
ADelete.Action = "DELETE"
|
||||
ADelete.ParentId = Amenu.MenuId
|
||||
ADelete.NoCache = false
|
||||
ADelete.Sort = 0
|
||||
ADelete.Visible = "1"
|
||||
ADelete.IsFrame = "0"
|
||||
ADelete.CreateBy = "1"
|
||||
ADelete.UpdateBy = "1"
|
||||
ADelete.CreatedAt = timeNow
|
||||
ADelete.UpdatedAt = timeNow
|
||||
ADelete.MenuId, err = ADelete.Create()
|
||||
|
||||
app.OK(c, "", "数据生成成功!")
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-admin/models/tools"
|
||||
tools2 "go-admin/tools"
|
||||
"go-admin/tools/app"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// @Summary 分页列表数据
|
||||
// @Description 生成表分页列表
|
||||
// @Tags 工具 - 生成表
|
||||
// @Param tableName query string false "tableName / 数据表名称"
|
||||
// @Param pageSize query int false "pageSize / 页条数"
|
||||
// @Param pageIndex query int false "pageIndex / 页码"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys/tables/page [get]
|
||||
func GetSysTableList(c *gin.Context) {
|
||||
var data tools.SysTables
|
||||
var err error
|
||||
var pageSize = 10
|
||||
var pageIndex = 1
|
||||
|
||||
if size := c.Request.FormValue("pageSize"); size != "" {
|
||||
pageSize, err = tools2.StringToInt(size)
|
||||
}
|
||||
|
||||
if index := c.Request.FormValue("pageIndex"); index != "" {
|
||||
pageIndex, err = tools2.StringToInt(index)
|
||||
}
|
||||
|
||||
data.TBName = c.Request.FormValue("tableName")
|
||||
data.TableComment = c.Request.FormValue("tableComment")
|
||||
result, count, err := data.GetPage(pageSize, pageIndex)
|
||||
tools2.HasError(err, "", -1)
|
||||
|
||||
var mp = make(map[string]interface{}, 3)
|
||||
mp["list"] = result
|
||||
mp["count"] = count
|
||||
mp["pageIndex"] = pageIndex
|
||||
mp["pageSize"] = pageSize
|
||||
|
||||
var res app.Response
|
||||
res.Data = mp
|
||||
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 获取配置
|
||||
// @Description 获取JSON
|
||||
// @Tags 工具 - 生成表
|
||||
// @Param configKey path int true "configKey"
|
||||
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys/tables/info/{tableId} [get]
|
||||
// @Security Bearer
|
||||
func GetSysTables(c *gin.Context) {
|
||||
var data tools.SysTables
|
||||
data.TableId, _ = tools2.StringToInt(c.Param("tableId"))
|
||||
result, err := data.Get()
|
||||
tools2.HasError(err, "抱歉未找到相关信息", -1)
|
||||
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
mp := make(map[string]interface{})
|
||||
mp["rows"] = result.Columns
|
||||
mp["info"] = result
|
||||
res.Data = mp
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 添加表结构
|
||||
// @Description 添加表结构
|
||||
// @Tags 工具 - 生成表
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param tables query string false "tableName / 数据表名称"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/sys/tables/info [post]
|
||||
// @Security Bearer
|
||||
func InsertSysTable(c *gin.Context) {
|
||||
|
||||
tablesList := strings.Split(c.Request.FormValue("tables"), ",")
|
||||
for i := 0; i < len(tablesList); i++ {
|
||||
|
||||
data, err := genTableInit(tablesList, i, c)
|
||||
|
||||
_, err = data.Create()
|
||||
tools2.HasError(err, "", -1)
|
||||
}
|
||||
var res app.Response
|
||||
res.Msg = "添加成功!"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
|
||||
}
|
||||
|
||||
func genTableInit(tablesList []string, i int, c *gin.Context) (tools.SysTables, error) {
|
||||
var data tools.SysTables
|
||||
var dbTable tools.DBTables
|
||||
var dbColumn tools.DBColumns
|
||||
data.TBName = tablesList[i]
|
||||
data.CreateBy = tools2.GetUserIdStr(c)
|
||||
|
||||
dbTable.TableName = data.TBName
|
||||
dbtable, err := dbTable.Get()
|
||||
|
||||
dbColumn.TableName = data.TBName
|
||||
tablenamelist := strings.Split(dbColumn.TableName, "_")
|
||||
for i := 0; i < len(tablenamelist); i++ {
|
||||
strStart := string([]byte(tablenamelist[i])[:1])
|
||||
strend := string([]byte(tablenamelist[i])[1:])
|
||||
data.ClassName += strings.ToUpper(strStart) + strend
|
||||
data.PackageName += strings.ToLower(strStart) + strings.ToLower(strend)
|
||||
data.ModuleName += strings.ToLower(strStart) + strings.ToLower(strend)
|
||||
}
|
||||
data.TplCategory = "crud"
|
||||
data.Crud = true
|
||||
|
||||
dbcolumn, err := dbColumn.GetList()
|
||||
data.CreateBy = tools2.GetUserIdStr(c)
|
||||
data.TableComment = dbtable.TableComment
|
||||
if dbtable.TableComment == "" {
|
||||
data.TableComment = data.ClassName
|
||||
}
|
||||
|
||||
data.FunctionName = data.TableComment
|
||||
data.BusinessName = data.ModuleName
|
||||
data.IsLogicalDelete = "1"
|
||||
data.LogicalDelete = true
|
||||
data.LogicalDeleteColumn = "is_del"
|
||||
|
||||
data.FunctionAuthor = "wenjianzhang"
|
||||
for i := 0; i < len(dbcolumn); i++ {
|
||||
var column tools.SysColumns
|
||||
column.ColumnComment = dbcolumn[i].ColumnComment
|
||||
column.ColumnName = dbcolumn[i].ColumnName
|
||||
column.ColumnType = dbcolumn[i].ColumnType
|
||||
column.Sort = i + 1
|
||||
column.Insert = true
|
||||
column.IsInsert = "1"
|
||||
column.QueryType = "EQ"
|
||||
column.IsPk = "0"
|
||||
|
||||
namelist := strings.Split(dbcolumn[i].ColumnName, "_")
|
||||
for i := 0; i < len(namelist); i++ {
|
||||
strStart := string([]byte(namelist[i])[:1])
|
||||
strend := string([]byte(namelist[i])[1:])
|
||||
column.GoField += strings.ToUpper(strStart) + strend
|
||||
if i == 0 {
|
||||
column.JsonField = strings.ToLower(strStart) + strend
|
||||
} else {
|
||||
column.JsonField += strings.ToUpper(strStart) + strend
|
||||
}
|
||||
}
|
||||
if strings.Contains(dbcolumn[i].ColumnKey, "PR") {
|
||||
column.IsPk = "1"
|
||||
column.Pk = true
|
||||
data.PkColumn = dbcolumn[i].ColumnName
|
||||
data.PkGoField = column.GoField
|
||||
data.PkJsonField = column.JsonField
|
||||
}
|
||||
column.IsRequired = "0"
|
||||
if strings.Contains(dbcolumn[i].IsNullable, "NO") {
|
||||
column.IsRequired = "1"
|
||||
column.Required = true
|
||||
}
|
||||
|
||||
if strings.Contains(dbcolumn[i].ColumnType, "int") {
|
||||
column.GoType = "int"
|
||||
column.HtmlType = "input"
|
||||
} else {
|
||||
column.GoType = "string"
|
||||
column.HtmlType = "input"
|
||||
}
|
||||
|
||||
data.Columns = append(data.Columns, column)
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// @Summary 修改表结构
|
||||
// @Description 修改表结构
|
||||
// @Tags 工具 - 生成表
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body tools.SysTables true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/sys/tables/info [put]
|
||||
// @Security Bearer
|
||||
func UpdateSysTable(c *gin.Context) {
|
||||
var data tools.SysTables
|
||||
err := c.Bind(&data)
|
||||
tools2.HasError(err, "数据解析失败", -1)
|
||||
data.UpdateBy = tools2.GetUserIdStr(c)
|
||||
result, err := data.Update()
|
||||
tools2.HasError(err, "", -1)
|
||||
|
||||
var res app.Response
|
||||
res.Data = result
|
||||
res.Msg = "修改成功"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
|
||||
// @Summary 删除表结构
|
||||
// @Description 删除表结构
|
||||
// @Tags 工具 - 生成表
|
||||
// @Param tableId path int true "tableId"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/sys/tables/info/{tableId} [delete]
|
||||
func DeleteSysTables(c *gin.Context) {
|
||||
var data tools.SysTables
|
||||
IDS := tools2.IdsStrToIdsIntGroup("tableId", c)
|
||||
_, err := data.BatchDelete(IDS)
|
||||
tools2.HasError(err, "删除失败", 500)
|
||||
var res app.Response
|
||||
res.Msg = "删除成功"
|
||||
c.JSON(http.StatusOK, res.ReturnOK())
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/v2/captcha"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
)
|
||||
|
||||
type System struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GenerateCaptchaHandler 获取验证码
|
||||
// @Summary 获取验证码
|
||||
// @Description 获取验证码
|
||||
// @Tags 登陆
|
||||
// @Success 200 {object} response.Response{data=string,id=string,msg=string} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/captcha [get]
|
||||
func (e System) GenerateCaptchaHandler(c *gin.Context) {
|
||||
if err := e.MakeContext(c).Errors; err != nil {
|
||||
e.Error(500, err, "服务初始化失败!")
|
||||
return
|
||||
}
|
||||
// The answer is deliberately discarded rather than logged. It used to be
|
||||
// written at info level, which put a currently valid captcha answer in the
|
||||
// application log - anyone able to read the log could bypass the check the
|
||||
// captcha exists to enforce.
|
||||
id, b64s, _, err := captcha.DriverDigitFunc()
|
||||
if err != nil {
|
||||
e.Logger.Errorf("DriverDigitFunc error, %s", err.Error())
|
||||
e.Error(500, err, "验证码获取失败")
|
||||
return
|
||||
}
|
||||
e.Custom(gin.H{
|
||||
"code": 200,
|
||||
"data": b64s,
|
||||
"id": id,
|
||||
"msg": "success",
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package system
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -11,13 +11,14 @@ const INDEX = `
|
||||
<meta charset="utf-8">
|
||||
<title>GO-ADMIN欢迎您</title>
|
||||
<style>
|
||||
body{
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow-y:hidden
|
||||
html,body{
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:100%;
|
||||
overflow-y:hidden;
|
||||
}
|
||||
</style>
|
||||
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
|
||||
<script src="https://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.onerror=function(){return true;}
|
||||
$(function(){
|
||||
@@ -28,12 +29,12 @@ $(function(){
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe" frameborder="0" src="http://doc.zhangwj.com/go-admin-site/" style="width:100%;"></iframe>
|
||||
<iframe id="iframe" frameborder="0" src="https://www.go-admin.pro" style="width:100%;height:100%;"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
func HelloWorld(c *gin.Context) {
|
||||
func GoAdmin(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(200, INDEX)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
"go-admin/common/actions"
|
||||
)
|
||||
|
||||
type SysApi struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage 获取接口管理列表
|
||||
// @Summary 获取接口管理列表
|
||||
// @Description 获取接口管理列表
|
||||
// @Tags 接口管理
|
||||
// @Param name query string false "名称"
|
||||
// @Param title query string false "标题"
|
||||
// @Param path query string false "地址"
|
||||
// @Param action query string false "类型"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-api [get]
|
||||
// @Security Bearer
|
||||
func (e SysApi) GetPage(c *gin.Context) {
|
||||
s := service.SysApi{}
|
||||
req := dto.SysApiGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
list := make([]models.SysApi, 0)
|
||||
var count int64
|
||||
err = s.GetPage(&req, p, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get 获取接口管理
|
||||
// @Summary 获取接口管理
|
||||
// @Description 获取接口管理
|
||||
// @Tags 接口管理
|
||||
// @Param id path string false "id"
|
||||
// @Success 200 {object} response.Response{data=models.SysApi} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-api/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysApi) Get(c *gin.Context) {
|
||||
req := dto.SysApiGetReq{}
|
||||
s := service.SysApi{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
var object models.SysApi
|
||||
err = s.Get(&req, p, &object).Error
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Update 修改接口管理
|
||||
// @Summary 修改接口管理
|
||||
// @Description 修改接口管理
|
||||
// @Tags 接口管理
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysApiUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
|
||||
// @Router /api/v1/sys-api/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysApi) Update(c *gin.Context) {
|
||||
req := dto.SysApiUpdateReq{}
|
||||
s := service.SysApi{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
err = s.Update(&req, p)
|
||||
if err != nil {
|
||||
e.Error(500, err, "更新失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// DeleteSysApi 删除接口管理
|
||||
// @Summary 删除接口管理
|
||||
// @Description 删除接口管理
|
||||
// @Tags 接口管理
|
||||
// @Param data body dto.SysApiDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
|
||||
// @Router /api/v1/sys-api [delete]
|
||||
// @Security Bearer
|
||||
func (e SysApi) DeleteSysApi(c *gin.Context) {
|
||||
req := dto.SysApiDeleteReq{}
|
||||
s := service.SysApi{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
err = s.Remove(&req, p)
|
||||
if err != nil {
|
||||
e.Error(500, err, "删除失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysConfig struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage 获取配置管理列表
|
||||
// @Summary 获取配置管理列表
|
||||
// @Description 获取配置管理列表
|
||||
// @Tags 配置管理
|
||||
// @Param configName query string false "名称"
|
||||
// @Param configKey query string false "key"
|
||||
// @Param configType query string false "类型"
|
||||
// @Param isFrontend query int false "是否前端"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-config [get]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) GetPage(c *gin.Context) {
|
||||
s := service.SysConfig{}
|
||||
req := dto.SysConfigGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]models.SysConfig, 0)
|
||||
var count int64
|
||||
err = s.GetPage(&req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get 获取配置管理
|
||||
// @Summary 获取配置管理
|
||||
// @Description 获取配置管理
|
||||
// @Tags 配置管理
|
||||
// @Param id path string false "id"
|
||||
// @Success 200 {object} response.Response{data=models.SysConfig} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-config/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) Get(c *gin.Context) {
|
||||
req := dto.SysConfigGetReq{}
|
||||
s := service.SysConfig{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysConfig
|
||||
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert 创建配置管理
|
||||
// @Summary 创建配置管理
|
||||
// @Description 创建配置管理
|
||||
// @Tags 配置管理
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysConfigControl true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "创建成功"}"
|
||||
// @Router /api/v1/sys-config [post]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) Insert(c *gin.Context) {
|
||||
s := service.SysConfig{}
|
||||
req := dto.SysConfigControl{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
|
||||
err = s.Insert(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "创建失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update 修改配置管理
|
||||
// @Summary 修改配置管理
|
||||
// @Description 修改配置管理
|
||||
// @Tags 配置管理
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysConfigControl true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
|
||||
// @Router /api/v1/sys-config/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) Update(c *gin.Context) {
|
||||
s := service.SysConfig{}
|
||||
req := dto.SysConfigControl{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Update(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "更新失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete 删除配置管理
|
||||
// @Summary 删除配置管理
|
||||
// @Description 删除配置管理
|
||||
// @Tags 配置管理
|
||||
// @Param ids body []int false "ids"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
|
||||
// @Router /api/v1/sys-config [delete]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) Delete(c *gin.Context) {
|
||||
s := service.SysConfig{}
|
||||
req := dto.SysConfigDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "删除失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
|
||||
// Get2SysApp 获取系统配置信息
|
||||
// @Summary 获取系统前台配置信息,主要注意这里不在验证权限
|
||||
// @Description 获取系统配置信息,主要注意这里不在验证权限
|
||||
// @Tags 配置管理
|
||||
// @Success 200 {object} response.Response{data=map[string]string} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/app-config [get]
|
||||
func (e SysConfig) Get2SysApp(c *gin.Context) {
|
||||
req := dto.SysConfigGetToSysAppReq{}
|
||||
s := service.SysConfig{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
// 控制只读前台的数据
|
||||
req.IsFrontend = "1"
|
||||
list := make([]models.SysConfig, 0)
|
||||
err = s.GetWithKeyList(&req, &list)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
mp := make(map[string]string)
|
||||
for i := 0; i < len(list); i++ {
|
||||
key := list[i].ConfigKey
|
||||
if key != "" {
|
||||
mp[key] = list[i].ConfigValue
|
||||
}
|
||||
}
|
||||
e.OK(mp, "查询成功")
|
||||
}
|
||||
|
||||
// Get2Set 获取配置
|
||||
// @Summary 获取配置
|
||||
// @Description 界面操作设置配置值的获取
|
||||
// @Tags 配置管理
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Success 200 {object} response.Response{data=map[string]interface{}} "{"code": 200, "message": "修改成功"}"
|
||||
// @Router /api/v1/set-config [get]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) Get2Set(c *gin.Context) {
|
||||
s := service.SysConfig{}
|
||||
req := make([]dto.GetSetSysConfigReq, 0)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
err = s.GetForSet(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
m := make(map[string]interface{}, 0)
|
||||
for _, v := range req {
|
||||
m[v.ConfigKey] = v.ConfigValue
|
||||
}
|
||||
e.OK(m, "查询成功")
|
||||
}
|
||||
|
||||
// Update2Set 设置配置
|
||||
// @Summary 设置配置
|
||||
// @Description 界面操作设置配置值
|
||||
// @Tags 配置管理
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body []dto.GetSetSysConfigReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
|
||||
// @Router /api/v1/set-config [put]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) Update2Set(c *gin.Context) {
|
||||
s := service.SysConfig{}
|
||||
req := make([]dto.GetSetSysConfigReq, 0)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = s.UpdateForSet(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
e.OK("", "更新成功")
|
||||
}
|
||||
|
||||
// GetSysConfigByKEYForService 根据Key获取SysConfig的Service
|
||||
// @Summary 根据Key获取SysConfig的Service
|
||||
// @Description 根据Key获取SysConfig的Service
|
||||
// @Tags 配置管理
|
||||
// @Param configKey path string false "configKey"
|
||||
// @Success 200 {object} response.Response{data=dto.SysConfigByKeyReq} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-config/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysConfig) GetSysConfigByKEYForService(c *gin.Context) {
|
||||
var s = new(service.SysConfig)
|
||||
var req = new(dto.SysConfigByKeyReq)
|
||||
var resp = new(dto.GetSysConfigByKEYForServiceResp)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = s.GetWithKey(req, resp)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
e.OK(resp, s.Msg)
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysDept struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage
|
||||
// @Summary 分页部门列表数据
|
||||
// @Description 分页列表
|
||||
// @Tags 部门
|
||||
// @Param deptName query string false "deptName"
|
||||
// @Param deptId query string false "deptId"
|
||||
// @Param position query string false "position"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dept [get]
|
||||
// @Security Bearer
|
||||
func (e SysDept) GetPage(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
req := dto.SysDeptGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SysDept, 0)
|
||||
list, err = s.SetDeptPage(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(list, "查询成功")
|
||||
}
|
||||
|
||||
// Get
|
||||
// @Summary 获取部门数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 部门
|
||||
// @Param deptId path string false "deptId"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dept/{deptId} [get]
|
||||
// @Security Bearer
|
||||
func (e SysDept) Get(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
req := dto.SysDeptGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysDept
|
||||
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert 添加部门
|
||||
// @Summary 添加部门
|
||||
// @Description 获取JSON
|
||||
// @Tags 部门
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysDeptInsertReq true "data"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dept [post]
|
||||
// @Security Bearer
|
||||
func (e SysDept) Insert(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
req := dto.SysDeptInsertReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 设置创建人
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
err = s.Insert(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "创建失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update
|
||||
// @Summary 修改部门
|
||||
// @Description 获取JSON
|
||||
// @Tags 部门
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param id path int true "id"
|
||||
// @Param data body dto.SysDeptUpdateReq true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
|
||||
// @Router /api/v1/dept/{deptId} [put]
|
||||
// @Security Bearer
|
||||
func (e SysDept) Update(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
req := dto.SysDeptUpdateReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Update(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Summary 删除部门
|
||||
// @Description 删除数据
|
||||
// @Tags 部门
|
||||
// @Param data body dto.SysDeptDeleteReq true "body"
|
||||
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
|
||||
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
|
||||
// @Router /api/v1/dept [delete]
|
||||
// @Security Bearer
|
||||
func (e SysDept) Delete(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
req := dto.SysDeptDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "删除失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
|
||||
// Get2Tree 用户管理 左侧部门树
|
||||
func (e SysDept) Get2Tree(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
req := dto.SysDeptGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]dto.DeptLabel, 0)
|
||||
list, err = s.SetDeptTree(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(list, "")
|
||||
}
|
||||
|
||||
// GetDeptTreeRoleSelect TODO: 此接口需要调整不应该将list和选中放在一起
|
||||
func (e SysDept) GetDeptTreeRoleSelect(c *gin.Context) {
|
||||
s := service.SysDept{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
id, err := pkg.StringToInt(c.Param("roleId"))
|
||||
result, err := s.SetDeptLabel()
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
menuIds := make([]int, 0)
|
||||
if id != 0 {
|
||||
menuIds, err = s.GetWithRoleId(id)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
e.OK(gin.H{
|
||||
"depts": result,
|
||||
"checkedKeys": menuIds,
|
||||
}, "")
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysDictData struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage
|
||||
// @Summary 字典数据列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Param status query string false "status"
|
||||
// @Param dictCode query string false "dictCode"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/data [get]
|
||||
// @Security Bearer
|
||||
func (e SysDictData) GetPage(c *gin.Context) {
|
||||
s := service.SysDictData{}
|
||||
req := dto.SysDictDataGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]models.SysDictData, 0)
|
||||
var count int64
|
||||
err = s.GetPage(&req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get
|
||||
// @Summary 通过编码获取字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Param dictCode path int true "字典编码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/data/{dictCode} [get]
|
||||
// @Security Bearer
|
||||
func (e SysDictData) Get(c *gin.Context) {
|
||||
s := service.SysDictData{}
|
||||
req := dto.SysDictDataGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var object models.SysDictData
|
||||
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Logger.Warnf("Get error: %s", err.Error())
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert
|
||||
// @Summary 添加字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysDictDataInsertReq true "data"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}"
|
||||
// @Router /api/v1/dict/data [post]
|
||||
// @Security Bearer
|
||||
func (e SysDictData) Insert(c *gin.Context) {
|
||||
s := service.SysDictData{}
|
||||
req := dto.SysDictDataInsertReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
err = s.Insert(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "创建失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update
|
||||
// @Summary 修改字典数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典数据
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysDictDataUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
|
||||
// @Router /api/v1/dict/data/{dictCode} [put]
|
||||
// @Security Bearer
|
||||
func (e SysDictData) Update(c *gin.Context) {
|
||||
s := service.SysDictData{}
|
||||
req := dto.SysDictDataUpdateReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Update(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "更新失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Summary 删除字典数据
|
||||
// @Description 删除数据
|
||||
// @Tags 字典数据
|
||||
// @Param dictCode body dto.SysDictDataDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
|
||||
// @Router /api/v1/dict/data [delete]
|
||||
// @Security Bearer
|
||||
func (e SysDictData) Delete(c *gin.Context) {
|
||||
s := service.SysDictData{}
|
||||
req := dto.SysDictDataDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "删除失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
|
||||
// GetAll 数据字典根据key获取 业务页面使用
|
||||
// @Summary 数据字典根据key获取
|
||||
// @Description 数据字典根据key获取
|
||||
// @Tags 字典数据
|
||||
// @Param dictType query int true "dictType"
|
||||
// @Success 200 {object} response.Response{data=[]dto.SysDictDataGetAllResp} "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict-data/option-select [get]
|
||||
// @Security Bearer
|
||||
func (e SysDictData) GetAll(c *gin.Context) {
|
||||
s := service.SysDictData{}
|
||||
req := dto.SysDictDataGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SysDictData, 0)
|
||||
err = s.GetAll(&req, &list)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
l := make([]dto.SysDictDataGetAllResp, 0)
|
||||
for _, i := range list {
|
||||
d := dto.SysDictDataGetAllResp{}
|
||||
e.Translate(i, &d)
|
||||
l = append(l, d)
|
||||
}
|
||||
|
||||
e.OK(l,"查询成功")
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysDictType struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage 字典类型列表数据
|
||||
// @Summary 字典类型列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Param dictName query string false "dictName"
|
||||
// @Param dictId query string false "dictId"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type [get]
|
||||
// @Security Bearer
|
||||
func (e SysDictType) GetPage(c *gin.Context) {
|
||||
s := service.SysDictType{}
|
||||
req :=dto.SysDictTypeGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SysDictType, 0)
|
||||
var count int64
|
||||
err = s.GetPage(&req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get 字典类型通过字典id获取
|
||||
// @Summary 字典类型通过字典id获取
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Param dictId path int true "字典类型编码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type/{dictId} [get]
|
||||
// @Security Bearer
|
||||
func (e SysDictType) Get(c *gin.Context) {
|
||||
s := service.SysDictType{}
|
||||
req :=dto.SysDictTypeGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysDictType
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
//Insert 字典类型创建
|
||||
// @Summary 添加字典类型
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysDictTypeInsertReq true "data"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type [post]
|
||||
// @Security Bearer
|
||||
func (e SysDictType) Insert(c *gin.Context) {
|
||||
s := service.SysDictType{}
|
||||
req :=dto.SysDictTypeInsertReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
err = s.Insert(&req)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err,fmt.Sprintf(" 创建字典类型失败,详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update
|
||||
// @Summary 修改字典类型
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysDictTypeUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type/{dictId} [put]
|
||||
// @Security Bearer
|
||||
func (e SysDictType) Update(c *gin.Context) {
|
||||
s := service.SysDictType{}
|
||||
req :=dto.SysDictTypeUpdateReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Update(&req)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Summary 删除字典类型
|
||||
// @Description 删除数据
|
||||
// @Tags 字典类型
|
||||
// @Param dictCode body dto.SysDictTypeDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type [delete]
|
||||
// @Security Bearer
|
||||
func (e SysDictType) Delete(c *gin.Context) {
|
||||
s := service.SysDictType{}
|
||||
req :=dto.SysDictTypeDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
|
||||
// GetAll
|
||||
// @Summary 字典类型全部数据 代码生成使用接口
|
||||
// @Description 获取JSON
|
||||
// @Tags 字典类型
|
||||
// @Param dictName query string false "dictName"
|
||||
// @Param dictId query string false "dictId"
|
||||
// @Param dictType query string false "dictType"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/dict/type-option-select [get]
|
||||
// @Security Bearer
|
||||
func (e SysDictType) GetAll(c *gin.Context) {
|
||||
s := service.SysDictType{}
|
||||
req :=dto.SysDictTypeGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SysDictType, 0)
|
||||
err = s.GetAll(&req, &list)
|
||||
if err != nil {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
e.OK(list, "查询成功")
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysLoginLog struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage 登录日志列表
|
||||
// @Summary 登录日志列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Param username query string false "用户名"
|
||||
// @Param ipaddr query string false "ip地址"
|
||||
// @Param loginLocation query string false "归属地"
|
||||
// @Param status query string false "状态"
|
||||
// @Param beginTime query string false "开始时间"
|
||||
// @Param endTime query string false "结束时间"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-login-log [get]
|
||||
// @Security Bearer
|
||||
func (e SysLoginLog) GetPage(c *gin.Context) {
|
||||
s := service.SysLoginLog{}
|
||||
req :=dto.SysLoginLogGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SysLoginLog, 0)
|
||||
var count int64
|
||||
err = s.GetPage(&req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get 登录日志通过id获取
|
||||
// @Summary 登录日志通过id获取
|
||||
// @Description 获取JSON
|
||||
// @Tags 登录日志
|
||||
// @Param id path string false "id"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-login-log/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysLoginLog) Get(c *gin.Context) {
|
||||
s := service.SysLoginLog{}
|
||||
req :=dto.SysLoginLogGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysLoginLog
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Delete 登录日志删除
|
||||
// @Summary 登录日志删除
|
||||
// @Description 登录日志删除
|
||||
// @Tags 登录日志
|
||||
// @Param data body dto.SysLoginLogDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-login-log [delete]
|
||||
// @Security Bearer
|
||||
func (e SysLoginLog) Delete(c *gin.Context) {
|
||||
s := service.SysLoginLog{}
|
||||
req :=dto.SysLoginLogDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, "删除失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysMenu struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage Menu列表数据
|
||||
// @Summary Menu列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Param menuName query string false "menuName"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menu [get]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) GetPage(c *gin.Context) {
|
||||
s := service.SysMenu{}
|
||||
req := dto.SysMenuGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var list = make([]models.SysMenu, 0)
|
||||
err = s.GetPage(&req, &list).Error
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(list, "查询成功")
|
||||
}
|
||||
|
||||
// Get 获取菜单详情
|
||||
// @Summary Menu详情数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Param id path string false "id"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menu/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) Get(c *gin.Context) {
|
||||
req := dto.SysMenuGetReq{}
|
||||
s := new(service.SysMenu)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object = models.SysMenu{}
|
||||
|
||||
err = s.Get(&req, &object).Error
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert 创建菜单
|
||||
// @Summary 创建菜单
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysMenuInsertReq true "data"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menu [post]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) Insert(c *gin.Context) {
|
||||
req := dto.SysMenuInsertReq{}
|
||||
s := new(service.SysMenu)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
// 设置创建人
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
err = s.Insert(&req).Error
|
||||
if err != nil {
|
||||
e.Error(500, err, "创建失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update 修改菜单
|
||||
// @Summary 修改菜单
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param id path int true "id"
|
||||
// @Param data body dto.SysMenuUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menu/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) Update(c *gin.Context) {
|
||||
req := dto.SysMenuUpdateReq{}
|
||||
s := new(service.SysMenu)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Update(&req).Error
|
||||
if err != nil {
|
||||
e.Error(500, err, "更新失败")
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete 删除菜单
|
||||
// @Summary 删除菜单
|
||||
// @Description 删除数据
|
||||
// @Tags 菜单
|
||||
// @Param data body dto.SysMenuDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menu [delete]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) Delete(c *gin.Context) {
|
||||
control := new(dto.SysMenuDeleteReq)
|
||||
s := new(service.SysMenu)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(control, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
err = s.Remove(control).Error
|
||||
if err != nil {
|
||||
e.Logger.Errorf("RemoveSysMenu error, %s", err)
|
||||
e.Error(500, err, "删除失败")
|
||||
return
|
||||
}
|
||||
e.OK(control.GetId(), "删除成功")
|
||||
}
|
||||
|
||||
// GetMenuRole 根据登录角色名称获取菜单列表数据(左菜单使用)
|
||||
// @Summary 根据登录角色名称获取菜单列表数据(左菜单使用)
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menurole [get]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) GetMenuRole(c *gin.Context) {
|
||||
s := new(service.SysMenu)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := s.SetMenuRole(user.GetRoleName(c))
|
||||
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(result, "")
|
||||
}
|
||||
|
||||
// GetMenuTreeSelect 根据角色ID查询菜单下拉树结构
|
||||
// @Summary 角色修改使用的菜单列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 菜单
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param roleId path int true "roleId"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/menuTreeselect/{roleId} [get]
|
||||
// @Security Bearer
|
||||
func (e SysMenu) GetMenuTreeSelect(c *gin.Context) {
|
||||
m := service.SysMenu{}
|
||||
r := service.SysRole{}
|
||||
req := dto.SelectRole{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&m.Service).
|
||||
MakeService(&r.Service).
|
||||
Bind(&req, nil).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := m.SetLabel()
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
menuIds := make([]int, 0)
|
||||
if req.RoleId != 0 {
|
||||
menuIds, err = r.GetRoleMenuId(req.RoleId)
|
||||
if err != nil {
|
||||
e.Error(500, err, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
e.OK(gin.H{
|
||||
"menus": result,
|
||||
"checkedKeys": menuIds,
|
||||
}, "获取成功")
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysOperaLog struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage 操作日志列表
|
||||
// @Summary 操作日志列表
|
||||
// @Description 获取JSON
|
||||
// @Tags 操作日志
|
||||
// @Param title query string false "title"
|
||||
// @Param method query string false "method"
|
||||
// @Param requestMethod query string false "requestMethod"
|
||||
// @Param operUrl query string false "operUrl"
|
||||
// @Param operIp query string false "operIp"
|
||||
// @Param status query string false "status"
|
||||
// @Param beginTime query string false "beginTime"
|
||||
// @Param endTime query string false "endTime"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-opera-log [get]
|
||||
// @Security Bearer
|
||||
func (e SysOperaLog) GetPage(c *gin.Context) {
|
||||
s := service.SysOperaLog{}
|
||||
req := new(dto.SysOperaLogGetPageReq)
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]models.SysOperaLog, 0)
|
||||
var count int64
|
||||
|
||||
err = s.GetPage(req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get 操作日志通过id获取
|
||||
// @Summary 操作日志通过id获取
|
||||
// @Description 获取JSON
|
||||
// @Tags 操作日志
|
||||
// @Param id path string false "id"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-opera-log/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysOperaLog) Get(c *gin.Context) {
|
||||
s := new(service.SysOperaLog)
|
||||
req :=dto.SysOperaLogGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysOperaLog
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Delete 操作日志删除
|
||||
// DeleteSysMenu 操作日志删除
|
||||
// @Summary 删除操作日志
|
||||
// @Description 删除数据
|
||||
// @Tags 操作日志
|
||||
// @Param data body dto.SysOperaLogDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-opera-log [delete]
|
||||
// @Security Bearer
|
||||
func (e SysOperaLog) Delete(c *gin.Context) {
|
||||
s := new(service.SysOperaLog)
|
||||
req :=dto.SysOperaLogDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500,err, fmt.Sprintf("删除失败!错误详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysPost struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage
|
||||
// @Summary 岗位列表数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Param postName query string false "postName"
|
||||
// @Param postCode query string false "postCode"
|
||||
// @Param postId query string false "postId"
|
||||
// @Param status query string false "status"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post [get]
|
||||
// @Security Bearer
|
||||
func (e SysPost) GetPage(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req :=dto.SysPostPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]models.SysPost, 0)
|
||||
var count int64
|
||||
|
||||
err = s.GetPage(&req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get
|
||||
// @Summary 获取岗位信息
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Param id path int true "编码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post/{postId} [get]
|
||||
// @Security Bearer
|
||||
func (e SysPost) Get(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req :=dto.SysPostGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysPost
|
||||
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("岗位信息获取失败!错误详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert
|
||||
// @Summary 添加岗位
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysPostInsertReq true "data"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post [post]
|
||||
// @Security Bearer
|
||||
func (e SysPost) Insert(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req :=dto.SysPostInsertReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
err = s.Insert(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("新建岗位失败!错误详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update
|
||||
// @Summary 修改岗位
|
||||
// @Description 获取JSON
|
||||
// @Tags 岗位
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysPostUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysPost) Update(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req :=dto.SysPostUpdateReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
err = s.Update(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("岗位更新失败!错误详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Summary 删除岗位
|
||||
// @Description 删除数据
|
||||
// @Tags 岗位
|
||||
// @Param id body dto.SysPostDeleteReq true "请求参数"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/post [delete]
|
||||
// @Security Bearer
|
||||
func (e SysPost) Delete(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req :=dto.SysPostDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.Remove(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("岗位删除失败!错误详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-admin/common/global"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
)
|
||||
|
||||
type SysRole struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage
|
||||
// @Summary 角色列表数据
|
||||
// @Description Get JSON
|
||||
// @Tags 角色/Role
|
||||
// @Param roleName query string false "roleName"
|
||||
// @Param status query string false "status"
|
||||
// @Param roleKey query string false "roleKey"
|
||||
// @Param pageSize query int false "页条数"
|
||||
// @Param pageIndex query int false "页码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role [get]
|
||||
// @Security Bearer
|
||||
func (e SysRole) GetPage(c *gin.Context) {
|
||||
s := service.SysRole{}
|
||||
req := dto.SysRoleGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.Form).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]models.SysRole, 0)
|
||||
var count int64
|
||||
|
||||
err = s.GetPage(&req, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get
|
||||
// @Summary 获取Role数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Param roleId path string false "roleId"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role/{id} [get]
|
||||
// @Security Bearer
|
||||
func (e SysRole) Get(c *gin.Context) {
|
||||
s := service.SysRole{}
|
||||
req := dto.SysRoleGetReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, fmt.Sprintf(" %s ", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var object models.SysRole
|
||||
|
||||
err = s.Get(&req, &object)
|
||||
if err != nil {
|
||||
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert
|
||||
// @Summary 创建角色
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysRoleInsertReq true "data"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role [post]
|
||||
// @Security Bearer
|
||||
func (e SysRole) Insert(c *gin.Context) {
|
||||
s := service.SysRole{}
|
||||
req := dto.SysRoleInsertReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 设置创建人
|
||||
req.CreateBy = user.GetUserId(c)
|
||||
if req.Status == "" {
|
||||
req.Status = "2"
|
||||
}
|
||||
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
err = s.Insert(&req, cb)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, "创建失败,"+err.Error())
|
||||
return
|
||||
}
|
||||
_, err = global.LoadPolicy(c)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, "创建失败,"+err.Error())
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update 修改用户角色
|
||||
// @Summary 修改用户角色
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysRoleUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysRole) Update(c *gin.Context) {
|
||||
s := service.SysRole{}
|
||||
req := dto.SysRoleUpdateReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
err = s.Update(&req, cb)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = global.LoadPolicy(c)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, "更新失败,"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Summary 删除用户角色
|
||||
// @Description 删除数据
|
||||
// @Tags 角色/Role
|
||||
// @Param data body dto.SysRoleDeleteReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role [delete]
|
||||
// @Security Bearer
|
||||
func (e SysRole) Delete(c *gin.Context) {
|
||||
s := new(service.SysRole)
|
||||
req := dto.SysRoleDeleteReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, fmt.Sprintf("删除角色 %v 失败,\r\n失败信息 %s", req.Ids, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
err = s.Remove(&req, cb)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, "")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(req.GetId(), fmt.Sprintf("删除角色角色 %v 状态成功!", req.GetId()))
|
||||
}
|
||||
|
||||
// Update2Status 修改用户角色状态
|
||||
// @Summary 修改用户角色
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.UpdateStatusReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role-status/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysRole) Update2Status(c *gin.Context) {
|
||||
s := service.SysRole{}
|
||||
req := dto.UpdateStatusReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error()))
|
||||
return
|
||||
}
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
err = s.UpdateStatus(&req)
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), fmt.Sprintf("更新角色 %v 状态成功!", req.GetId()))
|
||||
}
|
||||
|
||||
// Update2DataScope 更新角色数据权限
|
||||
// @Summary 更新角色数据权限
|
||||
// @Description 获取JSON
|
||||
// @Tags 角色/Role
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.RoleDataScopeReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/role-status/{id} [put]
|
||||
// @Security Bearer
|
||||
func (e SysRole) Update2DataScope(c *gin.Context) {
|
||||
s := service.SysRole{}
|
||||
req := dto.RoleDataScopeReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
data := &models.SysRole{
|
||||
RoleId: req.RoleId,
|
||||
DataScope: req.DataScope,
|
||||
DeptIds: req.DeptIds,
|
||||
}
|
||||
data.UpdateBy = user.GetUserId(c)
|
||||
err = s.UpdateDataScope(&req).Error
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("更新角色数据权限失败!错误详情:%s", err.Error()))
|
||||
return
|
||||
}
|
||||
e.OK(nil, "操作成功")
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"go-admin/app/admin/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
"go-admin/common/actions"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
type SysUser struct {
|
||||
api.Api
|
||||
}
|
||||
|
||||
// GetPage
|
||||
// @Summary 列表用户信息数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Param username query string false "username"
|
||||
// @Success 200 {string} {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-user [get]
|
||||
// @Security Bearer
|
||||
func (e SysUser) GetPage(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.SysUserGetPageReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
list := make([]models.SysUser, 0)
|
||||
var count int64
|
||||
|
||||
err = s.GetPage(&req, p, &list, &count)
|
||||
if err != nil {
|
||||
e.Error(500, err, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
|
||||
// Get
|
||||
// @Summary 获取用户
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Param userId path int true "用户编码"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-user/{userId} [get]
|
||||
// @Security Bearer
|
||||
func (e SysUser) Get(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.SysUserById{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
var object models.SysUser
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
err = s.Get(&req, p, &object)
|
||||
if err != nil {
|
||||
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
|
||||
return
|
||||
}
|
||||
e.OK(object, "查询成功")
|
||||
}
|
||||
|
||||
// Insert
|
||||
// @Summary 创建用户
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysUserInsertReq true "用户数据"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-user [post]
|
||||
// @Security Bearer
|
||||
func (e SysUser) Insert(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.SysUserInsertReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
// 设置创建人
|
||||
req.SetCreateBy(user.GetUserId(c))
|
||||
err = s.Insert(&req)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
// Update
|
||||
// @Summary 修改用户数据
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.SysUserUpdateReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-user/{userId} [put]
|
||||
// @Security Bearer
|
||||
func (e SysUser) Update(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.SysUserUpdateReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
callerId := user.GetUserId(c)
|
||||
|
||||
// This route is in CasbinExclude so the personal-center screen can edit
|
||||
// the caller's own record without a policy grant (see settings.go). That
|
||||
// exclusion covers the whole route, not just the caller's own record, and
|
||||
// the request carries the target userId in the body - so without this
|
||||
// check here, any authenticated caller could edit any other user, up to
|
||||
// and including their roleId. When the target is someone else, ask Casbin
|
||||
// directly for the permission AuthCheckRole skipped.
|
||||
if req.UserId != callerId {
|
||||
allowed, err := middleware.EnforceRoleFor(c, c.Request.URL.Path, c.Request.Method)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
if !allowed {
|
||||
e.Error(http.StatusForbidden, errors.New("无权更新其他用户数据"), "对不起,您没有该接口访问权限,请联系管理员")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
req.SetUpdateBy(callerId)
|
||||
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
err = s.Update(&req, p, callerId)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @Summary 删除用户数据
|
||||
// @Description 删除数据
|
||||
// @Tags 用户
|
||||
// @Param userId path int true "userId"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/sys-user/{userId} [delete]
|
||||
// @Security Bearer
|
||||
func (e SysUser) Delete(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.SysUserById{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 设置编辑人
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
// 数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
err = s.Remove(&req, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "删除成功")
|
||||
}
|
||||
|
||||
// InsetAvatar
|
||||
// @Summary 修改头像
|
||||
// @Description 获取JSON
|
||||
// @Tags 个人中心
|
||||
// @Accept multipart/form-data
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/user/avatar [post]
|
||||
// @Security Bearer
|
||||
func (e SysUser) InsetAvatar(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.UpdateSysUserAvatarReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
// 数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
form, _ := c.MultipartForm()
|
||||
files := form.File["upload[]"]
|
||||
guid := uuid.New().String()
|
||||
filPath := "static/uploadfile/" + guid + ".jpg"
|
||||
for _, file := range files {
|
||||
e.Logger.Debugf("upload avatar file: %s", file.Filename)
|
||||
// 上传文件至指定目录
|
||||
err = c.SaveUploadedFile(file, filPath)
|
||||
if err != nil {
|
||||
e.Logger.Errorf("save file error, %s", err.Error())
|
||||
e.Error(500, err, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
req.UserId = p.UserId
|
||||
req.Avatar = "/" + filPath
|
||||
|
||||
err = s.UpdateAvatar(&req, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
e.OK(filPath, "修改成功")
|
||||
}
|
||||
|
||||
// UpdateStatus 修改用户状态
|
||||
// @Summary 修改用户状态
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.UpdateSysUserStatusReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/user/status [put]
|
||||
// @Security Bearer
|
||||
func (e SysUser) UpdateStatus(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.UpdateSysUserStatusReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON, nil).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
err = s.UpdateStatus(&req, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// ResetPwd 重置用户密码
|
||||
// @Summary 重置用户密码
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.ResetSysUserPwdReq true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/user/pwd/reset [put]
|
||||
// @Security Bearer
|
||||
func (e SysUser) ResetPwd(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.ResetSysUserPwdReq{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req, binding.JSON).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
err = s.ResetPwd(&req, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// UpdatePwd
|
||||
// @Summary 修改密码
|
||||
// @Description 获取JSON
|
||||
// @Tags 用户
|
||||
// @Accept application/json
|
||||
// @Product application/json
|
||||
// @Param data body dto.PassWord true "body"
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/user/pwd/set [put]
|
||||
// @Security Bearer
|
||||
func (e SysUser) UpdatePwd(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.PassWord{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
Bind(&req).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
var hash []byte
|
||||
if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil {
|
||||
req.NewPassword = string(hash)
|
||||
}
|
||||
|
||||
err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(http.StatusForbidden, err, "密码修改失败")
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(nil, "密码修改成功")
|
||||
}
|
||||
|
||||
// GetProfile
|
||||
// @Summary 获取个人中心用户
|
||||
// @Description 获取JSON
|
||||
// @Tags 个人中心
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/user/profile [get]
|
||||
// @Security Bearer
|
||||
func (e SysUser) GetProfile(c *gin.Context) {
|
||||
s := service.SysUser{}
|
||||
req := dto.SysUserById{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.Id = user.GetUserId(c)
|
||||
|
||||
sysUser := models.SysUser{}
|
||||
roles := make([]models.SysRole, 0)
|
||||
posts := make([]models.SysPost, 0)
|
||||
err = s.GetProfile(&req, &sysUser, &roles, &posts)
|
||||
if err != nil {
|
||||
e.Logger.Errorf("get user profile error, %s", err.Error())
|
||||
e.Error(500, err, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
e.OK(gin.H{
|
||||
"user": sysUser,
|
||||
"roles": roles,
|
||||
"posts": posts,
|
||||
}, "查询成功")
|
||||
}
|
||||
|
||||
// GetInfo
|
||||
// @Summary 获取个人信息
|
||||
// @Description 获取JSON
|
||||
// @Tags 个人中心
|
||||
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
||||
// @Router /api/v1/getinfo [get]
|
||||
// @Security Bearer
|
||||
func (e SysUser) GetInfo(c *gin.Context) {
|
||||
req := dto.SysUserById{}
|
||||
s := service.SysUser{}
|
||||
r := service.SysRole{}
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&r.Service).
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
var roles = make([]string, 1)
|
||||
roles[0] = user.GetRoleName(c)
|
||||
var permissions = make([]string, 1)
|
||||
permissions[0] = "*:*:*"
|
||||
var buttons = make([]string, 1)
|
||||
buttons[0] = "*:*:*"
|
||||
|
||||
var mp = make(map[string]interface{})
|
||||
mp["roles"] = roles
|
||||
if user.GetRoleName(c) == "admin" || user.GetRoleName(c) == "系统管理员" {
|
||||
mp["permissions"] = permissions
|
||||
mp["buttons"] = buttons
|
||||
} else {
|
||||
list, _ := r.GetById(user.GetRoleId(c))
|
||||
mp["permissions"] = list
|
||||
mp["buttons"] = list
|
||||
}
|
||||
sysUser := models.SysUser{}
|
||||
req.Id = user.GetUserId(c)
|
||||
err = s.Get(&req, p, &sysUser)
|
||||
if err != nil {
|
||||
e.Error(http.StatusUnauthorized, err, "登录失败")
|
||||
return
|
||||
}
|
||||
mp["introduction"] = " am a super administrator"
|
||||
mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
|
||||
if sysUser.Avatar != "" {
|
||||
mp["avatar"] = sysUser.Avatar
|
||||
}
|
||||
mp["userName"] = sysUser.Username
|
||||
mp["userId"] = sysUser.UserId
|
||||
mp["deptId"] = sysUser.DeptId
|
||||
mp["name"] = sysUser.NickName
|
||||
mp["code"] = 200
|
||||
e.OK(mp, "")
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
mycasbin "github.com/go-admin-team/go-admin-core/v2/casbin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
)
|
||||
|
||||
// PUT /api/v1/sys-user is in settings.go's CasbinExclude so the
|
||||
// personal-center screen (go-admin-ui's userInfo.vue) can edit the caller's
|
||||
// own record without holding a policy grant on this route. AuthCheckRole
|
||||
// skips Enforce entirely for an excluded route, so this file's job is to pin
|
||||
// what the handler itself now has to hold shut: the target userId comes from
|
||||
// the request body, and nothing upstream of the handler ever checked it
|
||||
// against the caller.
|
||||
|
||||
// setupPrivescDB wires an in-memory database and a Casbin enforcer with an
|
||||
// empty policy - the state of a fresh install for any role but admin - under
|
||||
// a tenant unique to the calling test, so mycasbin's process-wide enforcer
|
||||
// cache can't hand one test's database to another.
|
||||
func setupPrivescDB(t *testing.T) (*gorm.DB, string) {
|
||||
t.Helper()
|
||||
|
||||
// Fatalf, not Skipf: this database is in-memory sqlite with no external
|
||||
// dependency, so failing to open or migrate it means the environment is
|
||||
// actually broken. Skipping here would let these two anti-privesc
|
||||
// regression tests silently stop running while CI stays green - a
|
||||
// standing assertion that never fires is worse than no assertion.
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("sqlite unavailable: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.SysUser{}); err != nil {
|
||||
t.Fatalf("automigrate: %v", err)
|
||||
}
|
||||
|
||||
tenant := "sys-user-privesc-" + t.Name()
|
||||
|
||||
previousInterval := mycasbin.ReloadInterval
|
||||
mycasbin.ReloadInterval = 0 // opt out of the background reload goroutine; the test never writes a policy
|
||||
t.Cleanup(func() { mycasbin.ReloadInterval = previousInterval })
|
||||
|
||||
e := mycasbin.Setup(db, tenant)
|
||||
previousEnforcer := sdk.Runtime.GetCasbinByTenant(tenant)
|
||||
sdk.Runtime.SetCasbinByTenant(tenant, e)
|
||||
t.Cleanup(func() { sdk.Runtime.SetCasbinByTenant(tenant, previousEnforcer) })
|
||||
|
||||
return db, tenant
|
||||
}
|
||||
|
||||
// callUpdate drives SysUser.Update the way the router does for an
|
||||
// authenticated, non-admin caller: JWT claims already decoded into the
|
||||
// context (that is jwtauth's job, not this handler's) and a database - but
|
||||
// without AuthCheckRole, since that middleware never runs Enforce for this
|
||||
// route at all.
|
||||
func callUpdate(t *testing.T, db *gorm.DB, tenant string, callerId int, body map[string]interface{}) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
raw, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal request body: %v", err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest(http.MethodPut, "/api/v1/sys-user", bytes.NewReader(raw))
|
||||
c.Request.Host = tenant
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
c.Set("db", db)
|
||||
c.Set(pkg.LoggerKey, logger.NewHelper(logger.DefaultLogger))
|
||||
c.Set(jwt.JwtPayloadKey, jwt.MapClaims{
|
||||
"identity": float64(callerId),
|
||||
"rolekey": "ordinary-role", // holds no Casbin policy anywhere in this test
|
||||
})
|
||||
|
||||
SysUser{}.Update(c)
|
||||
return w
|
||||
}
|
||||
|
||||
// TestUpdate_CannotEscalatePrivilegeThroughAnotherUsersRecord is the
|
||||
// regression for H6. Before the fix, an ordinary authenticated user could PUT
|
||||
// a body naming another user's id and change that user's roleId - the route
|
||||
// being Casbin-excluded meant no permission check ever ran, and the data
|
||||
// permission scope that would otherwise gate this is off by default.
|
||||
func TestUpdate_CannotEscalatePrivilegeThroughAnotherUsersRecord(t *testing.T) {
|
||||
db, tenant := setupPrivescDB(t)
|
||||
|
||||
victim := models.SysUser{Username: "bob", NickName: "Bob", RoleId: 2, DeptId: 1, Status: "1"}
|
||||
if err := db.Create(&victim).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
attacker := models.SysUser{Username: "alice", NickName: "Alice", RoleId: 2, DeptId: 1, Status: "1"}
|
||||
if err := db.Create(&attacker).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const elevatedRoleId = 1 // a role the attacker does not hold and has no policy for
|
||||
|
||||
callUpdate(t, db, tenant, attacker.UserId, map[string]interface{}{
|
||||
"userId": victim.UserId,
|
||||
"username": victim.Username,
|
||||
"nickName": "pwned",
|
||||
"phone": "13800000000",
|
||||
"email": "bob@example.com",
|
||||
"roleId": elevatedRoleId,
|
||||
"deptId": victim.DeptId,
|
||||
"status": victim.Status,
|
||||
})
|
||||
|
||||
var after models.SysUser
|
||||
if err := db.First(&after, victim.UserId).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if after.RoleId == elevatedRoleId {
|
||||
t.Fatalf("an attacker with no Casbin permission on this route escalated the victim's roleId to %d", after.RoleId)
|
||||
}
|
||||
if after.NickName == "pwned" {
|
||||
t.Fatalf("an attacker with no Casbin permission on this route modified another user's record: %+v", after)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdate_SelfEditCannotChangePrivilegedFields covers the case the
|
||||
// CasbinExclude entry exists for: the personal-center screen has to keep
|
||||
// working for the caller's own record. The fields that screen exposes
|
||||
// (nickName/phone/email/sex) must still save, while roleId/deptId/status stay
|
||||
// whatever the database already had even if the request carries something
|
||||
// else - a compromised or hand-crafted client is the only way that request
|
||||
// would ever differ from what the honest form sends.
|
||||
func TestUpdate_SelfEditCannotChangePrivilegedFields(t *testing.T) {
|
||||
db, tenant := setupPrivescDB(t)
|
||||
|
||||
self := models.SysUser{Username: "carol", NickName: "Carol", RoleId: 2, DeptId: 1, Status: "1"}
|
||||
if err := db.Create(&self).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const elevatedRoleId = 1
|
||||
|
||||
callUpdate(t, db, tenant, self.UserId, map[string]interface{}{
|
||||
"userId": self.UserId,
|
||||
"username": self.Username,
|
||||
"nickName": "Carol Updated",
|
||||
"phone": "13900000000",
|
||||
"email": "carol@example.com",
|
||||
"roleId": elevatedRoleId, // tampered; must not take effect
|
||||
"deptId": self.DeptId,
|
||||
"status": self.Status,
|
||||
})
|
||||
|
||||
var after models.SysUser
|
||||
if err := db.First(&after, self.UserId).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if after.RoleId == elevatedRoleId {
|
||||
t.Fatalf("a self-edit changed the caller's own roleId to %d", after.RoleId)
|
||||
}
|
||||
if after.NickName != "Carol Updated" {
|
||||
t.Fatalf("the legitimate personal-center edit did not go through: %+v", after)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
type CasbinRule struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V0 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V1 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V2 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V3 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V4 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V5 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
}
|
||||
|
||||
func (CasbinRule) TableName() string {
|
||||
return "sys_casbin_rule"
|
||||
}
|
||||
@@ -2,22 +2,24 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-admin/global"
|
||||
"go-admin/common/global"
|
||||
"gorm.io/gorm"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func InitDb() error {
|
||||
func InitDb(db *gorm.DB) (err error) {
|
||||
filePath := "config/db.sql"
|
||||
err := ExecSql(filePath)
|
||||
err = ExecSql(db, filePath)
|
||||
if global.Driver == "postgres" {
|
||||
filePath = "config/pg.sql"
|
||||
err = ExecSql(filePath)
|
||||
err = ExecSql(db, filePath)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ExecSql(filePath string) error {
|
||||
func ExecSql(db *gorm.DB, filePath string) error {
|
||||
sql, err := Ioutil(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("数据库基础数据初始化脚本读取失败!原因:", err.Error())
|
||||
@@ -29,9 +31,10 @@ func ExecSql(filePath string) error {
|
||||
fmt.Println(sqlList[i])
|
||||
continue
|
||||
}
|
||||
sql := strings.Replace(sqlList[i]+";", "\n", "", 0)
|
||||
sql := strings.Replace(sqlList[i]+";", "\n", "", -1)
|
||||
sql = strings.TrimSpace(sql)
|
||||
if err = global.Eloquent.Exec(sql).Error; err != nil {
|
||||
if err = db.Exec(sql).Error; err != nil {
|
||||
log.Printf("error sql: %s", sql)
|
||||
if !strings.Contains(err.Error(), "Query was empty") {
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/runtime"
|
||||
"github.com/go-admin-team/go-admin-core/v2/storage"
|
||||
|
||||
"go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysApi struct {
|
||||
Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
|
||||
Handle string `json:"handle" gorm:"size:128;comment:handle"`
|
||||
Title string `json:"title" gorm:"size:128;comment:标题"`
|
||||
Path string `json:"path" gorm:"size:128;comment:地址"`
|
||||
Action string `json:"action" gorm:"size:16;comment:请求类型"`
|
||||
Type string `json:"type" gorm:"size:16;comment:接口类型"`
|
||||
// AppCode identifies which application's seed.SeedMenus call wrote this
|
||||
// row; empty for the host's own built-in APIs. Same NOT NULL DEFAULT ''
|
||||
// reasoning as SysMenu.AppCode.
|
||||
AppCode string `json:"appCode" gorm:"type:varchar(64);not null;default:'';index:idx_sys_api_app_code;comment:AppCode"`
|
||||
models.ModelTime
|
||||
models.ControlBy
|
||||
}
|
||||
|
||||
func (*SysApi) TableName() string {
|
||||
return "sys_api"
|
||||
}
|
||||
|
||||
func (e *SysApi) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysApi) GetId() interface{} {
|
||||
return e.Id
|
||||
}
|
||||
|
||||
func SaveSysApi(message storage.Messager) (err error) {
|
||||
var rb []byte
|
||||
rb, err = json.Marshal(message.GetValues())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("json Marshal error, %v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
var l runtime.Routers
|
||||
err = json.Unmarshal(rb, &l)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("json Unmarshal error, %s", err.Error())
|
||||
return err
|
||||
}
|
||||
dbList := sdk.Runtime.GetAllDb()
|
||||
for _, d := range dbList {
|
||||
for _, v := range l.List {
|
||||
if v.HttpMethod != "HEAD" ||
|
||||
strings.Contains(v.RelativePath, "/swagger/") ||
|
||||
strings.Contains(v.RelativePath, "/static/") ||
|
||||
strings.Contains(v.RelativePath, "/form-generator/") ||
|
||||
strings.Contains(v.RelativePath, "/sys/tables") {
|
||||
|
||||
// 根据接口方法注释里的@Summary填充接口名称,适用于代码生成器
|
||||
// 可在此处增加配置路径前缀的if判断,只对代码生成的自建应用进行定向的接口名称填充
|
||||
jsonFile, _ := ioutil.ReadFile("docs/swagger.json")
|
||||
jsonData, _ := simplejson.NewFromReader(bytes.NewReader(jsonFile))
|
||||
urlPath := v.RelativePath
|
||||
idPatten := "(.*)/:(\\w+)" // 正则替换,把:id换成{id}
|
||||
reg, _ := regexp.Compile(idPatten)
|
||||
if reg.MatchString(urlPath) {
|
||||
urlPath = reg.ReplaceAllString(v.RelativePath, "${1}/{${2}}") // 把:id换成{id}
|
||||
}
|
||||
apiTitle, _ := jsonData.Get("paths").Get(urlPath).Get(strings.ToLower(v.HttpMethod)).Get("summary").String()
|
||||
|
||||
err := d.Debug().Where(SysApi{Path: v.RelativePath, Action: v.HttpMethod}).
|
||||
Attrs(SysApi{Handle: v.Handler, Title: apiTitle}).
|
||||
FirstOrCreate(&SysApi{}).
|
||||
//Update("handle", v.Handler).
|
||||
Error
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Models SaveSysApi error: %s \r\n ", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysConfig struct {
|
||||
models.Model
|
||||
ConfigName string `json:"configName" gorm:"size:128;comment:ConfigName"` //
|
||||
ConfigKey string `json:"configKey" gorm:"size:128;comment:ConfigKey"` //
|
||||
ConfigValue string `json:"configValue" gorm:"size:255;comment:ConfigValue"` //
|
||||
ConfigType string `json:"configType" gorm:"size:64;comment:ConfigType"`
|
||||
IsFrontend string `json:"isFrontend" gorm:"size:64;comment:是否前台"` //
|
||||
Remark string `json:"remark" gorm:"size:128;comment:Remark"` //
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (*SysConfig) TableName() string {
|
||||
return "sys_config"
|
||||
}
|
||||
|
||||
func (e *SysConfig) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysConfig) GetId() interface{} {
|
||||
return e.Id
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package models
|
||||
|
||||
import "go-admin/common/models"
|
||||
|
||||
type SysDept struct {
|
||||
DeptId int `json:"deptId" gorm:"primaryKey;autoIncrement;"` //部门编码
|
||||
ParentId int `json:"parentId" gorm:""` //上级部门
|
||||
DeptPath string `json:"deptPath" gorm:"size:255;"` //
|
||||
DeptName string `json:"deptName" gorm:"size:128;"` //部门名称
|
||||
Sort int `json:"sort" gorm:"size:4;"` //排序
|
||||
Leader string `json:"leader" gorm:"size:128;"` //负责人
|
||||
Phone string `json:"phone" gorm:"size:11;"` //手机
|
||||
Email string `json:"email" gorm:"size:64;"` //邮箱
|
||||
Status int `json:"status" gorm:"size:4;"` //状态
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
DataScope string `json:"dataScope" gorm:"-"`
|
||||
Params string `json:"params" gorm:"-"`
|
||||
Children []SysDept `json:"children" gorm:"-"`
|
||||
}
|
||||
|
||||
func (*SysDept) TableName() string {
|
||||
return "sys_dept"
|
||||
}
|
||||
|
||||
func (e *SysDept) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysDept) GetId() interface{} {
|
||||
return e.DeptId
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysDictData struct {
|
||||
DictCode int `json:"dictCode" gorm:"primaryKey;column:dict_code;autoIncrement;comment:主键编码"`
|
||||
DictSort int `json:"dictSort" gorm:"size:20;comment:DictSort"`
|
||||
DictLabel string `json:"dictLabel" gorm:"size:128;comment:DictLabel"`
|
||||
DictValue string `json:"dictValue" gorm:"size:255;comment:DictValue"`
|
||||
DictType string `json:"dictType" gorm:"size:64;comment:DictType"`
|
||||
CssClass string `json:"cssClass" gorm:"size:128;comment:CssClass"`
|
||||
ListClass string `json:"listClass" gorm:"size:128;comment:ListClass"`
|
||||
IsDefault string `json:"isDefault" gorm:"size:8;comment:IsDefault"`
|
||||
Status int `json:"status" gorm:"size:4;comment:Status"`
|
||||
Default string `json:"default" gorm:"size:8;comment:Default"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:Remark"`
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (*SysDictData) TableName() string {
|
||||
return "sys_dict_data"
|
||||
}
|
||||
|
||||
func (e *SysDictData) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysDictData) GetId() interface{} {
|
||||
return e.DictCode
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysDictType struct {
|
||||
ID int `json:"id" gorm:"primaryKey;column:dict_id;autoIncrement;comment:主键编码"`
|
||||
DictName string `json:"dictName" gorm:"size:128;comment:DictName"`
|
||||
DictType string `json:"dictType" gorm:"size:128;comment:DictType"`
|
||||
Status int `json:"status" gorm:"size:4;comment:Status"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:Remark"`
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (*SysDictType) TableName() string {
|
||||
return "sys_dict_type"
|
||||
}
|
||||
|
||||
func (e *SysDictType) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysDictType) GetId() interface{} {
|
||||
return e.ID
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/storage"
|
||||
|
||||
"go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysLoginLog struct {
|
||||
models.Model
|
||||
Username string `json:"username" gorm:"size:128;comment:用户名"`
|
||||
Status string `json:"status" gorm:"size:4;comment:状态"`
|
||||
Ipaddr string `json:"ipaddr" gorm:"size:255;comment:ip地址"`
|
||||
LoginLocation string `json:"loginLocation" gorm:"size:255;comment:归属地"`
|
||||
Browser string `json:"browser" gorm:"size:255;comment:浏览器"`
|
||||
Os string `json:"os" gorm:"size:255;comment:系统"`
|
||||
Platform string `json:"platform" gorm:"size:255;comment:固件"`
|
||||
LoginTime time.Time `json:"loginTime" gorm:"comment:登录时间"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:备注"`
|
||||
Msg string `json:"msg" gorm:"size:255;comment:信息"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
|
||||
models.ControlBy
|
||||
}
|
||||
|
||||
func (*SysLoginLog) TableName() string {
|
||||
return "sys_login_log"
|
||||
}
|
||||
|
||||
func (e *SysLoginLog) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysLoginLog) GetId() interface{} {
|
||||
return e.Id
|
||||
}
|
||||
|
||||
// SaveLoginLog 从队列中获取登录日志
|
||||
func SaveLoginLog(message storage.Messager) (err error) {
|
||||
//准备db
|
||||
db := sdk.Runtime.GetDbByTenant(message.GetPrefix())
|
||||
if db == nil {
|
||||
err = errors.New("db not exist")
|
||||
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
|
||||
return err
|
||||
}
|
||||
var rb []byte
|
||||
rb, err = json.Marshal(message.GetValues())
|
||||
if err != nil {
|
||||
log.Errorf("json Marshal error, %s", err.Error())
|
||||
return err
|
||||
}
|
||||
var l SysLoginLog
|
||||
err = json.Unmarshal(rb, &l)
|
||||
if err != nil {
|
||||
log.Errorf("json Unmarshal error, %s", err.Error())
|
||||
return err
|
||||
}
|
||||
err = db.Create(&l).Error
|
||||
if err != nil {
|
||||
log.Errorf("db create error, %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package models
|
||||
|
||||
import "go-admin/common/models"
|
||||
|
||||
type SysMenu struct {
|
||||
MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"`
|
||||
MenuName string `json:"menuName" gorm:"size:128;"`
|
||||
Title string `json:"title" gorm:"size:128;"`
|
||||
Icon string `json:"icon" gorm:"size:128;"`
|
||||
Path string `json:"path" gorm:"size:128;"`
|
||||
Paths string `json:"paths" gorm:"size:128;"`
|
||||
MenuType string `json:"menuType" gorm:"size:1;"`
|
||||
Action string `json:"action" gorm:"size:16;"`
|
||||
Permission string `json:"permission" gorm:"size:255;"`
|
||||
ParentId int `json:"parentId" gorm:"size:11;"`
|
||||
NoCache bool `json:"noCache" gorm:"size:8;"`
|
||||
Breadcrumb string `json:"breadcrumb" gorm:"size:255;"`
|
||||
Component string `json:"component" gorm:"size:255;"`
|
||||
Sort int `json:"sort" gorm:"size:4;"`
|
||||
Visible string `json:"visible" gorm:"size:1;"`
|
||||
IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"`
|
||||
SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"`
|
||||
Apis []int `json:"apis" gorm:"-"`
|
||||
DataScope string `json:"dataScope" gorm:"-"`
|
||||
Params string `json:"params" gorm:"-"`
|
||||
RoleId int `gorm:"-"`
|
||||
Children []SysMenu `json:"children,omitempty" gorm:"-"`
|
||||
IsSelect bool `json:"is_select" gorm:"-"`
|
||||
// AppCode identifies which application's seed.SeedMenus call wrote this
|
||||
// row; empty for the host's own built-in menus. NOT NULL DEFAULT '' for
|
||||
// the same reason sys_migration.app_code is (see contract/models.Migration):
|
||||
// AutoMigrate adding this column to an existing table leaves every
|
||||
// pre-existing row reading back as "" rather than NULL.
|
||||
AppCode string `json:"appCode" gorm:"type:varchar(64);not null;default:'';index:idx_sys_menu_app_code;comment:AppCode"`
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
type SysMenuSlice []SysMenu
|
||||
|
||||
func (x SysMenuSlice) Len() int { return len(x) }
|
||||
func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort }
|
||||
func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (*SysMenu) TableName() string {
|
||||
return "sys_menu"
|
||||
}
|
||||
|
||||
func (e *SysMenu) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysMenu) GetId() interface{} {
|
||||
return e.MenuId
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/storage"
|
||||
|
||||
"go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysOperaLog struct {
|
||||
models.Model
|
||||
Title string `json:"title" gorm:"size:255;comment:操作模块"`
|
||||
BusinessType string `json:"businessType" gorm:"size:128;comment:操作类型"`
|
||||
BusinessTypes string `json:"businessTypes" gorm:"size:128;comment:BusinessTypes"`
|
||||
Method string `json:"method" gorm:"size:128;comment:函数"`
|
||||
RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式 GET POST PUT DELETE"`
|
||||
OperatorType string `json:"operatorType" gorm:"size:128;comment:操作类型"`
|
||||
OperName string `json:"operName" gorm:"size:128;comment:操作者"`
|
||||
DeptName string `json:"deptName" gorm:"size:128;comment:部门名称"`
|
||||
OperUrl string `json:"operUrl" gorm:"size:255;comment:访问地址"`
|
||||
OperIp string `json:"operIp" gorm:"size:128;comment:客户端ip"`
|
||||
OperLocation string `json:"operLocation" gorm:"size:128;comment:访问位置"`
|
||||
OperParam string `json:"operParam" gorm:"text;comment:请求参数"`
|
||||
Status string `json:"status" gorm:"size:4;comment:操作状态 1:正常 2:关闭"`
|
||||
OperTime time.Time `json:"operTime" gorm:"comment:操作时间"`
|
||||
JsonResult string `json:"jsonResult" gorm:"size:255;comment:返回数据"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:备注"`
|
||||
LatencyTime string `json:"latencyTime" gorm:"size:128;comment:耗时"`
|
||||
UserAgent string `json:"userAgent" gorm:"size:255;comment:ua"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
|
||||
models.ControlBy
|
||||
}
|
||||
|
||||
func (*SysOperaLog) TableName() string {
|
||||
return "sys_opera_log"
|
||||
}
|
||||
|
||||
func (e *SysOperaLog) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysOperaLog) GetId() interface{} {
|
||||
return e.Id
|
||||
}
|
||||
|
||||
// SaveOperaLog 从队列中获取操作日志
|
||||
func SaveOperaLog(message storage.Messager) (err error) {
|
||||
//准备db
|
||||
db := sdk.Runtime.GetDbByTenant(message.GetPrefix())
|
||||
if db == nil {
|
||||
err = errors.New("db not exist")
|
||||
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
|
||||
// Log writing to the database ignores error
|
||||
return nil
|
||||
}
|
||||
var rb []byte
|
||||
rb, err = json.Marshal(message.GetValues())
|
||||
if err != nil {
|
||||
log.Errorf("json Marshal error, %s", err.Error())
|
||||
// Log writing to the database ignores error
|
||||
return nil
|
||||
}
|
||||
var l SysOperaLog
|
||||
err = json.Unmarshal(rb, &l)
|
||||
if err != nil {
|
||||
log.Errorf("json Unmarshal error, %s", err.Error())
|
||||
// Log writing to the database ignores error
|
||||
return nil
|
||||
}
|
||||
// 超出100个字符返回值截断
|
||||
if len(l.JsonResult) > 100 {
|
||||
l.JsonResult = l.JsonResult[:100]
|
||||
}
|
||||
err = db.Create(&l).Error
|
||||
if err != nil {
|
||||
log.Errorf("db create error, %s", err.Error())
|
||||
// Log writing to the database ignores error
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import "go-admin/common/models"
|
||||
|
||||
type SysPost struct {
|
||||
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"` //岗位编号
|
||||
PostName string `gorm:"size:128;" json:"postName"` //岗位名称
|
||||
PostCode string `gorm:"size:128;" json:"postCode"` //岗位代码
|
||||
Sort int `gorm:"size:4;" json:"sort"` //岗位排序
|
||||
Status int `gorm:"size:4;" json:"status"` //状态
|
||||
Remark string `gorm:"size:255;" json:"remark"` //描述
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
|
||||
DataScope string `gorm:"-" json:"dataScope"`
|
||||
Params string `gorm:"-" json:"params"`
|
||||
}
|
||||
|
||||
func (*SysPost) TableName() string {
|
||||
return "sys_post"
|
||||
}
|
||||
|
||||
func (e *SysPost) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysPost) GetId() interface{} {
|
||||
return e.PostId
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package models
|
||||
|
||||
import "go-admin/common/models"
|
||||
|
||||
type SysRole struct {
|
||||
RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码
|
||||
RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称
|
||||
Status string `json:"status" gorm:"size:4;"` // 状态 1禁用 2正常
|
||||
RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码
|
||||
RoleSort int `json:"roleSort" gorm:""` //角色排序
|
||||
Flag string `json:"flag" gorm:"size:128;"` //
|
||||
Remark string `json:"remark" gorm:"size:255;"` //备注
|
||||
Admin bool `json:"admin" gorm:"size:4;"`
|
||||
DataScope string `json:"dataScope" gorm:"size:128;"`
|
||||
Params string `json:"params" gorm:"-"`
|
||||
MenuIds []int `json:"menuIds" gorm:"-"`
|
||||
DeptIds []int `json:"deptIds" gorm:"-"`
|
||||
SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"`
|
||||
SysMenu *[]SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"`
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (*SysRole) TableName() string {
|
||||
return "sys_role"
|
||||
}
|
||||
|
||||
func (e *SysRole) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysRole) GetId() interface{} {
|
||||
return e.RoleId
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"go-admin/common/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SysUser struct {
|
||||
UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"`
|
||||
Username string `json:"username" gorm:"size:64;comment:用户名"`
|
||||
Password string `json:"-" gorm:"size:128;comment:密码"`
|
||||
NickName string `json:"nickName" gorm:"size:128;comment:昵称"`
|
||||
Phone string `json:"phone" gorm:"size:11;comment:手机号"`
|
||||
RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"`
|
||||
Salt string `json:"-" gorm:"size:255;comment:加盐"`
|
||||
Avatar string `json:"avatar" gorm:"size:255;comment:头像"`
|
||||
Sex string `json:"sex" gorm:"size:255;comment:性别"`
|
||||
Email string `json:"email" gorm:"size:128;comment:邮箱"`
|
||||
DeptId int `json:"deptId" gorm:"size:20;comment:部门"`
|
||||
PostId int `json:"postId" gorm:"size:20;comment:岗位"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:备注"`
|
||||
Status string `json:"status" gorm:"size:4;comment:状态"`
|
||||
DeptIds []int `json:"deptIds" gorm:"-"`
|
||||
PostIds []int `json:"postIds" gorm:"-"`
|
||||
RoleIds []int `json:"roleIds" gorm:"-"`
|
||||
Dept *SysDept `json:"dept"`
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (*SysUser) TableName() string {
|
||||
return "sys_user"
|
||||
}
|
||||
|
||||
func (e *SysUser) Generate() models.ActiveRecord {
|
||||
o := *e
|
||||
return &o
|
||||
}
|
||||
|
||||
func (e *SysUser) GetId() interface{} {
|
||||
return e.UserId
|
||||
}
|
||||
|
||||
// Encrypt hashes Password, unless it already holds a hash.
|
||||
//
|
||||
// The hooks below run on whatever is in the struct, and a user read from the
|
||||
// database carries the stored hash in that field. Hashing it again produces a
|
||||
// hash of a hash, and the password that user knows no longer matches anything:
|
||||
// they cannot log in, and nothing reports an error. The only thing preventing
|
||||
// that today is an Omit("password") on the one update that loads a user first,
|
||||
// which makes every other write to this model one line away from destroying
|
||||
// credentials.
|
||||
//
|
||||
// bcrypt.Cost parses a hash and fails on anything else, so it distinguishes
|
||||
// the two cases without the call site having to say which it is. The cost is
|
||||
// that a password which is itself a well-formed bcrypt hash would be stored
|
||||
// unchanged - a 60-character string beginning "$2a$", not something a person
|
||||
// types, and it grants whoever set it no access they did not already have.
|
||||
func (e *SysUser) Encrypt() error {
|
||||
if e.Password == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := bcrypt.Cost([]byte(e.Password)); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(e.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Password = string(hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SysUser) BeforeCreate(_ *gorm.DB) error {
|
||||
return e.Encrypt()
|
||||
}
|
||||
|
||||
func (e *SysUser) BeforeUpdate(_ *gorm.DB) error {
|
||||
return e.Encrypt()
|
||||
}
|
||||
|
||||
func (e *SysUser) AfterFind(_ *gorm.DB) error {
|
||||
e.DeptIds = []int{e.DeptId}
|
||||
e.PostIds = []int{e.PostId}
|
||||
e.RoleIds = []int{e.RoleId}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const knownPassword = "correct-horse-battery-staple"
|
||||
|
||||
// A user loaded from the database carries the stored hash in Password, and the
|
||||
// hooks run on whatever is in the struct. Hashing it a second time produces a
|
||||
// hash of a hash: the password the user knows stops matching, they cannot log
|
||||
// in, and nothing reports an error.
|
||||
//
|
||||
// Only an Omit("password") on one call site stood between this and every write
|
||||
// to the model. This is the test that removes the need for it.
|
||||
func TestEncryptLeavesAnAlreadyHashedPasswordAlone(t *testing.T) {
|
||||
fresh := SysUser{Password: knownPassword}
|
||||
if err := fresh.Encrypt(); err != nil {
|
||||
t.Fatalf("Encrypt: %v", err)
|
||||
}
|
||||
stored := fresh.Password
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte(knownPassword)); err != nil {
|
||||
t.Fatalf("setup failed: the password was not hashed: %v", err)
|
||||
}
|
||||
|
||||
// What a query puts in the struct, and what an update then hands the hook.
|
||||
loaded := SysUser{Password: stored}
|
||||
if err := loaded.Encrypt(); err != nil {
|
||||
t.Fatalf("Encrypt on a loaded user: %v", err)
|
||||
}
|
||||
if loaded.Password != stored {
|
||||
t.Error("Encrypt re-hashed a stored hash; the user can no longer log in")
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(loaded.Password), []byte(knownPassword)); err != nil {
|
||||
t.Errorf("the user can no longer log in with their password: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// The other half: a password that is not a hash still gets hashed, on create
|
||||
// and on update alike.
|
||||
func TestEncryptHashesAPlaintextPassword(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
hook func(*SysUser) error
|
||||
}{
|
||||
{"BeforeCreate", func(u *SysUser) error { return u.BeforeCreate(nil) }},
|
||||
{"BeforeUpdate", func(u *SysUser) error { return u.BeforeUpdate(nil) }},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
u := SysUser{Password: knownPassword}
|
||||
if err := c.hook(&u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u.Password == knownPassword {
|
||||
t.Fatal("the password was stored as it was typed")
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(knownPassword)); err != nil {
|
||||
t.Errorf("the stored value does not verify the password: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// An empty Password means "not being set", and must not become a hash of "".
|
||||
func TestEncryptIgnoresAnEmptyPassword(t *testing.T) {
|
||||
u := SysUser{}
|
||||
if err := u.Encrypt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u.Password != "" {
|
||||
t.Errorf("an unset password became %q", u.Password)
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt runs on every update of this model, including the ones that change
|
||||
// something else entirely. What it costs when there is nothing to do is the
|
||||
// difference between a profile update and a bcrypt round; the correctness test
|
||||
// above is what catches a regression, this reports the size of it.
|
||||
func BenchmarkEncrypt(b *testing.B) {
|
||||
fresh := SysUser{Password: knownPassword}
|
||||
if err := fresh.Encrypt(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.Run("already hashed", func(b *testing.B) {
|
||||
u := SysUser{Password: fresh.Password}
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := u.Encrypt(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("plaintext", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
u := SysUser{Password: knownPassword}
|
||||
if err := u.Encrypt(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
common "go-admin/common/middleware"
|
||||
)
|
||||
|
||||
// InitRouter 路由初始化,不要怀疑,这里用到了
|
||||
func InitRouter() {
|
||||
var r *gin.Engine
|
||||
h := sdk.Runtime.GetEngine()
|
||||
if h == nil {
|
||||
log.Fatal("not found engine...")
|
||||
os.Exit(-1)
|
||||
}
|
||||
switch h.(type) {
|
||||
case *gin.Engine:
|
||||
r = h.(*gin.Engine)
|
||||
default:
|
||||
log.Fatal("not support other engine")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// the jwt middleware: shared instance InitMiddleware built at startup,
|
||||
// not one built here per module (see common/middleware.GetAuthMiddleware).
|
||||
authMiddleware := common.GetAuthMiddleware()
|
||||
|
||||
// 注册系统路由
|
||||
InitSysRouter(r, authMiddleware)
|
||||
|
||||
// 注册业务路由
|
||||
// TODO: 这里可存放业务路由,里边并无实际路由只有演示代码
|
||||
InitExamplesRouter(r, authMiddleware)
|
||||
}
|
||||
@@ -3,11 +3,15 @@ package router
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/gin-gonic/gin"
|
||||
"go-admin/pkg/jwtauth"
|
||||
jwt "go-admin/pkg/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
)
|
||||
|
||||
var (
|
||||
routerNoCheckRole = make([]func(*gin.RouterGroup), 0)
|
||||
routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0)
|
||||
)
|
||||
|
||||
// 路由示例
|
||||
func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine {
|
||||
|
||||
// 无需认证的路由
|
||||
@@ -22,18 +26,16 @@ func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gi
|
||||
func examplesNoCheckRoleRouter(r *gin.Engine) {
|
||||
// 可根据业务需求来设置接口版本
|
||||
v1 := r.Group("/api/v1")
|
||||
// 空接口防止v1定义无使用报错
|
||||
v1.GET("/nilcheckrole", nil)
|
||||
|
||||
// {{无需认证路由自动补充在此处请勿删除}}
|
||||
for _, f := range routerNoCheckRole {
|
||||
f(v1)
|
||||
}
|
||||
}
|
||||
|
||||
// 需要认证的路由示例
|
||||
func examplesCheckRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) {
|
||||
// 可根据业务需求来设置接口版本
|
||||
v1 := r.Group("/api/v1")
|
||||
// 空接口防止v1定义无使用报错
|
||||
v1.GET("/checkrole", nil)
|
||||
|
||||
// {{认证路由自动补充在此处请勿删除}}
|
||||
for _, f := range routerCheckRole {
|
||||
f(v1, authMiddleware)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysApiRouter)
|
||||
}
|
||||
|
||||
// registerSysApiRouter
|
||||
func registerSysApiRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysApi{}
|
||||
r := v1.Group("/sys-api").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.PUT("/:id", api.Update)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysConfigRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysConfigRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysConfig{}
|
||||
r := v1.Group("/config").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("/:id", api.Update)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
|
||||
r1 := v1.Group("/configKey").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
r1.GET("/:configKey", api.GetSysConfigByKEYForService)
|
||||
}
|
||||
|
||||
r2 := v1.Group("/app-config")
|
||||
{
|
||||
r2.GET("", api.Get2SysApp)
|
||||
}
|
||||
|
||||
r3 := v1.Group("/set-config").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
r3.PUT("", api.Update2Set)
|
||||
r3.GET("", api.Get2Set)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysDeptRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysDeptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysDept{}
|
||||
|
||||
r := v1.Group("/dept").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("/:id", api.Update)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
|
||||
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
r1.GET("/deptTree", api.Get2Tree)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerDictRouter)
|
||||
}
|
||||
|
||||
func registerDictRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
dictApi := apis.SysDictType{}
|
||||
dataApi := apis.SysDictData{}
|
||||
dicts := v1.Group("/dict").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
|
||||
dicts.GET("/data", dataApi.GetPage)
|
||||
dicts.GET("/data/:dictCode", dataApi.Get)
|
||||
dicts.POST("/data", dataApi.Insert)
|
||||
dicts.PUT("/data/:dictCode", dataApi.Update)
|
||||
dicts.DELETE("/data", dataApi.Delete)
|
||||
|
||||
dicts.GET("/type-option-select", dictApi.GetAll)
|
||||
dicts.GET("/type", dictApi.GetPage)
|
||||
dicts.GET("/type/:id", dictApi.Get)
|
||||
dicts.POST("/type", dictApi.Insert)
|
||||
dicts.PUT("/type/:id", dictApi.Update)
|
||||
dicts.DELETE("/type", dictApi.Delete)
|
||||
}
|
||||
opSelect := v1.Group("/dict-data").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
opSelect.GET("/option-select", dataApi.GetAll)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysLoginLogRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysLoginLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysLoginLog{}
|
||||
|
||||
r := v1.Group("/sys-login-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysMenuRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysMenuRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysMenu{}
|
||||
|
||||
r := v1.Group("/menu").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("/:id", api.Update)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
|
||||
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
r1.GET("/menurole", api.GetMenuRole)
|
||||
//r1.GET("/menuids", api.GetMenuIDS)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysOperaLogRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysOperaLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysOperaLog{}
|
||||
r := v1.Group("/sys-opera-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSyPostRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSyPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysPost{}
|
||||
r := v1.Group("/post").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("/:id", api.Update)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysRoleRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysRole{}
|
||||
r := v1.Group("/role").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("/:id", api.Update)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
r1.PUT("/role-status", api.Update2Status)
|
||||
r1.PUT("/roledatascope", api.Update2DataScope)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/apis"
|
||||
"mime"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg/ws"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
|
||||
"go-admin/common/middleware"
|
||||
"go-admin/common/middleware/handler"
|
||||
_ "go-admin/docs/admin"
|
||||
)
|
||||
|
||||
func InitSysRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterGroup {
|
||||
g := r.Group("")
|
||||
sysBaseRouter(g)
|
||||
// 静态文件
|
||||
sysStaticFileRouter(g)
|
||||
// swagger;注意:生产环境可以注释掉
|
||||
if config.ApplicationConfig.Mode != "prod" {
|
||||
sysSwaggerRouter(g)
|
||||
}
|
||||
// 需要认证
|
||||
sysCheckRoleRouterInit(g, authMiddleware)
|
||||
return g
|
||||
}
|
||||
|
||||
func sysBaseRouter(r *gin.RouterGroup) {
|
||||
|
||||
go ws.WebsocketManager.Start()
|
||||
go ws.WebsocketManager.SendService()
|
||||
go ws.WebsocketManager.SendAllService()
|
||||
|
||||
if config.ApplicationConfig.Mode != "prod" {
|
||||
r.GET("/", apis.GoAdmin)
|
||||
}
|
||||
r.GET("/info", handler.Ping)
|
||||
}
|
||||
|
||||
func sysStaticFileRouter(r *gin.RouterGroup) {
|
||||
err := mime.AddExtensionType(".js", "application/javascript")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r.Static("/static", "./static")
|
||||
if config.ApplicationConfig.Mode != "prod" {
|
||||
r.Static("/form-generator", "./static/form-generator")
|
||||
}
|
||||
}
|
||||
|
||||
func sysSwaggerRouter(r *gin.RouterGroup) {
|
||||
r.GET("/swagger/admin/*any", ginSwagger.WrapHandler(swaggerfiles.NewHandler(), ginSwagger.InstanceName("admin")))
|
||||
}
|
||||
|
||||
func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
wss := r.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
wss.GET("/ws/:id/:channel", ws.WebsocketManager.WsClient)
|
||||
wss.GET("/wslogout/:id/:channel", ws.WebsocketManager.UnWsClient)
|
||||
}
|
||||
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
v1.POST("/login", authMiddleware.LoginHandler)
|
||||
// GET /api/v1/refresh_token 已移除,原因见 issue #820:
|
||||
// 该接口用业务 token 即可换取新 token,而续期上限 MaxRefresh 依据的
|
||||
// orig_iat 在每次续期时被一并重置,上限永远无法到达 —— token 一旦泄
|
||||
// 露即等同于永久访问权。它此前还位于 CasbinExclude 中,任何角色的已
|
||||
// 登录用户都能调用,不受权限约束。
|
||||
//
|
||||
// 官方前端从未调用该接口(store 中的 refreshToken action 无人 dispatch),
|
||||
// 移除不影响正常使用。若确需无感续期,应在 go-admin-core 中区分
|
||||
// access token 与 refresh token 后重新实现,而非沿用此路由。
|
||||
}
|
||||
registerBaseRouter(v1, authMiddleware)
|
||||
}
|
||||
|
||||
func registerBaseRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysMenu{}
|
||||
api2 := apis.SysDept{}
|
||||
v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
v1auth.GET("/roleMenuTreeselect/:roleId", api.GetMenuTreeSelect)
|
||||
//v1.GET("/menuTreeselect", api.GetMenuTreeSelect)
|
||||
v1auth.GET("/roleDeptTreeselect/:roleId", api2.GetDeptTreeRoleSelect)
|
||||
v1auth.POST("/logout", handler.LogOut)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/actions"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerSysUserRouter)
|
||||
}
|
||||
|
||||
// 需认证的路由代码
|
||||
func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysUser{}
|
||||
r := v1.Group("/sys-user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
|
||||
{
|
||||
r.GET("", api.GetPage)
|
||||
r.GET("/:id", api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("", api.Update)
|
||||
r.DELETE("", api.Delete)
|
||||
}
|
||||
|
||||
user := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
|
||||
{
|
||||
user.GET("/profile", api.GetProfile)
|
||||
user.POST("/avatar", api.InsetAvatar)
|
||||
user.PUT("/pwd/set", api.UpdatePwd)
|
||||
user.PUT("/pwd/reset", api.ResetPwd)
|
||||
user.PUT("/status", api.UpdateStatus)
|
||||
}
|
||||
v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
v1auth.GET("/getinfo", api.GetInfo)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/common/dto"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// SysApiGetPageReq 功能列表请求参数
|
||||
type SysApiGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
Title string `form:"title" search:"type:contains;column:title;table:sys_api" comment:"标题"`
|
||||
Path string `form:"path" search:"type:contains;column:path;table:sys_api" comment:"地址"`
|
||||
Action string `form:"action" search:"type:exact;column:action;table:sys_api" comment:"请求方式"`
|
||||
ParentId string `form:"parentId" search:"type:exact;column:parent_id;table:sys_api" comment:"按钮id"`
|
||||
Type string `form:"type" search:"-" comment:"类型"`
|
||||
SysApiOrder
|
||||
}
|
||||
|
||||
type SysApiOrder struct {
|
||||
TitleOrder string `search:"type:order;column:title;table:sys_api" form:"titleOrder"`
|
||||
PathOrder string `search:"type:order;column:path;table:sys_api" form:"pathOrder"`
|
||||
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_api" form:"createdAtOrder"`
|
||||
}
|
||||
|
||||
func (m *SysApiGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
// SysApiInsertReq 功能创建请求参数
|
||||
type SysApiInsertReq struct {
|
||||
Id int `json:"-" comment:"编码"` // 编码
|
||||
Handle string `json:"handle" comment:"handle"`
|
||||
Title string `json:"title" comment:"标题"`
|
||||
Path string `json:"path" comment:"地址"`
|
||||
Type string `json:"type" comment:""`
|
||||
Action string `json:"action" comment:"类型"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysApiInsertReq) Generate(model *models.SysApi) {
|
||||
model.Handle = s.Handle
|
||||
model.Title = s.Title
|
||||
model.Path = s.Path
|
||||
model.Type = s.Type
|
||||
model.Action = s.Action
|
||||
}
|
||||
|
||||
func (s *SysApiInsertReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// SysApiUpdateReq 功能更新请求参数
|
||||
type SysApiUpdateReq struct {
|
||||
Id int `uri:"id" comment:"编码"` // 编码
|
||||
Handle string `json:"handle" comment:"handle"`
|
||||
Title string `json:"title" comment:"标题"`
|
||||
Path string `json:"path" comment:"地址"`
|
||||
Type string `json:"type" comment:""`
|
||||
Action string `json:"action" comment:"类型"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysApiUpdateReq) Generate(model *models.SysApi) {
|
||||
if s.Id != 0 {
|
||||
model.Id = s.Id
|
||||
}
|
||||
model.Handle = s.Handle
|
||||
model.Title = s.Title
|
||||
model.Path = s.Path
|
||||
model.Type = s.Type
|
||||
model.Action = s.Action
|
||||
}
|
||||
|
||||
func (s *SysApiUpdateReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// SysApiGetReq 功能获取请求参数
|
||||
type SysApiGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysApiGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// SysApiDeleteReq 功能删除请求参数
|
||||
type SysApiDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
}
|
||||
|
||||
func (s *SysApiDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/common/dto"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// SysConfigGetPageReq 列表或者搜索使用结构体
|
||||
type SysConfigGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
ConfigName string `form:"configName" search:"type:contains;column:config_name;table:sys_config"`
|
||||
ConfigKey string `form:"configKey" search:"type:contains;column:config_key;table:sys_config"`
|
||||
ConfigType string `form:"configType" search:"type:exact;column:config_type;table:sys_config"`
|
||||
IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
|
||||
SysConfigOrder
|
||||
}
|
||||
|
||||
type SysConfigOrder struct {
|
||||
IdOrder string `search:"type:order;column:id;table:sys_config" form:"idOrder"`
|
||||
ConfigNameOrder string `search:"type:order;column:config_name;table:sys_config" form:"configNameOrder"`
|
||||
ConfigKeyOrder string `search:"type:order;column:config_key;table:sys_config" form:"configKeyOrder"`
|
||||
ConfigTypeOrder string `search:"type:order;column:config_type;table:sys_config" form:"configTypeOrder"`
|
||||
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_config" form:"createdAtOrder"`
|
||||
}
|
||||
|
||||
func (m *SysConfigGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysConfigGetToSysAppReq struct {
|
||||
IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
|
||||
}
|
||||
|
||||
func (m *SysConfigGetToSysAppReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
// SysConfigControl 增、改使用的结构体
|
||||
type SysConfigControl struct {
|
||||
Id int `uri:"Id" comment:"编码"` // 编码
|
||||
ConfigName string `json:"configName" comment:""`
|
||||
ConfigKey string `uri:"configKey" json:"configKey" comment:""`
|
||||
ConfigValue string `json:"configValue" comment:""`
|
||||
ConfigType string `json:"configType" comment:""`
|
||||
IsFrontend string `json:"isFrontend"`
|
||||
Remark string `json:"remark" comment:""`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型
|
||||
func (s *SysConfigControl) Generate(model *models.SysConfig) {
|
||||
if s.Id == 0 {
|
||||
model.Model = common.Model{Id: s.Id}
|
||||
}
|
||||
model.ConfigName = s.ConfigName
|
||||
model.ConfigKey = s.ConfigKey
|
||||
model.ConfigValue = s.ConfigValue
|
||||
model.ConfigType = s.ConfigType
|
||||
model.IsFrontend = s.IsFrontend
|
||||
model.Remark = s.Remark
|
||||
|
||||
}
|
||||
|
||||
// GetId 获取数据对应的ID
|
||||
func (s *SysConfigControl) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// GetSetSysConfigReq 增、改使用的结构体
|
||||
type GetSetSysConfigReq struct {
|
||||
ConfigKey string `json:"configKey" comment:""`
|
||||
ConfigValue string `json:"configValue" comment:""`
|
||||
}
|
||||
|
||||
// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型
|
||||
func (s *GetSetSysConfigReq) Generate(model *models.SysConfig) {
|
||||
model.ConfigValue = s.ConfigValue
|
||||
}
|
||||
|
||||
type UpdateSetSysConfigReq map[string]string
|
||||
|
||||
// SysConfigByKeyReq 根据Key获取配置
|
||||
type SysConfigByKeyReq struct {
|
||||
ConfigKey string `uri:"configKey" search:"type:contains;column:config_key;table:sys_config"`
|
||||
}
|
||||
|
||||
func (m *SysConfigByKeyReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type GetSysConfigByKEYForServiceResp struct {
|
||||
ConfigKey string `json:"configKey" comment:""`
|
||||
ConfigValue string `json:"configValue" comment:""`
|
||||
}
|
||||
|
||||
type SysConfigGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysConfigGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysConfigDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysConfigDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// SysDeptGetPageReq 列表或者搜索使用结构体
|
||||
type SysDeptGetPageReq struct {
|
||||
DeptId int `form:"deptId" search:"type:exact;column:dept_id;table:sys_dept" comment:"id"` //id
|
||||
ParentId int `form:"parentId" search:"type:exact;column:parent_id;table:sys_dept" comment:"上级部门"` //上级部门
|
||||
DeptPath string `form:"deptPath" search:"type:exact;column:dept_path;table:sys_dept" comment:""` //路径
|
||||
DeptName string `form:"deptName" search:"type:exact;column:dept_name;table:sys_dept" comment:"部门名称"` //部门名称
|
||||
Sort int `form:"sort" search:"type:exact;column:sort;table:sys_dept" comment:"排序"` //排序
|
||||
Leader string `form:"leader" search:"type:exact;column:leader;table:sys_dept" comment:"负责人"` //负责人
|
||||
Phone string `form:"phone" search:"type:exact;column:phone;table:sys_dept" comment:"手机"` //手机
|
||||
Email string `form:"email" search:"type:exact;column:email;table:sys_dept" comment:"邮箱"` //邮箱
|
||||
Status string `form:"status" search:"type:exact;column:status;table:sys_dept" comment:"状态"` //状态
|
||||
}
|
||||
|
||||
func (m *SysDeptGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysDeptInsertReq struct {
|
||||
DeptId int `uri:"id" comment:"编码"` // 编码
|
||||
ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门
|
||||
DeptPath string `json:"deptPath" comment:""` //路径
|
||||
DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称
|
||||
Sort int `json:"sort" comment:"排序" vd:"?"` //排序
|
||||
Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人
|
||||
Phone string `json:"phone" comment:"手机" vd:"?"` //手机
|
||||
Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱
|
||||
Status int `json:"status" comment:"状态" vd:"$>0"` //状态
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysDeptInsertReq) Generate(model *models.SysDept) {
|
||||
if s.DeptId != 0 {
|
||||
model.DeptId = s.DeptId
|
||||
}
|
||||
model.DeptName = s.DeptName
|
||||
model.ParentId = s.ParentId
|
||||
model.DeptPath = s.DeptPath
|
||||
model.Sort = s.Sort
|
||||
model.Leader = s.Leader
|
||||
model.Phone = s.Phone
|
||||
model.Email = s.Email
|
||||
model.Status = s.Status
|
||||
}
|
||||
|
||||
// GetId 获取数据对应的ID
|
||||
func (s *SysDeptInsertReq) GetId() interface{} {
|
||||
return s.DeptId
|
||||
}
|
||||
|
||||
type SysDeptUpdateReq struct {
|
||||
DeptId int `uri:"id" comment:"编码"` // 编码
|
||||
ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门
|
||||
DeptPath string `json:"deptPath" comment:""` //路径
|
||||
DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称
|
||||
Sort int `json:"sort" comment:"排序" vd:"?"` //排序
|
||||
Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人
|
||||
Phone string `json:"phone" comment:"手机" vd:"?"` //手机
|
||||
Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱
|
||||
Status int `json:"status" comment:"状态" vd:"$>0"` //状态
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
// Generate 结构体数据转化 从 SysDeptControl 至 SysDept 对应的模型
|
||||
func (s *SysDeptUpdateReq) Generate(model *models.SysDept) {
|
||||
if s.DeptId != 0 {
|
||||
model.DeptId = s.DeptId
|
||||
}
|
||||
model.DeptName = s.DeptName
|
||||
model.ParentId = s.ParentId
|
||||
model.DeptPath = s.DeptPath
|
||||
model.Sort = s.Sort
|
||||
model.Leader = s.Leader
|
||||
model.Phone = s.Phone
|
||||
model.Email = s.Email
|
||||
model.Status = s.Status
|
||||
}
|
||||
|
||||
// GetId 获取数据对应的ID
|
||||
func (s *SysDeptUpdateReq) GetId() interface{} {
|
||||
return s.DeptId
|
||||
}
|
||||
|
||||
type SysDeptGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysDeptGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDeptDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
}
|
||||
|
||||
func (s *SysDeptDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
|
||||
type DeptLabel struct {
|
||||
Id int `gorm:"-" json:"id"`
|
||||
Label string `gorm:"-" json:"label"`
|
||||
Children []DeptLabel `gorm:"-" json:"children"`
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/common/dto"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysDictDataGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
Id int `form:"id" search:"type:exact;column:dict_code;table:sys_dict_data" comment:""`
|
||||
DictLabel string `form:"dictLabel" search:"type:contains;column:dict_label;table:sys_dict_data" comment:""`
|
||||
DictValue string `form:"dictValue" search:"type:contains;column:dict_value;table:sys_dict_data" comment:""`
|
||||
DictType string `form:"dictType" search:"type:contains;column:dict_type;table:sys_dict_data" comment:""`
|
||||
Status string `form:"status" search:"type:exact;column:status;table:sys_dict_data" comment:""`
|
||||
}
|
||||
|
||||
func (m *SysDictDataGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysDictDataGetAllResp struct {
|
||||
DictLabel string `json:"label"`
|
||||
DictValue string `json:"value"`
|
||||
}
|
||||
|
||||
type SysDictDataInsertReq struct {
|
||||
Id int `json:"-" comment:""`
|
||||
DictSort int `json:"dictSort" comment:""`
|
||||
DictLabel string `json:"dictLabel" comment:""`
|
||||
DictValue string `json:"dictValue" comment:""`
|
||||
DictType string `json:"dictType" comment:""`
|
||||
CssClass string `json:"cssClass" comment:""`
|
||||
ListClass string `json:"listClass" comment:""`
|
||||
IsDefault string `json:"isDefault" comment:""`
|
||||
Status int `json:"status" comment:""`
|
||||
Default string `json:"default" comment:""`
|
||||
Remark string `json:"remark" comment:""`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysDictDataInsertReq) Generate(model *models.SysDictData) {
|
||||
model.DictCode = s.Id
|
||||
model.DictSort = s.DictSort
|
||||
model.DictLabel = s.DictLabel
|
||||
model.DictValue = s.DictValue
|
||||
model.DictType = s.DictType
|
||||
model.CssClass = s.CssClass
|
||||
model.ListClass = s.ListClass
|
||||
model.IsDefault = s.IsDefault
|
||||
model.Status = s.Status
|
||||
model.Default = s.Default
|
||||
model.Remark = s.Remark
|
||||
}
|
||||
|
||||
func (s *SysDictDataInsertReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDictDataUpdateReq struct {
|
||||
Id int `uri:"dictCode" comment:""`
|
||||
DictSort int `json:"dictSort" comment:""`
|
||||
DictLabel string `json:"dictLabel" comment:""`
|
||||
DictValue string `json:"dictValue" comment:""`
|
||||
DictType string `json:"dictType" comment:""`
|
||||
CssClass string `json:"cssClass" comment:""`
|
||||
ListClass string `json:"listClass" comment:""`
|
||||
IsDefault string `json:"isDefault" comment:""`
|
||||
Status int `json:"status" comment:""`
|
||||
Default string `json:"default" comment:""`
|
||||
Remark string `json:"remark" comment:""`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysDictDataUpdateReq) Generate(model *models.SysDictData) {
|
||||
model.DictCode = s.Id
|
||||
model.DictSort = s.DictSort
|
||||
model.DictLabel = s.DictLabel
|
||||
model.DictValue = s.DictValue
|
||||
model.DictType = s.DictType
|
||||
model.CssClass = s.CssClass
|
||||
model.ListClass = s.ListClass
|
||||
model.IsDefault = s.IsDefault
|
||||
model.Status = s.Status
|
||||
model.Default = s.Default
|
||||
model.Remark = s.Remark
|
||||
}
|
||||
|
||||
func (s *SysDictDataUpdateReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDictDataGetReq struct {
|
||||
Id int `uri:"dictCode"`
|
||||
}
|
||||
|
||||
func (s *SysDictDataGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDictDataDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
common.ControlBy `json:"-"`
|
||||
}
|
||||
|
||||
func (s *SysDictDataDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/common/dto"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysDictTypeGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
DictId []int `form:"dictId" search:"type:in;column:dict_id;table:sys_dict_type"`
|
||||
DictName string `form:"dictName" search:"type:icontains;column:dict_name;table:sys_dict_type"`
|
||||
DictType string `form:"dictType" search:"type:icontains;column:dict_type;table:sys_dict_type"`
|
||||
Status int `form:"status" search:"type:exact;column:status;table:sys_dict_type"`
|
||||
}
|
||||
|
||||
type SysDictTypeOrder struct {
|
||||
DictIdOrder string `search:"type:order;column:dict_id;table:sys_dict_type" form:"dictIdOrder"`
|
||||
}
|
||||
|
||||
func (m *SysDictTypeGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysDictTypeInsertReq struct {
|
||||
Id int `uri:"id"`
|
||||
DictName string `json:"dictName"`
|
||||
DictType string `json:"dictType"`
|
||||
Status int `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysDictTypeInsertReq) Generate(model *models.SysDictType) {
|
||||
if s.Id != 0 {
|
||||
model.ID = s.Id
|
||||
}
|
||||
model.DictName = s.DictName
|
||||
model.DictType = s.DictType
|
||||
model.Status = s.Status
|
||||
model.Remark = s.Remark
|
||||
|
||||
}
|
||||
|
||||
func (s *SysDictTypeInsertReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDictTypeUpdateReq struct {
|
||||
Id int `uri:"id"`
|
||||
DictName string `json:"dictName"`
|
||||
DictType string `json:"dictType"`
|
||||
Status int `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysDictTypeUpdateReq) Generate(model *models.SysDictType) {
|
||||
if s.Id != 0 {
|
||||
model.ID = s.Id
|
||||
}
|
||||
model.DictName = s.DictName
|
||||
model.DictType = s.DictType
|
||||
model.Status = s.Status
|
||||
model.Remark = s.Remark
|
||||
|
||||
}
|
||||
|
||||
func (s *SysDictTypeUpdateReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDictTypeGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysDictTypeGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysDictTypeDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysDictTypeDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go-admin/common/dto"
|
||||
)
|
||||
|
||||
type SysLoginLogGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
Username string `form:"username" search:"type:exact;column:username;table:sys_login_log" comment:"用户名"`
|
||||
Status string `form:"status" search:"type:exact;column:status;table:sys_login_log" comment:"状态"`
|
||||
Ipaddr string `form:"ipaddr" search:"type:exact;column:ipaddr;table:sys_login_log" comment:"ip地址"`
|
||||
LoginLocation string `form:"loginLocation" search:"type:exact;column:login_location;table:sys_login_log" comment:"归属地"`
|
||||
BeginTime string `form:"beginTime" search:"type:gte;column:ctime;table:sys_login_log" comment:"创建时间"`
|
||||
EndTime string `form:"endTime" search:"type:lte;column:ctime;table:sys_login_log" comment:"创建时间"`
|
||||
SysLoginLogOrder
|
||||
}
|
||||
|
||||
type SysLoginLogOrder struct {
|
||||
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_login_log" form:"createdAtOrder"`
|
||||
}
|
||||
|
||||
func (m *SysLoginLogGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysLoginLogControl struct {
|
||||
ID int `uri:"Id" comment:"主键"` // 主键
|
||||
Username string `json:"username" comment:"用户名"`
|
||||
Status string `json:"status" comment:"状态"`
|
||||
Ipaddr string `json:"ipaddr" comment:"ip地址"`
|
||||
LoginLocation string `json:"loginLocation" comment:"归属地"`
|
||||
Browser string `json:"browser" comment:"浏览器"`
|
||||
Os string `json:"os" comment:"系统"`
|
||||
Platform string `json:"platform" comment:"固件"`
|
||||
LoginTime time.Time `json:"loginTime" comment:"登录时间"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
Msg string `json:"msg" comment:"信息"`
|
||||
}
|
||||
|
||||
type SysLoginLogGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysLoginLogGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// SysLoginLogDeleteReq 功能删除请求参数
|
||||
type SysLoginLogDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
}
|
||||
|
||||
func (s *SysLoginLogDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
common "go-admin/common/models"
|
||||
|
||||
"go-admin/common/dto"
|
||||
)
|
||||
|
||||
// SysMenuGetPageReq 列表或者搜索使用结构体
|
||||
type SysMenuGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
Title string `form:"title" search:"type:contains;column:title;table:sys_menu" comment:"菜单名称"` // 菜单名称
|
||||
Visible int `form:"visible" search:"type:exact;column:visible;table:sys_menu" comment:"显示状态"` // 显示状态
|
||||
}
|
||||
|
||||
func (m *SysMenuGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysMenuInsertReq struct {
|
||||
MenuId int `uri:"id" comment:"编码"` // 编码
|
||||
MenuName string `form:"menuName" comment:"菜单name"` //菜单name
|
||||
Title string `form:"title" comment:"显示名称"` //显示名称
|
||||
Icon string `form:"icon" comment:"图标"` //图标
|
||||
Path string `form:"path" comment:"路径"` //路径
|
||||
Paths string `form:"paths" comment:"id路径"` //id路径
|
||||
MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型
|
||||
SysApi []models.SysApi `form:"sysApi"`
|
||||
Apis []int `form:"apis"`
|
||||
Action string `form:"action" comment:"请求方式"` //请求方式
|
||||
Permission string `form:"permission" comment:"权限编码"` //权限编码
|
||||
ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单
|
||||
NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存
|
||||
Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑
|
||||
Component string `form:"component" comment:"组件"` //组件
|
||||
Sort int `form:"sort" comment:"排序"` //排序
|
||||
Visible string `form:"visible" comment:"是否显示"` //是否显示
|
||||
IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysMenuInsertReq) Generate(model *models.SysMenu) {
|
||||
if s.MenuId != 0 {
|
||||
model.MenuId = s.MenuId
|
||||
}
|
||||
model.MenuName = s.MenuName
|
||||
model.Title = s.Title
|
||||
model.Icon = s.Icon
|
||||
model.Path = s.Path
|
||||
model.Paths = s.Paths
|
||||
model.MenuType = s.MenuType
|
||||
model.Action = s.Action
|
||||
model.SysApi = s.SysApi
|
||||
model.Permission = s.Permission
|
||||
model.ParentId = s.ParentId
|
||||
model.NoCache = s.NoCache
|
||||
model.Breadcrumb = s.Breadcrumb
|
||||
model.Component = s.Component
|
||||
model.Sort = s.Sort
|
||||
model.Visible = s.Visible
|
||||
model.IsFrame = s.IsFrame
|
||||
if s.CreateBy != 0 {
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
if s.UpdateBy != 0 {
|
||||
model.UpdateBy = s.UpdateBy
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SysMenuInsertReq) GetId() interface{} {
|
||||
return s.MenuId
|
||||
}
|
||||
|
||||
type SysMenuUpdateReq struct {
|
||||
MenuId int `uri:"id" comment:"编码"` // 编码
|
||||
MenuName string `form:"menuName" comment:"菜单name"` //菜单name
|
||||
Title string `form:"title" comment:"显示名称"` //显示名称
|
||||
Icon string `form:"icon" comment:"图标"` //图标
|
||||
Path string `form:"path" comment:"路径"` //路径
|
||||
Paths string `form:"paths" comment:"id路径"` //id路径
|
||||
MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型
|
||||
SysApi []models.SysApi `form:"sysApi"`
|
||||
Apis []int `form:"apis"`
|
||||
Action string `form:"action" comment:"请求方式"` //请求方式
|
||||
Permission string `form:"permission" comment:"权限编码"` //权限编码
|
||||
ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单
|
||||
NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存
|
||||
Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑
|
||||
Component string `form:"component" comment:"组件"` //组件
|
||||
Sort int `form:"sort" comment:"排序"` //排序
|
||||
Visible string `form:"visible" comment:"是否显示"` //是否显示
|
||||
IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysMenuUpdateReq) Generate(model *models.SysMenu) {
|
||||
if s.MenuId != 0 {
|
||||
model.MenuId = s.MenuId
|
||||
}
|
||||
model.MenuName = s.MenuName
|
||||
model.Title = s.Title
|
||||
model.Icon = s.Icon
|
||||
model.Path = s.Path
|
||||
model.Paths = s.Paths
|
||||
model.MenuType = s.MenuType
|
||||
model.Action = s.Action
|
||||
model.SysApi = s.SysApi
|
||||
model.Permission = s.Permission
|
||||
model.ParentId = s.ParentId
|
||||
model.NoCache = s.NoCache
|
||||
model.Breadcrumb = s.Breadcrumb
|
||||
model.Component = s.Component
|
||||
model.Sort = s.Sort
|
||||
model.Visible = s.Visible
|
||||
model.IsFrame = s.IsFrame
|
||||
if s.CreateBy != 0 {
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
if s.UpdateBy != 0 {
|
||||
model.UpdateBy = s.UpdateBy
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SysMenuUpdateReq) GetId() interface{} {
|
||||
return s.MenuId
|
||||
}
|
||||
|
||||
type SysMenuGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysMenuGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysMenuDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysMenuDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
|
||||
type MenuLabel struct {
|
||||
Id int `json:"id,omitempty" gorm:"-"`
|
||||
Label string `json:"label,omitempty" gorm:"-"`
|
||||
Children []MenuLabel `json:"children,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
type MenuRole struct {
|
||||
models.SysMenu
|
||||
IsSelect bool `json:"is_select" gorm:"-"`
|
||||
}
|
||||
|
||||
type SelectRole struct {
|
||||
RoleId int `uri:"roleId"`
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/common/dto"
|
||||
"go-admin/common/global"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// Deprecated: use global.OperaStatusEnabled / global.OperaStatusDisabled.
|
||||
// These two names are kept - misspelling and all - because forks import them;
|
||||
// the values moved to common/global so common/middleware no longer has to
|
||||
// import this package. See docs/contract.md.
|
||||
const (
|
||||
OperaStatusEnabel = global.OperaStatusEnabled // 状态-正常
|
||||
OperaStatusDisable = global.OperaStatusDisabled // 状态-关闭
|
||||
)
|
||||
|
||||
type SysOperaLogGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
Title string `form:"title" search:"type:contains;column:title;table:sys_opera_log" comment:"操作模块"`
|
||||
Method string `form:"method" search:"type:contains;column:method;table:sys_opera_log" comment:"函数"`
|
||||
RequestMethod string `form:"requestMethod" search:"type:contains;column:request_method;table:sys_opera_log" comment:"请求方式: GET POST PUT DELETE"`
|
||||
OperUrl string `form:"operUrl" search:"type:contains;column:oper_url;table:sys_opera_log" comment:"访问地址"`
|
||||
OperIp string `form:"operIp" search:"type:exact;column:oper_ip;table:sys_opera_log" comment:"客户端ip"`
|
||||
Status int `form:"status" search:"type:exact;column:status;table:sys_opera_log" comment:"状态 1:正常 2:关闭"`
|
||||
BeginTime string `form:"beginTime" search:"type:gte;column:created_at;table:sys_opera_log" comment:"创建时间"`
|
||||
EndTime string `form:"endTime" search:"type:lte;column:created_at;table:sys_opera_log" comment:"更新时间"`
|
||||
SysOperaLogOrder
|
||||
}
|
||||
|
||||
type SysOperaLogOrder struct {
|
||||
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_opera_log" form:"createdAtOrder"`
|
||||
}
|
||||
|
||||
func (m *SysOperaLogGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysOperaLogControl struct {
|
||||
ID int `uri:"Id" comment:"编码"` // 编码
|
||||
Title string `json:"title" comment:"操作模块"`
|
||||
BusinessType string `json:"businessType" comment:"操作类型"`
|
||||
BusinessTypes string `json:"businessTypes" comment:""`
|
||||
Method string `json:"method" comment:"函数"`
|
||||
RequestMethod string `json:"requestMethod" comment:"请求方式"`
|
||||
OperatorType string `json:"operatorType" comment:"操作类型"`
|
||||
OperName string `json:"operName" comment:"操作者"`
|
||||
DeptName string `json:"deptName" comment:"部门名称"`
|
||||
OperUrl string `json:"operUrl" comment:"访问地址"`
|
||||
OperIp string `json:"operIp" comment:"客户端ip"`
|
||||
OperLocation string `json:"operLocation" comment:"访问位置"`
|
||||
OperParam string `json:"operParam" comment:"请求参数"`
|
||||
Status string `json:"status" comment:"操作状态"`
|
||||
OperTime time.Time `json:"operTime" comment:"操作时间"`
|
||||
JsonResult string `json:"jsonResult" comment:"返回数据"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
LatencyTime string `json:"latencyTime" comment:"耗时"`
|
||||
UserAgent string `json:"userAgent" comment:"ua"`
|
||||
}
|
||||
|
||||
func (s *SysOperaLogControl) Generate() (*models.SysOperaLog, error) {
|
||||
return &models.SysOperaLog{
|
||||
Model: common.Model{Id: s.ID},
|
||||
Title: s.Title,
|
||||
BusinessType: s.BusinessType,
|
||||
BusinessTypes: s.BusinessTypes,
|
||||
Method: s.Method,
|
||||
RequestMethod: s.RequestMethod,
|
||||
OperatorType: s.OperatorType,
|
||||
OperName: s.OperName,
|
||||
DeptName: s.DeptName,
|
||||
OperUrl: s.OperUrl,
|
||||
OperIp: s.OperIp,
|
||||
OperLocation: s.OperLocation,
|
||||
OperParam: s.OperParam,
|
||||
Status: s.Status,
|
||||
OperTime: s.OperTime,
|
||||
JsonResult: s.JsonResult,
|
||||
Remark: s.Remark,
|
||||
LatencyTime: s.LatencyTime,
|
||||
UserAgent: s.UserAgent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SysOperaLogControl) GetId() interface{} {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
type SysOperaLogGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysOperaLogGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// SysOperaLogDeleteReq 功能删除请求参数
|
||||
type SysOperaLogDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
}
|
||||
|
||||
func (s *SysOperaLogDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go-admin/common/global"
|
||||
)
|
||||
|
||||
// The values moved to common/global so common/middleware would stop importing
|
||||
// this package; these two names stayed behind as aliases, misspelling and all,
|
||||
// because forks import them.
|
||||
//
|
||||
// If they ever drift apart, rows written through the two spellings land in
|
||||
// different buckets and the operation-log filter silently misses half of them.
|
||||
func TestDeprecatedStatusAliasesStillMatch(t *testing.T) {
|
||||
if OperaStatusEnabel != global.OperaStatusEnabled {
|
||||
t.Errorf("OperaStatusEnabel = %q, global.OperaStatusEnabled = %q",
|
||||
OperaStatusEnabel, global.OperaStatusEnabled)
|
||||
}
|
||||
if OperaStatusDisable != global.OperaStatusDisabled {
|
||||
t.Errorf("OperaStatusDisable = %q, global.OperaStatusDisabled = %q",
|
||||
OperaStatusDisable, global.OperaStatusDisabled)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
common "go-admin/common/models"
|
||||
|
||||
"go-admin/common/dto"
|
||||
)
|
||||
|
||||
// SysPostPageReq 列表或者搜索使用结构体
|
||||
type SysPostPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
PostId int `form:"postId" search:"type:exact;column:post_id;table:sys_post" comment:"id"` // id
|
||||
PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post" comment:"名称"` // 名称
|
||||
PostCode string `form:"postCode" search:"type:contains;column:post_code;table:sys_post" comment:"编码"` // 编码
|
||||
Sort int `form:"sort" search:"type:exact;column:sort;table:sys_post" comment:"排序"` // 排序
|
||||
Status int `form:"status" search:"type:exact;column:status;table:sys_post" comment:"状态"` // 状态
|
||||
Remark string `form:"remark" search:"type:exact;column:remark;table:sys_post" comment:"备注"` // 备注
|
||||
}
|
||||
|
||||
func (m *SysPostPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
// SysPostInsertReq 增使用的结构体
|
||||
type SysPostInsertReq struct {
|
||||
PostId int `uri:"id" comment:"id"`
|
||||
PostName string `form:"postName" comment:"名称"`
|
||||
PostCode string `form:"postCode" comment:"编码"`
|
||||
Sort int `form:"sort" comment:"排序"`
|
||||
Status int `form:"status" comment:"状态"`
|
||||
Remark string `form:"remark" comment:"备注"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysPostInsertReq) Generate(model *models.SysPost) {
|
||||
model.PostName = s.PostName
|
||||
model.PostCode = s.PostCode
|
||||
model.Sort = s.Sort
|
||||
model.Status = s.Status
|
||||
model.Remark = s.Remark
|
||||
if s.ControlBy.UpdateBy != 0 {
|
||||
model.UpdateBy = s.UpdateBy
|
||||
}
|
||||
if s.ControlBy.CreateBy != 0 {
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
}
|
||||
|
||||
// GetId 获取数据对应的ID
|
||||
func (s *SysPostInsertReq) GetId() interface{} {
|
||||
return s.PostId
|
||||
}
|
||||
|
||||
// SysPostUpdateReq 改使用的结构体
|
||||
type SysPostUpdateReq struct {
|
||||
PostId int `uri:"id" comment:"id"`
|
||||
PostName string `form:"postName" comment:"名称"`
|
||||
PostCode string `form:"postCode" comment:"编码"`
|
||||
Sort int `form:"sort" comment:"排序"`
|
||||
Status int `form:"status" comment:"状态"`
|
||||
Remark string `form:"remark" comment:"备注"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysPostUpdateReq) Generate(model *models.SysPost) {
|
||||
model.PostId = s.PostId
|
||||
model.PostName = s.PostName
|
||||
model.PostCode = s.PostCode
|
||||
model.Sort = s.Sort
|
||||
model.Status = s.Status
|
||||
model.Remark = s.Remark
|
||||
if s.ControlBy.UpdateBy != 0 {
|
||||
model.UpdateBy = s.UpdateBy
|
||||
}
|
||||
if s.ControlBy.CreateBy != 0 {
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SysPostUpdateReq) GetId() interface{} {
|
||||
return s.PostId
|
||||
}
|
||||
|
||||
// SysPostGetReq 获取单个的结构体
|
||||
type SysPostGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysPostGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
// SysPostDeleteReq 删除的结构体
|
||||
type SysPostDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysPostDeleteReq) Generate(model *models.SysPost) {
|
||||
if s.ControlBy.UpdateBy != 0 {
|
||||
model.UpdateBy = s.UpdateBy
|
||||
}
|
||||
if s.ControlBy.CreateBy != 0 {
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SysPostDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
common "go-admin/common/models"
|
||||
|
||||
"go-admin/common/dto"
|
||||
)
|
||||
|
||||
type SysRoleGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
|
||||
RoleId int `form:"roleId" search:"type:exact;column:role_id;table:sys_role" comment:"角色编码"` // 角色编码
|
||||
RoleName string `form:"roleName" search:"type:exact;column:role_name;table:sys_role" comment:"角色名称"` // 角色名称
|
||||
Status string `form:"status" search:"type:exact;column:status;table:sys_role" comment:"状态"` // 状态
|
||||
RoleKey string `form:"roleKey" search:"type:exact;column:role_key;table:sys_role" comment:"角色代码"` // 角色代码
|
||||
RoleSort int `form:"roleSort" search:"type:exact;column:role_sort;table:sys_role" comment:"角色排序"` // 角色排序
|
||||
Flag string `form:"flag" search:"type:exact;column:flag;table:sys_role" comment:"标记"` // 标记
|
||||
Remark string `form:"remark" search:"type:exact;column:remark;table:sys_role" comment:"备注"` // 备注
|
||||
Admin bool `form:"admin" search:"type:exact;column:admin;table:sys_role" comment:"是否管理员"`
|
||||
DataScope string `form:"dataScope" search:"type:exact;column:data_scope;table:sys_role" comment:"是否管理员"`
|
||||
}
|
||||
|
||||
type SysRoleOrder struct {
|
||||
RoleIdOrder string `search:"type:order;column:role_id;table:sys_role" form:"roleIdOrder"`
|
||||
RoleNameOrder string `search:"type:order;column:role_name;table:sys_role" form:"roleNameOrder"`
|
||||
RoleSortOrder string `search:"type:order;column:role_sort;table:sys_role" form:"usernameOrder"`
|
||||
StatusOrder string `search:"type:order;column:status;table:sys_role" form:"statusOrder"`
|
||||
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_role" form:"createdAtOrder"`
|
||||
}
|
||||
|
||||
func (m *SysRoleGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type SysRoleInsertReq struct {
|
||||
RoleId int `uri:"id" comment:"角色编码"` // 角色编码
|
||||
RoleName string `form:"roleName" comment:"角色名称"` // 角色名称
|
||||
Status string `form:"status" comment:"状态"` // 状态 1禁用 2正常
|
||||
RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码
|
||||
RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序
|
||||
Flag string `form:"flag" comment:"标记"` // 标记
|
||||
Remark string `form:"remark" comment:"备注"` // 备注
|
||||
Admin bool `form:"admin" comment:"是否管理员"`
|
||||
DataScope string `form:"dataScope" vd:"$=='1'||$=='2'||$=='3'||$=='4'||$=='5'"` // must be one of actions.DataScope{All,Custom,Dept,DeptTree,Self}; PRD 006 F14/H2
|
||||
SysMenu []models.SysMenu `form:"sysMenu"`
|
||||
MenuIds []int `form:"menuIds"`
|
||||
SysDept []models.SysDept `form:"sysDept"`
|
||||
DeptIds []int `form:"deptIds"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysRoleInsertReq) Generate(model *models.SysRole) {
|
||||
if s.RoleId != 0 {
|
||||
model.RoleId = s.RoleId
|
||||
}
|
||||
model.RoleName = s.RoleName
|
||||
model.Status = s.Status
|
||||
model.RoleKey = s.RoleKey
|
||||
model.RoleSort = s.RoleSort
|
||||
model.Flag = s.Flag
|
||||
model.Remark = s.Remark
|
||||
model.Admin = s.Admin
|
||||
model.DataScope = s.DataScope
|
||||
model.SysMenu = &s.SysMenu
|
||||
model.SysDept = s.SysDept
|
||||
}
|
||||
|
||||
func (s *SysRoleInsertReq) GetId() interface{} {
|
||||
return s.RoleId
|
||||
}
|
||||
|
||||
type SysRoleUpdateReq struct {
|
||||
RoleId int `uri:"id" comment:"角色编码"` // 角色编码
|
||||
RoleName string `form:"roleName" comment:"角色名称"` // 角色名称
|
||||
Status string `form:"status" comment:"状态"` // 状态
|
||||
RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码
|
||||
RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序
|
||||
Flag string `form:"flag" comment:"标记"` // 标记
|
||||
Remark string `form:"remark" comment:"备注"` // 备注
|
||||
Admin bool `form:"admin" comment:"是否管理员"`
|
||||
DataScope string `form:"dataScope" vd:"$=='1'||$=='2'||$=='3'||$=='4'||$=='5'"` // must be one of actions.DataScope{All,Custom,Dept,DeptTree,Self}; PRD 006 F14/H2
|
||||
SysMenu []models.SysMenu `form:"sysMenu"`
|
||||
MenuIds []int `form:"menuIds"`
|
||||
SysDept []models.SysDept `form:"sysDept"`
|
||||
DeptIds []int `form:"deptIds"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysRoleUpdateReq) Generate(model *models.SysRole) {
|
||||
if s.RoleId != 0 {
|
||||
model.RoleId = s.RoleId
|
||||
}
|
||||
model.RoleName = s.RoleName
|
||||
model.Status = s.Status
|
||||
model.RoleKey = s.RoleKey
|
||||
model.RoleSort = s.RoleSort
|
||||
model.Flag = s.Flag
|
||||
model.Remark = s.Remark
|
||||
model.Admin = s.Admin
|
||||
model.DataScope = s.DataScope
|
||||
model.SysMenu = &s.SysMenu
|
||||
model.SysDept = s.SysDept
|
||||
}
|
||||
|
||||
func (s *SysRoleUpdateReq) GetId() interface{} {
|
||||
return s.RoleId
|
||||
}
|
||||
|
||||
type UpdateStatusReq struct {
|
||||
RoleId int `form:"roleId" comment:"角色编码"` // 角色编码
|
||||
Status string `form:"status" comment:"状态"` // 状态
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *UpdateStatusReq) Generate(model *models.SysRole) {
|
||||
if s.RoleId != 0 {
|
||||
model.RoleId = s.RoleId
|
||||
}
|
||||
model.Status = s.Status
|
||||
}
|
||||
|
||||
func (s *UpdateStatusReq) GetId() interface{} {
|
||||
return s.RoleId
|
||||
}
|
||||
|
||||
type SysRoleByName struct {
|
||||
RoleName string `form:"role"` // 角色编码
|
||||
}
|
||||
|
||||
type SysRoleGetReq struct {
|
||||
Id int `uri:"id"`
|
||||
}
|
||||
|
||||
func (s *SysRoleGetReq) GetId() interface{} {
|
||||
return s.Id
|
||||
}
|
||||
|
||||
type SysRoleDeleteReq struct {
|
||||
Ids []int `json:"ids"`
|
||||
}
|
||||
|
||||
func (s *SysRoleDeleteReq) GetId() interface{} {
|
||||
return s.Ids
|
||||
}
|
||||
|
||||
// RoleDataScopeReq 角色数据权限修改
|
||||
type RoleDataScopeReq struct {
|
||||
RoleId int `json:"roleId" binding:"required"`
|
||||
DataScope string `json:"dataScope" binding:"required" vd:"$=='1'||$=='2'||$=='3'||$=='4'||$=='5'"` // must be one of actions.DataScope{All,Custom,Dept,DeptTree,Self}; PRD 006 F14/H2
|
||||
DeptIds []int `json:"deptIds"`
|
||||
}
|
||||
|
||||
func (s *RoleDataScopeReq) Generate(model *models.SysRole) {
|
||||
if s.RoleId != 0 {
|
||||
model.RoleId = s.RoleId
|
||||
}
|
||||
model.DataScope = s.DataScope
|
||||
model.DeptIds = s.DeptIds
|
||||
}
|
||||
|
||||
type DeptIdList struct {
|
||||
DeptId int `json:"DeptId"`
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
vd "github.com/bytedance/go-tagexpr/v2/validator"
|
||||
)
|
||||
|
||||
// api.Bind calls vd.Validate unconditionally on every request, regardless of
|
||||
// which binding stage ran, so a vd tag on DataScope is enough to reject
|
||||
// anything actions.Permission's fail-closed default would otherwise have to
|
||||
// deal with. PRD 006 F14/H2 named this the real trigger for the default
|
||||
// branch: SysRoleInsertReq.DataScope had no validation at all, so leaving
|
||||
// dataScope out of a create-role request wrote an empty string straight to
|
||||
// sys_role.
|
||||
func TestDataScopeRejectsWhatPermissionCannotRecognize(t *testing.T) {
|
||||
invalid := []string{"", "0", "6", "all", " 1", "1 "}
|
||||
valid := []string{"1", "2", "3", "4", "5"}
|
||||
|
||||
t.Run("SysRoleInsertReq", func(t *testing.T) {
|
||||
for _, s := range invalid {
|
||||
req := SysRoleInsertReq{RoleName: "r", RoleKey: "r", DataScope: s}
|
||||
if err := vd.Validate(&req); err == nil {
|
||||
t.Errorf("DataScope %q was accepted, want rejected", s)
|
||||
}
|
||||
}
|
||||
for _, s := range valid {
|
||||
req := SysRoleInsertReq{RoleName: "r", RoleKey: "r", DataScope: s}
|
||||
if err := vd.Validate(&req); err != nil {
|
||||
t.Errorf("DataScope %q was rejected: %v", s, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SysRoleUpdateReq", func(t *testing.T) {
|
||||
for _, s := range invalid {
|
||||
req := SysRoleUpdateReq{RoleName: "r", RoleKey: "r", DataScope: s}
|
||||
if err := vd.Validate(&req); err == nil {
|
||||
t.Errorf("DataScope %q was accepted, want rejected", s)
|
||||
}
|
||||
}
|
||||
for _, s := range valid {
|
||||
req := SysRoleUpdateReq{RoleName: "r", RoleKey: "r", DataScope: s}
|
||||
if err := vd.Validate(&req); err != nil {
|
||||
t.Errorf("DataScope %q was rejected: %v", s, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("RoleDataScopeReq", func(t *testing.T) {
|
||||
for _, s := range invalid {
|
||||
req := RoleDataScopeReq{RoleId: 1, DataScope: s}
|
||||
if err := vd.Validate(&req); err == nil {
|
||||
t.Errorf("DataScope %q was accepted, want rejected", s)
|
||||
}
|
||||
}
|
||||
for _, s := range valid {
|
||||
req := RoleDataScopeReq{RoleId: 1, DataScope: s}
|
||||
if err := vd.Validate(&req); err != nil {
|
||||
t.Errorf("DataScope %q was rejected: %v", s, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/common/dto"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
type SysUserGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
UserId int `form:"userId" search:"type:exact;column:user_id;table:sys_user" comment:"用户ID"`
|
||||
Username string `form:"username" search:"type:contains;column:username;table:sys_user" comment:"用户名"`
|
||||
NickName string `form:"nickName" search:"type:contains;column:nick_name;table:sys_user" comment:"昵称"`
|
||||
Phone string `form:"phone" search:"type:contains;column:phone;table:sys_user" comment:"手机号"`
|
||||
RoleId string `form:"roleId" search:"type:exact;column:role_id;table:sys_user" comment:"角色ID"`
|
||||
Sex string `form:"sex" search:"type:exact;column:sex;table:sys_user" comment:"性别"`
|
||||
Email string `form:"email" search:"type:contains;column:email;table:sys_user" comment:"邮箱"`
|
||||
PostId string `form:"postId" search:"type:exact;column:post_id;table:sys_user" comment:"岗位"`
|
||||
Status string `form:"status" search:"type:exact;column:status;table:sys_user" comment:"状态"`
|
||||
DeptJoin `search:"type:left;on:dept_id:dept_id;table:sys_user;join:sys_dept"`
|
||||
SysUserOrder
|
||||
}
|
||||
|
||||
type SysUserOrder struct {
|
||||
UserIdOrder string `search:"type:order;column:user_id;table:sys_user" form:"userIdOrder"`
|
||||
UsernameOrder string `search:"type:order;column:username;table:sys_user" form:"usernameOrder"`
|
||||
StatusOrder string `search:"type:order;column:status;table:sys_user" form:"statusOrder"`
|
||||
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_user" form:"createdAtOrder"`
|
||||
}
|
||||
|
||||
type DeptJoin struct {
|
||||
DeptId string `search:"type:contains;column:dept_path;table:sys_dept" form:"deptId"`
|
||||
}
|
||||
|
||||
func (m *SysUserGetPageReq) GetNeedSearch() interface{} {
|
||||
return *m
|
||||
}
|
||||
|
||||
type ResetSysUserPwdReq struct {
|
||||
UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID
|
||||
Password string `json:"password" comment:"密码" vd:"len($)>0"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *ResetSysUserPwdReq) GetId() interface{} {
|
||||
return s.UserId
|
||||
}
|
||||
|
||||
func (s *ResetSysUserPwdReq) Generate(model *models.SysUser) {
|
||||
if s.UserId != 0 {
|
||||
model.UserId = s.UserId
|
||||
}
|
||||
model.Password = s.Password
|
||||
}
|
||||
|
||||
type UpdateSysUserAvatarReq struct {
|
||||
UserId int `json:"userId" comment:"用户ID" vd:"len($)>0"` // 用户ID
|
||||
Avatar string `json:"avatar" comment:"头像" vd:"len($)>0"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *UpdateSysUserAvatarReq) GetId() interface{} {
|
||||
return s.UserId
|
||||
}
|
||||
|
||||
func (s *UpdateSysUserAvatarReq) Generate(model *models.SysUser) {
|
||||
if s.UserId != 0 {
|
||||
model.UserId = s.UserId
|
||||
}
|
||||
model.Avatar = s.Avatar
|
||||
}
|
||||
|
||||
type UpdateSysUserStatusReq struct {
|
||||
UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID
|
||||
Status string `json:"status" comment:"状态" vd:"len($)>0"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *UpdateSysUserStatusReq) GetId() interface{} {
|
||||
return s.UserId
|
||||
}
|
||||
|
||||
func (s *UpdateSysUserStatusReq) Generate(model *models.SysUser) {
|
||||
if s.UserId != 0 {
|
||||
model.UserId = s.UserId
|
||||
}
|
||||
model.Status = s.Status
|
||||
}
|
||||
|
||||
type SysUserInsertReq struct {
|
||||
UserId int `json:"userId" comment:"用户ID"` // 用户ID
|
||||
Username string `json:"username" comment:"用户名" vd:"len($)>0"`
|
||||
Password string `json:"password" comment:"密码"`
|
||||
NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"`
|
||||
Phone string `json:"phone" comment:"手机号" vd:"len($)>0"`
|
||||
RoleId int `json:"roleId" comment:"角色ID"`
|
||||
Avatar string `json:"avatar" comment:"头像"`
|
||||
Sex string `json:"sex" comment:"性别"`
|
||||
Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"`
|
||||
DeptId int `json:"deptId" comment:"部门" vd:"$>0"`
|
||||
PostId int `json:"postId" comment:"岗位"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
Status string `json:"status" comment:"状态" vd:"len($)>0" default:"1"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysUserInsertReq) Generate(model *models.SysUser) {
|
||||
if s.UserId != 0 {
|
||||
model.UserId = s.UserId
|
||||
}
|
||||
model.Username = s.Username
|
||||
model.Password = s.Password
|
||||
model.NickName = s.NickName
|
||||
model.Phone = s.Phone
|
||||
model.RoleId = s.RoleId
|
||||
model.Avatar = s.Avatar
|
||||
model.Sex = s.Sex
|
||||
model.Email = s.Email
|
||||
model.DeptId = s.DeptId
|
||||
model.PostId = s.PostId
|
||||
model.Remark = s.Remark
|
||||
model.Status = s.Status
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
|
||||
func (s *SysUserInsertReq) GetId() interface{} {
|
||||
return s.UserId
|
||||
}
|
||||
|
||||
type SysUserUpdateReq struct {
|
||||
UserId int `json:"userId" comment:"用户ID"` // 用户ID
|
||||
Username string `json:"username" comment:"用户名" vd:"len($)>0"`
|
||||
NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"`
|
||||
Phone string `json:"phone" comment:"手机号" vd:"len($)>0"`
|
||||
RoleId int `json:"roleId" comment:"角色ID"`
|
||||
Avatar string `json:"avatar" comment:"头像"`
|
||||
Sex string `json:"sex" comment:"性别"`
|
||||
Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"`
|
||||
DeptId int `json:"deptId" comment:"部门" vd:"$>0"`
|
||||
PostId int `json:"postId" comment:"岗位"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
Status string `json:"status" comment:"状态" default:"1"`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysUserUpdateReq) Generate(model *models.SysUser) {
|
||||
if s.UserId != 0 {
|
||||
model.UserId = s.UserId
|
||||
}
|
||||
model.Username = s.Username
|
||||
model.NickName = s.NickName
|
||||
model.Phone = s.Phone
|
||||
model.RoleId = s.RoleId
|
||||
model.Avatar = s.Avatar
|
||||
model.Sex = s.Sex
|
||||
model.Email = s.Email
|
||||
model.DeptId = s.DeptId
|
||||
model.PostId = s.PostId
|
||||
model.Remark = s.Remark
|
||||
model.Status = s.Status
|
||||
}
|
||||
|
||||
func (s *SysUserUpdateReq) GetId() interface{} {
|
||||
return s.UserId
|
||||
}
|
||||
|
||||
type SysUserById struct {
|
||||
dto.ObjectById
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
func (s *SysUserById) GetId() interface{} {
|
||||
if len(s.Ids) > 0 {
|
||||
s.Ids = append(s.Ids, s.Id)
|
||||
return s.Ids
|
||||
}
|
||||
return s.Id
|
||||
}
|
||||
|
||||
func (s *SysUserById) GenerateM() (common.ActiveRecord, error) {
|
||||
return &models.SysUser{}, nil
|
||||
}
|
||||
|
||||
// PassWord 密码
|
||||
type PassWord struct {
|
||||
NewPassword string `json:"newPassword" vd:"len($)>0"`
|
||||
OldPassword string `json:"oldPassword" vd:"len($)>0"`
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
contractmodels "github.com/go-admin-team/go-admin-core/v2/sdk/contract/models"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/contract/seed"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
)
|
||||
|
||||
// adminSeeder is go-admin's own implementation of seed.Seeder: it turns the
|
||||
// MenuSpec/ApiSpec values a third-party application asks for into rows
|
||||
// across the four tables a visible, working menu entry needs - sys_api,
|
||||
// sys_menu, sys_menu_api_rule, and sys_role_menu/casbin_rule - following the
|
||||
// same shape cmd/migrate/migration/version/1786700001000_demo_menu.go
|
||||
// already hand-writes for the host's own demo module.
|
||||
//
|
||||
// See go-admin-core's docs/contract.md, "Application-supplied menu and API
|
||||
// entries", for the requirements this satisfies, and the security note on
|
||||
// seed.Seeder for what this boundary does and does not protect against: an
|
||||
// application already holds the same *gorm.DB this receives and could write
|
||||
// sys_menu/sys_api/casbin_rule directly, bypassing this entirely.
|
||||
type adminSeeder struct{}
|
||||
|
||||
func init() {
|
||||
seed.RegisterSeeder(adminSeeder{})
|
||||
}
|
||||
|
||||
// adminRoleKey is the role every seeded menu is granted to. This mirrors
|
||||
// 1786700001000_demo_menu.go's own convention rather than inventing a
|
||||
// second one: MenuSpec carries no "which roles should see this" field for a
|
||||
// Seeder to consult instead, and admin is the one role guaranteed to exist
|
||||
// once the framework's own seed data has run.
|
||||
const adminRoleKey = "admin"
|
||||
|
||||
// menuSortRange is what sys_menu.sort's column type actually holds.
|
||||
//
|
||||
// sort is `gorm:"size:4"`, which MySQL builds as a tinyint (-128..127);
|
||||
// sqlite ignores the width and accepts anything, so this only ever surfaces
|
||||
// on a real install, mid-migration, as Error 1264 - by which point the
|
||||
// migration has already run other, non-transactional DDL that will not be
|
||||
// retried. tools/checksilent's menu-sort-overflow check catches this for
|
||||
// every MenuSpec-shaped literal committed to this repository, but it walks
|
||||
// the repository's own source tree: a third-party application living in the
|
||||
// module cache is invisible to it. This is the equivalent check for that
|
||||
// application, run when its migration actually calls SeedMenus rather than
|
||||
// never.
|
||||
const (
|
||||
menuSortMin = -128
|
||||
menuSortMax = 127
|
||||
)
|
||||
|
||||
func (adminSeeder) SeedMenus(tx *gorm.DB, appCode string, menus []seed.MenuSpec, apis []seed.ApiSpec) error {
|
||||
apiRows, err := seedApis(tx, appCode, apis)
|
||||
if err != nil {
|
||||
return fmt.Errorf("seed: app %q: apis: %w", appCode, err)
|
||||
}
|
||||
|
||||
menuIDs, err := seedMenuTree(tx, appCode, menus, apiRows)
|
||||
if err != nil {
|
||||
return fmt.Errorf("seed: app %q: menus: %w", appCode, err)
|
||||
}
|
||||
|
||||
// Not `len(menuIDs) == 0`: grantToAdminRole grants two independent
|
||||
// things, and an application is free to register apis without menus -
|
||||
// endpoints another service calls, or a UI mounted somewhere else.
|
||||
// Skipping the whole call on an empty menu list wrote the sys_api rows
|
||||
// and then no casbin rule for them, so those endpoints were denied to
|
||||
// everyone, admin included, with a migration that reported success.
|
||||
if len(menuIDs) == 0 && len(apiRows) == 0 {
|
||||
return nil
|
||||
}
|
||||
return grantToAdminRole(tx, menuIDs, apiRows)
|
||||
}
|
||||
|
||||
// seedApis writes one sys_api row per ApiSpec and returns them keyed by
|
||||
// ApiSpec.Code, so seedMenuTree can resolve a MenuSpec's ApiCodes into the
|
||||
// rows sys_menu_api_rule needs to reference.
|
||||
//
|
||||
// sys_api.id is left to autoincrement rather than assigned by the caller,
|
||||
// unlike 1786700001000_demo_menu.go's hand-picked ids: that migration is
|
||||
// the one file tools/checksilent's menu-id-collision check can see, because
|
||||
// it lives in this repository; nothing plays that role for a third-party
|
||||
// application's ids in the module cache. Never accepting a caller-chosen id
|
||||
// here removes the collision this Seeder has no way to detect instead of
|
||||
// trying to detect it after the fact.
|
||||
func seedApis(tx *gorm.DB, appCode string, apis []seed.ApiSpec) (map[string]models.SysApi, error) {
|
||||
seen := make(map[string]bool, len(apis))
|
||||
rows := make(map[string]models.SysApi, len(apis))
|
||||
for _, a := range apis {
|
||||
if a.Code == "" {
|
||||
return nil, errors.New("ApiSpec.Code must not be empty")
|
||||
}
|
||||
if seen[a.Code] {
|
||||
return nil, fmt.Errorf("duplicate ApiSpec.Code %q", a.Code)
|
||||
}
|
||||
seen[a.Code] = true
|
||||
|
||||
row := models.SysApi{
|
||||
Handle: a.Handle,
|
||||
Title: a.Title,
|
||||
Path: a.Path,
|
||||
Action: a.Method,
|
||||
Type: "SYS",
|
||||
AppCode: appCode,
|
||||
}
|
||||
if err := tx.Create(&row).Error; err != nil {
|
||||
return nil, fmt.Errorf("api %q: %w", a.Code, err)
|
||||
}
|
||||
rows[a.Code] = row
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// seedMenuTree writes one sys_menu row per MenuSpec, resolving Parent/Code
|
||||
// references into parent_id/paths, and returns every menu id created so the
|
||||
// caller can grant them to a role.
|
||||
//
|
||||
// Specs do not have to be given in parent-before-child order: this makes
|
||||
// repeated passes over the remaining specs, creating whichever ones have
|
||||
// their Parent (if any) already created, until every spec is placed. A
|
||||
// spec whose Parent never resolves - naming a Code missing from this call,
|
||||
// or only reachable through a cycle - stops making progress and is reported
|
||||
// rather than looping forever.
|
||||
func seedMenuTree(tx *gorm.DB, appCode string, specs []seed.MenuSpec, apiRows map[string]models.SysApi) ([]int, error) {
|
||||
byCode := make(map[string]seed.MenuSpec, len(specs))
|
||||
for _, s := range specs {
|
||||
if s.Code == "" {
|
||||
return nil, errors.New("MenuSpec.Code must not be empty")
|
||||
}
|
||||
if _, dup := byCode[s.Code]; dup {
|
||||
return nil, fmt.Errorf("duplicate MenuSpec.Code %q", s.Code)
|
||||
}
|
||||
if err := validateMenuSpec(s); err != nil {
|
||||
return nil, fmt.Errorf("%q: %w", s.Code, err)
|
||||
}
|
||||
byCode[s.Code] = s
|
||||
}
|
||||
|
||||
created := make(map[string]models.SysMenu, len(specs))
|
||||
ids := make([]int, 0, len(specs))
|
||||
|
||||
for len(created) < len(specs) {
|
||||
progressed := false
|
||||
for _, s := range specs {
|
||||
if _, done := created[s.Code]; done {
|
||||
continue
|
||||
}
|
||||
|
||||
var parentRow models.SysMenu
|
||||
if s.Parent != "" {
|
||||
parent, ok := created[s.Parent]
|
||||
if !ok {
|
||||
if _, exists := byCode[s.Parent]; !exists {
|
||||
return nil, fmt.Errorf("%q: Parent %q is not a Code in this call", s.Code, s.Parent)
|
||||
}
|
||||
continue // s.Parent exists but has not been created yet; retry next pass
|
||||
}
|
||||
parentRow = parent
|
||||
}
|
||||
|
||||
row := models.SysMenu{
|
||||
MenuName: menuName(appCode, s.Code),
|
||||
Title: s.Title,
|
||||
Icon: s.Icon,
|
||||
Path: s.Path,
|
||||
MenuType: s.Kind,
|
||||
Permission: s.Permission,
|
||||
ParentId: parentRow.MenuId,
|
||||
Component: s.Component,
|
||||
Sort: s.Sort,
|
||||
// Visible "0" is shown, not hidden - the same defaults
|
||||
// 1786700001000_demo_menu.go seeds its own menu with. A
|
||||
// freshly installed application's menu should not need an
|
||||
// administrator to first find and unhide it.
|
||||
Visible: "0",
|
||||
IsFrame: "1",
|
||||
AppCode: appCode,
|
||||
}
|
||||
for _, code := range s.ApiCodes {
|
||||
api, ok := apiRows[code]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q: ApiCodes references %q, which is not an ApiSpec.Code in this call", s.Code, code)
|
||||
}
|
||||
// The full row, not just {Id: api.Id}: gorm's many2many
|
||||
// association save upserts an associated row whose primary
|
||||
// key is already set, so a stub carrying only Id would
|
||||
// overwrite every other column of an sys_api row this same
|
||||
// call just wrote with zero values.
|
||||
row.SysApi = append(row.SysApi, api)
|
||||
}
|
||||
|
||||
if err := tx.Create(&row).Error; err != nil {
|
||||
return nil, fmt.Errorf("%q: %w", s.Code, err)
|
||||
}
|
||||
|
||||
// paths is a materialized path from the root ("/0"), built from
|
||||
// ids that only exist once the row above is created - the same
|
||||
// two-step create-then-update 1786700001000_demo_menu.go's
|
||||
// hand-assigned ids let it do in one literal, sequenced here
|
||||
// instead.
|
||||
if s.Parent == "" {
|
||||
row.Paths = "/0/" + strconv.Itoa(row.MenuId)
|
||||
} else {
|
||||
row.Paths = parentRow.Paths + "/" + strconv.Itoa(row.MenuId)
|
||||
}
|
||||
if err := tx.Model(&models.SysMenu{}).Where("menu_id = ?", row.MenuId).
|
||||
Update("paths", row.Paths).Error; err != nil {
|
||||
return nil, fmt.Errorf("%q: writing paths: %w", s.Code, err)
|
||||
}
|
||||
|
||||
created[s.Code] = row
|
||||
ids = append(ids, row.MenuId)
|
||||
progressed = true
|
||||
}
|
||||
if !progressed {
|
||||
return nil, fmt.Errorf("unresolved Parent reference(s) among %d remaining spec(s); check for a cycle", len(specs)-len(created))
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// validateMenuSpec rejects the malformed input tools/checksilent's
|
||||
// menu-sort-overflow and Kind-adjacent checks would catch for an in-tree
|
||||
// seed but cannot for a third-party application's - see menuSortRange's doc
|
||||
// comment.
|
||||
func validateMenuSpec(s seed.MenuSpec) error {
|
||||
switch s.Kind {
|
||||
case contractmodels.Directory, contractmodels.Menu, contractmodels.Button:
|
||||
default:
|
||||
return fmt.Errorf("Kind %q is not one of Directory/Menu/Button", s.Kind)
|
||||
}
|
||||
if s.Sort < menuSortMin || s.Sort > menuSortMax {
|
||||
return fmt.Errorf("Sort %d does not fit sys_menu.sort's tinyint column (%d..%d)", s.Sort, menuSortMin, menuSortMax)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// menuName synthesizes sys_menu.menu_name from appCode and the spec's Code,
|
||||
// since MenuSpec carries no field of its own for it - contract/seed's
|
||||
// package doc says a MenuSpec is what rendering a menu and checking a
|
||||
// button permission need, not a mirror of sys_menu's columns.
|
||||
//
|
||||
// PascalCasing both and concatenating them, rather than using Code alone,
|
||||
// is what keeps two applications that both picked the plain word "list" as
|
||||
// a Code from producing the identical menu_name: the frontend's keep-alive
|
||||
// cache matches a route by this exact string, not by (appCode, Code), so a
|
||||
// collision there is a UI bug, not a database error, and nothing else here
|
||||
// would ever surface it.
|
||||
func menuName(appCode, code string) string {
|
||||
return pascalCase(appCode) + pascalCase(code)
|
||||
}
|
||||
|
||||
func pascalCase(s string) string {
|
||||
var b strings.Builder
|
||||
for _, part := range strings.FieldsFunc(s, func(r rune) bool { return r == '-' || r == '_' }) {
|
||||
b.WriteString(strings.ToUpper(part[:1]))
|
||||
b.WriteString(part[1:])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// grantToAdminRole is sys_role_menu and casbin_rule: the two tables
|
||||
// go-admin-core's contract.md requires alongside sys_menu/sys_api, without
|
||||
// which a seeded menu is invisible to every role and its apis are
|
||||
// authorized for no one.
|
||||
//
|
||||
// It follows 1786700001000_demo_menu.go's exact pattern, including
|
||||
// tolerating a missing admin role: a database that has not yet run the
|
||||
// framework's own seed data (config/db.sql, inside 1599190683659_tables.go)
|
||||
// has nothing to grant to yet, and namespacedKey's ordering guarantee - every
|
||||
// framework migration sorts before every app-prefixed one - means that
|
||||
// should not happen in practice, but failing this call over it would be
|
||||
// worse than a menu with no grant yet.
|
||||
func grantToAdminRole(tx *gorm.DB, menuIDs []int, apiRows map[string]models.SysApi) error {
|
||||
var role models.SysRole
|
||||
if err := tx.Where("role_key = ?", adminRoleKey).First(&role).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range menuIDs {
|
||||
if err := tx.Exec(
|
||||
"INSERT INTO sys_role_menu (role_id, menu_id) SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu WHERE role_id = ? AND menu_id = ?)",
|
||||
role.RoleId, id, role.RoleId, id,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range apiRows {
|
||||
if err := tx.Exec(
|
||||
"INSERT INTO casbin_rule (ptype, v0, v1, v2, v3, v4, v5) SELECT 'p', ?, ?, ?, '', '', '' WHERE NOT EXISTS (SELECT 1 FROM casbin_rule WHERE ptype='p' AND v0=? AND v1=? AND v2=?)",
|
||||
role.RoleKey, a.Path, a.Action, role.RoleKey, a.Path, a.Action,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
contractmodels "github.com/go-admin-team/go-admin-core/v2/sdk/contract/models"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/contract/seed"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
)
|
||||
|
||||
// newSeedTestDB builds the tables adminSeeder.SeedMenus writes to. sys_menu,
|
||||
// sys_api, sys_role and sys_role_menu (GORM's own join table for
|
||||
// SysRole.SysMenu) come from AutoMigrate; casbin_rule does not have a GORM
|
||||
// model anywhere in this codebase - see 1786700001000_demo_menu.go's own
|
||||
// comment on why models.CasbinRule (-> sys_casbin_rule) is the wrong table -
|
||||
// so it is created directly, matching the columns grantToAdminRole's INSERT
|
||||
// addresses.
|
||||
func newSeedTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.SysMenu{}, &models.SysApi{}, &models.SysRole{}); err != nil {
|
||||
t.Fatalf("automigrate: %v", err)
|
||||
}
|
||||
if err := db.Exec(`CREATE TABLE casbin_rule (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ptype TEXT, v0 TEXT, v1 TEXT, v2 TEXT, v3 TEXT, v4 TEXT, v5 TEXT
|
||||
)`).Error; err != nil {
|
||||
t.Fatalf("create casbin_rule: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func seedAdminRole(t *testing.T, db *gorm.DB) models.SysRole {
|
||||
t.Helper()
|
||||
role := models.SysRole{RoleName: "Administrator", RoleKey: adminRoleKey}
|
||||
if err := db.Create(&role).Error; err != nil {
|
||||
t.Fatalf("seed admin role: %v", err)
|
||||
}
|
||||
return role
|
||||
}
|
||||
|
||||
// This is the acceptance case go-admin-core's docs/contract.md requires: one
|
||||
// SeedMenus call populates all four tables a visible, working menu entry
|
||||
// needs, every row tagged with the appCode it was called with, and the
|
||||
// parent/child tree resolved into sys_menu's parent_id/paths.
|
||||
func TestSeedMenusPopulatesAllFourTables(t *testing.T) {
|
||||
db := newSeedTestDB(t)
|
||||
seedAdminRole(t, db)
|
||||
|
||||
menus := []seed.MenuSpec{
|
||||
{Code: "dir", Kind: contractmodels.Directory, Title: "Order Example", Path: "/apps/order", Component: "Layout", Sort: 10},
|
||||
{Code: "list", Parent: "dir", Kind: contractmodels.Menu, Title: "Orders", Path: "list", Component: "apps/order/order/index", Sort: 1, ApiCodes: []string{"list"}},
|
||||
{Code: "btn-create", Parent: "list", Kind: contractmodels.Button, Title: "Create", Permission: "order:order:create", Sort: 1},
|
||||
}
|
||||
apis := []seed.ApiSpec{
|
||||
{Code: "list", Title: "Order list", Path: "/api/v1/order", Method: "GET", Handle: "apis.Order.GetPage-fm"},
|
||||
}
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return adminSeeder{}.SeedMenus(tx, "order", menus, apis)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SeedMenus: %v", err)
|
||||
}
|
||||
|
||||
var apiRows []models.SysApi
|
||||
if err := db.Find(&apiRows).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(apiRows) != 1 || apiRows[0].AppCode != "order" || apiRows[0].Path != "/api/v1/order" {
|
||||
t.Fatalf("sys_api = %+v", apiRows)
|
||||
}
|
||||
|
||||
var menuRows []models.SysMenu
|
||||
if err := db.Order("sort").Find(&menuRows).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(menuRows) != 3 {
|
||||
t.Fatalf("sys_menu has %d rows, want 3: %+v", len(menuRows), menuRows)
|
||||
}
|
||||
byName := map[string]models.SysMenu{}
|
||||
for _, m := range menuRows {
|
||||
if m.AppCode != "order" {
|
||||
t.Errorf("menu %q app_code = %q, want order", m.MenuName, m.AppCode)
|
||||
}
|
||||
byName[m.MenuName] = m
|
||||
}
|
||||
dir, ok := byName[menuName("order", "dir")]
|
||||
if !ok || dir.ParentId != 0 || dir.Paths != "/0/"+strconv.Itoa(dir.MenuId) {
|
||||
t.Fatalf("dir menu = %+v", dir)
|
||||
}
|
||||
list, ok := byName[menuName("order", "list")]
|
||||
if !ok || list.ParentId != dir.MenuId || list.Paths != dir.Paths+"/"+strconv.Itoa(list.MenuId) {
|
||||
t.Fatalf("list menu = %+v (dir=%+v)", list, dir)
|
||||
}
|
||||
btn, ok := byName[menuName("order", "btn-create")]
|
||||
if !ok || btn.ParentId != list.MenuId {
|
||||
t.Fatalf("btn menu = %+v (list=%+v)", btn, list)
|
||||
}
|
||||
|
||||
// sys_menu_api_rule: gorm's own many2many join table for SysMenu.SysApi.
|
||||
var joinCount int64
|
||||
if err := db.Table("sys_menu_api_rule").
|
||||
Where("sys_menu_menu_id = ? AND sys_api_id = ?", list.MenuId, apiRows[0].Id).
|
||||
Count(&joinCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if joinCount != 1 {
|
||||
t.Errorf("sys_menu_api_rule has %d row(s) linking list to its api, want 1", joinCount)
|
||||
}
|
||||
|
||||
// sys_role_menu: every seeded menu granted to the admin role.
|
||||
var roleMenuCount int64
|
||||
if err := db.Table("sys_role_menu").Count(&roleMenuCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if roleMenuCount != 3 {
|
||||
t.Errorf("sys_role_menu has %d row(s), want 3 (one per seeded menu)", roleMenuCount)
|
||||
}
|
||||
|
||||
// casbin_rule: the api's path/method granted to the admin role.
|
||||
var casbinCount int64
|
||||
if err := db.Table("casbin_rule").
|
||||
Where("ptype = 'p' AND v0 = ? AND v1 = ? AND v2 = ?", adminRoleKey, "/api/v1/order", "GET").
|
||||
Count(&casbinCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if casbinCount != 1 {
|
||||
t.Errorf("casbin_rule has %d matching row(s), want 1", casbinCount)
|
||||
}
|
||||
}
|
||||
|
||||
// A database that has not run the framework's own seed data yet (no admin
|
||||
// role) must not fail SeedMenus - 1786700001000_demo_menu.go tolerates
|
||||
// exactly the same condition for the host's own demo module.
|
||||
func TestSeedMenusToleratesMissingAdminRole(t *testing.T) {
|
||||
db := newSeedTestDB(t)
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return adminSeeder{}.SeedMenus(tx, "order", []seed.MenuSpec{
|
||||
{Code: "dir", Kind: contractmodels.Directory, Title: "Order"},
|
||||
}, nil)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SeedMenus: %v", err)
|
||||
}
|
||||
|
||||
var roleMenuCount int64
|
||||
if err := db.Table("sys_role_menu").Count(&roleMenuCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if roleMenuCount != 0 {
|
||||
t.Errorf("sys_role_menu has %d row(s) with no role to grant to", roleMenuCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedMenusRejectsMalformedSpecs(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
menus []seed.MenuSpec
|
||||
apis []seed.ApiSpec
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "duplicate menu code",
|
||||
menus: []seed.MenuSpec{{Code: "a", Kind: contractmodels.Directory}, {Code: "a", Kind: contractmodels.Directory}},
|
||||
want: `duplicate MenuSpec.Code "a"`,
|
||||
},
|
||||
{
|
||||
name: "unresolved parent",
|
||||
menus: []seed.MenuSpec{{Code: "a", Parent: "missing", Kind: contractmodels.Menu}},
|
||||
want: `Parent "missing" is not a Code in this call`,
|
||||
},
|
||||
{
|
||||
name: "unresolved api code",
|
||||
menus: []seed.MenuSpec{{Code: "a", Kind: contractmodels.Menu, ApiCodes: []string{"missing"}}},
|
||||
want: `ApiCodes references "missing"`,
|
||||
},
|
||||
{
|
||||
name: "unknown kind",
|
||||
menus: []seed.MenuSpec{{Code: "a", Kind: "X"}},
|
||||
want: `Kind "X" is not one of Directory/Menu/Button`,
|
||||
},
|
||||
{
|
||||
name: "sort overflows a tinyint",
|
||||
menus: []seed.MenuSpec{{Code: "a", Kind: contractmodels.Directory, Sort: 900}},
|
||||
want: `Sort 900 does not fit sys_menu.sort's tinyint column`,
|
||||
},
|
||||
{
|
||||
name: "duplicate api code",
|
||||
apis: []seed.ApiSpec{{Code: "x"}, {Code: "x"}},
|
||||
want: `duplicate ApiSpec.Code "x"`,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
db := newSeedTestDB(t)
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return adminSeeder{}.SeedMenus(tx, "order", tc.menus, tc.apis)
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("err = %v, want it to contain %q", err, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSeederIsRegistered pins the registration itself, not the behaviour.
|
||||
//
|
||||
// Every other test here calls adminSeeder{}.SeedMenus directly, which proves
|
||||
// the implementation is right and proves nothing about whether anything ever
|
||||
// reaches it: delete the RegisterSeeder call in init() and they all stay
|
||||
// green, while a real migrate fails with ErrNoSeeder and no menu is written.
|
||||
// Going through the package-level SeedMenus is what closes that gap - it is
|
||||
// the door an application actually knocks on.
|
||||
func TestSeederIsRegistered(t *testing.T) {
|
||||
db := newSeedTestDB(t)
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return seed.SeedMenus(tx, "probe", []seed.MenuSpec{{
|
||||
Code: "root", Kind: contractmodels.Directory, Title: "Probe", Sort: 1,
|
||||
}}, nil)
|
||||
})
|
||||
if errors.Is(err, seed.ErrNoSeeder) {
|
||||
t.Fatal("no Seeder is registered: an application's SeedMenus would write no menu at all")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("SeedMenus through the package-level entry point: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// An application is free to register apis with no menus at all - endpoints
|
||||
// another service calls, or a UI mounted somewhere else. Skipping
|
||||
// grantToAdminRole on an empty menu list wrote the sys_api rows and then no
|
||||
// casbin rule for them, so every one of those endpoints was denied to
|
||||
// everyone including admin, from a migration that reported success.
|
||||
func TestSeedMenusGrantsApisWhenThereAreNoMenus(t *testing.T) {
|
||||
db := newSeedTestDB(t)
|
||||
role := seedAdminRole(t, db)
|
||||
|
||||
apis := []seed.ApiSpec{
|
||||
{Code: "hook", Title: "Inbound hook", Path: "/api/v1/hook", Method: "POST", Handle: "hook.Receive"},
|
||||
{Code: "sync", Title: "Sync", Path: "/api/v1/sync", Method: "GET", Handle: "hook.Sync"},
|
||||
}
|
||||
if err := (adminSeeder{}).SeedMenus(db, "hooks", nil, apis); err != nil {
|
||||
t.Fatalf("SeedMenus: %v", err)
|
||||
}
|
||||
|
||||
var apiCount int64
|
||||
db.Model(&models.SysApi{}).Where("app_code = ?", "hooks").Count(&apiCount)
|
||||
if apiCount != int64(len(apis)) {
|
||||
t.Fatalf("sys_api rows = %d, want %d", apiCount, len(apis))
|
||||
}
|
||||
|
||||
for _, a := range apis {
|
||||
var n int64
|
||||
db.Table("casbin_rule").
|
||||
Where("ptype = 'p' AND v0 = ? AND v1 = ? AND v2 = ?", role.RoleKey, a.Path, a.Method).
|
||||
Count(&n)
|
||||
if n != 1 {
|
||||
t.Errorf("casbin_rule for %s %s = %d rows, want 1: the endpoint is denied to admin", a.Method, a.Path, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The other half of the same guard: nothing registered at all must stay a
|
||||
// no-op rather than start touching sys_role_menu or casbin_rule.
|
||||
func TestSeedMenusWithNothingRegisteredWritesNothing(t *testing.T) {
|
||||
db := newSeedTestDB(t)
|
||||
seedAdminRole(t, db)
|
||||
|
||||
if err := (adminSeeder{}).SeedMenus(db, "empty", nil, nil); err != nil {
|
||||
t.Fatalf("SeedMenus: %v", err)
|
||||
}
|
||||
for _, table := range []string{"casbin_rule", "sys_role_menu"} {
|
||||
var n int64
|
||||
db.Table(table).Count(&n)
|
||||
if n != 0 {
|
||||
t.Errorf("%s has %d rows, want 0", table, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/runtime"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
"go-admin/common/actions"
|
||||
cDto "go-admin/common/dto"
|
||||
"go-admin/common/global"
|
||||
)
|
||||
|
||||
type SysApi struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取SysApi列表
|
||||
func (e *SysApi) GetPage(c *dto.SysApiGetPageReq, p *actions.DataPermission, list *[]models.SysApi, count *int64) error {
|
||||
var err error
|
||||
var data models.SysApi
|
||||
|
||||
orm := e.Orm.Debug().Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
actions.Permission(data.TableName(), p),
|
||||
)
|
||||
if c.Type != "" {
|
||||
qType := c.Type
|
||||
if qType == "暂无" {
|
||||
qType = ""
|
||||
}
|
||||
if global.Driver == "postgres" {
|
||||
orm = orm.Where("type = ?", qType)
|
||||
} else {
|
||||
orm = orm.Where("`type` = ?", qType)
|
||||
}
|
||||
|
||||
}
|
||||
err = orm.Find(list).Limit(-1).Offset(-1).
|
||||
Count(count).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysApiPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取SysApi对象with id
|
||||
func (e *SysApi) Get(d *dto.SysApiGetReq, p *actions.DataPermission, model *models.SysApi) *SysApi {
|
||||
var data models.SysApi
|
||||
err := e.Orm.Model(&data).
|
||||
Scopes(
|
||||
actions.Permission(data.TableName(), p),
|
||||
).
|
||||
FirstOrInit(model, d.GetId()).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
if model.Id == 0 {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("Service GetSysApi error: %s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Update 修改SysApi对象
|
||||
func (e *SysApi) Update(c *dto.SysApiUpdateReq, p *actions.DataPermission) error {
|
||||
var model = models.SysApi{}
|
||||
db := e.Orm.Scopes(
|
||||
actions.Permission(model.TableName(), p),
|
||||
).First(&model, c.GetId())
|
||||
if err := db.Error; err != nil {
|
||||
// First reports a row the data permission excluded exactly as it
|
||||
// reports one that does not exist, and the caller should not be able
|
||||
// to tell those apart either.
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("无权更新该数据")
|
||||
}
|
||||
e.Log.Errorf("Service UpdateSysApi error:%s", err)
|
||||
return err
|
||||
}
|
||||
c.Generate(&model)
|
||||
db = e.Orm.Save(&model)
|
||||
if err := db.Error; err != nil {
|
||||
e.Log.Errorf("Service UpdateSysApi error:%s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除SysApi
|
||||
func (e *SysApi) Remove(d *dto.SysApiDeleteReq, p *actions.DataPermission) error {
|
||||
var data models.SysApi
|
||||
|
||||
db := e.Orm.Model(&data).
|
||||
Scopes(
|
||||
actions.Permission(data.TableName(), p),
|
||||
).Delete(&data, d.GetId())
|
||||
if err := db.Error; err != nil {
|
||||
e.Log.Errorf("Service RemoveSysApi error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权删除该数据")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckStorageSysApi 创建SysApi对象
|
||||
func (e *SysApi) CheckStorageSysApi(c *[]runtime.Router) error {
|
||||
for _, v := range *c {
|
||||
err := e.Orm.Debug().Where(models.SysApi{Path: v.RelativePath, Action: v.HttpMethod}).
|
||||
Attrs(models.SysApi{Handle: v.Handler}).
|
||||
FirstOrCreate(&models.SysApi{}).Error
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Service CheckStorageSysApi error: %s \r\n ", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
"go-admin/common/actions"
|
||||
)
|
||||
|
||||
// An update the data permission excludes has to be refused, and refused in a
|
||||
// way that does not tell the caller whether the row exists. First reports both
|
||||
// cases the same way - no rows - so the message has to come from there rather
|
||||
// than from a RowsAffected check the error return has already skipped past.
|
||||
func TestSysApiUpdateRefusesARowOutsideTheDataPermission(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open("file:sysapi-perm?mode=memory&cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Skipf("sqlite unavailable: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.SysApi{}); err != nil {
|
||||
t.Skipf("automigrate: %v", err)
|
||||
}
|
||||
|
||||
prev := config.ApplicationConfig.EnableDP
|
||||
config.ApplicationConfig.EnableDP = true
|
||||
t.Cleanup(func() { config.ApplicationConfig.EnableDP = prev })
|
||||
|
||||
// Owned by user 1.
|
||||
row := models.SysApi{Handle: "h", Title: "t", Path: "/api/v1/probe", Type: "BUS", Action: "GET"}
|
||||
row.CreateBy = 1
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
e := &SysApi{Service: service.Service{Orm: db, Log: logger.NewHelper(logger.DefaultLogger)}}
|
||||
req := &dto.SysApiUpdateReq{Id: row.Id, Title: "changed"}
|
||||
|
||||
// User 2, scope 5: only rows they created.
|
||||
outsider := &actions.DataPermission{DataScope: "5", UserId: 2, DeptId: 1, RoleId: 2}
|
||||
err = e.Update(req, outsider)
|
||||
if err == nil {
|
||||
t.Fatal("the update was allowed on a row the data permission excludes")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "无权更新该数据") {
|
||||
t.Errorf("refused with %q, want the permission message; a raw database error tells the "+
|
||||
"caller the row exists", err)
|
||||
}
|
||||
|
||||
var after models.SysApi
|
||||
if err := db.First(&after, row.Id).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if after.Title != "t" {
|
||||
t.Errorf("the row was modified: title is now %q", after.Title)
|
||||
}
|
||||
|
||||
// The owner still gets through, so the scope is refusing rather than
|
||||
// everything failing.
|
||||
owner := &actions.DataPermission{DataScope: "5", UserId: 1, DeptId: 1, RoleId: 1}
|
||||
if err := e.Update(&dto.SysApiUpdateReq{Id: row.Id, Title: "by owner"}, owner); err != nil {
|
||||
t.Fatalf("the owner could not update their own row: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
type SysConfig struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取SysConfig列表
|
||||
func (e *SysConfig) GetPage(c *dto.SysConfigGetPageReq, list *[]models.SysConfig, count *int64) error {
|
||||
err := e.Orm.
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
).
|
||||
Find(list).Limit(-1).Offset(-1).
|
||||
Count(count).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取SysConfig对象
|
||||
func (e *SysConfig) Get(d *dto.SysConfigGetReq, model *models.SysConfig) error {
|
||||
err := e.Orm.
|
||||
FirstOrInit(model, d.GetId()).
|
||||
Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return err
|
||||
}
|
||||
if model.Id == 0 {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("Service GetSysApi error: %s", err)
|
||||
_ = e.AddError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert 创建SysConfig对象
|
||||
func (e *SysConfig) Insert(c *dto.SysConfigControl) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
c.Generate(&data)
|
||||
err = e.Orm.Create(&data).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service InsertSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 修改SysConfig对象
|
||||
func (e *SysConfig) Update(c *dto.SysConfigControl) error {
|
||||
var err error
|
||||
var model = models.SysConfig{}
|
||||
e.Orm.First(&model, c.GetId())
|
||||
c.Generate(&model)
|
||||
db := e.Orm.Save(&model)
|
||||
err = db.Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service UpdateSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权更新该数据")
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSysConfig 修改SysConfig对象
|
||||
func (e *SysConfig) SetSysConfig(c *[]dto.GetSetSysConfigReq) error {
|
||||
var err error
|
||||
for _, req := range *c {
|
||||
var model = models.SysConfig{}
|
||||
e.Orm.Where("config_key = ?", req.ConfigKey).First(&model)
|
||||
if model.Id != 0 {
|
||||
req.Generate(&model)
|
||||
db := e.Orm.Save(&model)
|
||||
err = db.Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service SetSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权更新该数据")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SysConfig) GetForSet(c *[]dto.GetSetSysConfigReq) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Find(c).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error {
|
||||
m := *c
|
||||
for _, req := range m {
|
||||
var data models.SysConfig
|
||||
if err := e.Orm.Where("config_key = ?", req.ConfigKey).
|
||||
First(&data).Error; err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
if data.ConfigValue != req.ConfigValue {
|
||||
data.ConfigValue = req.ConfigValue
|
||||
|
||||
if err := e.Orm.Save(&data).Error; err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除SysConfig
|
||||
func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
|
||||
db := e.Orm.Delete(&data, d.Ids)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Service RemoveSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
err = errors.New("无权删除该数据")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWithKey 根据Key获取SysConfig
|
||||
func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigByKEYForServiceResp) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
err = e.Orm.Table(data.TableName()).Where("config_key = ?", c.ConfigKey).First(resp).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("At Service GetSysConfigByKEY Error:%s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error {
|
||||
err := e.Orm.
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigByKey error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
type SysDept struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取SysDept列表
|
||||
//func (e *SysDept) GetPage(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error {
|
||||
// var err error
|
||||
// var data models.SysDept
|
||||
//
|
||||
// err = e.Orm.Model(&data).
|
||||
// Scopes(
|
||||
// cDto.MakeCondition(c.GetNeedSearch()),
|
||||
// ).
|
||||
// Find(list).Error
|
||||
// if err != nil {
|
||||
// e.Log.Errorf("db error:%s", err)
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Get 获取SysDept对象
|
||||
func (e *SysDept) Get(d *dto.SysDeptGetReq, model *models.SysDept) error {
|
||||
var err error
|
||||
var data models.SysDept
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
FirstOrInit(model, d.GetId()).
|
||||
Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return err
|
||||
}
|
||||
if model.DeptId == 0 {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("Service GetSysApi error: %s", err)
|
||||
_ = e.AddError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert 创建SysDept对象
|
||||
func (e *SysDept) Insert(c *dto.SysDeptInsertReq) error {
|
||||
var err error
|
||||
var data models.SysDept
|
||||
c.Generate(&data)
|
||||
tx := e.Orm.Debug().Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
err = tx.Create(&data).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
deptPath := pkg.IntToString(data.DeptId) + "/"
|
||||
if data.ParentId != 0 {
|
||||
var deptP models.SysDept
|
||||
tx.First(&deptP, data.ParentId)
|
||||
deptPath = deptP.DeptPath + deptPath
|
||||
} else {
|
||||
deptPath = "/0/" + deptPath
|
||||
}
|
||||
var mp = map[string]string{}
|
||||
mp["dept_path"] = deptPath
|
||||
if err = tx.Model(&data).Update("dept_path", deptPath).Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 修改SysDept对象
|
||||
func (e *SysDept) Update(c *dto.SysDeptUpdateReq) error {
|
||||
var err error
|
||||
var model = models.SysDept{}
|
||||
tx := e.Orm.Debug().Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
tx.First(&model, c.GetId())
|
||||
c.Generate(&model)
|
||||
|
||||
deptPath := pkg.IntToString(model.DeptId) + "/"
|
||||
if model.ParentId != 0 {
|
||||
var deptP models.SysDept
|
||||
tx.First(&deptP, model.ParentId)
|
||||
deptPath = deptP.DeptPath + deptPath
|
||||
} else {
|
||||
deptPath = "/0/" + deptPath
|
||||
}
|
||||
model.DeptPath = deptPath
|
||||
db := tx.Save(&model)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("UpdateSysDept error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权更新该数据")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除SysDept
|
||||
func (e *SysDept) Remove(d *dto.SysDeptDeleteReq) error {
|
||||
var err error
|
||||
var data models.SysDept
|
||||
|
||||
db := e.Orm.Model(&data).Delete(&data, d.GetId())
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
err = errors.New("无权删除该数据")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSysDeptList 获取组织数据
|
||||
func (e *SysDept) getList(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error {
|
||||
var err error
|
||||
var data models.SysDept
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeptTree 设置组织数据
|
||||
func (e *SysDept) SetDeptTree(c *dto.SysDeptGetPageReq) (m []dto.DeptLabel, err error) {
|
||||
var list []models.SysDept
|
||||
err = e.getList(c, &list)
|
||||
|
||||
m = make([]dto.DeptLabel, 0)
|
||||
for i := 0; i < len(list); i++ {
|
||||
if list[i].ParentId != 0 {
|
||||
continue
|
||||
}
|
||||
e := dto.DeptLabel{}
|
||||
e.Id = list[i].DeptId
|
||||
e.Label = list[i].DeptName
|
||||
deptsInfo := deptTreeCall(&list, e)
|
||||
|
||||
m = append(m, deptsInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Call 递归构造组织数据
|
||||
func deptTreeCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel {
|
||||
list := *deptList
|
||||
childrenList := make([]dto.DeptLabel, 0)
|
||||
for j := 0; j < len(list); j++ {
|
||||
if dept.Id != list[j].ParentId {
|
||||
continue
|
||||
}
|
||||
mi := dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}}
|
||||
ms := deptTreeCall(deptList, mi)
|
||||
childrenList = append(childrenList, ms)
|
||||
}
|
||||
dept.Children = childrenList
|
||||
return dept
|
||||
}
|
||||
|
||||
// SetDeptPage 设置dept页面数据
|
||||
func (e *SysDept) SetDeptPage(c *dto.SysDeptGetPageReq) (m []models.SysDept, err error) {
|
||||
var list []models.SysDept
|
||||
err = e.getList(c, &list)
|
||||
for i := 0; i < len(list); i++ {
|
||||
if list[i].ParentId != 0 {
|
||||
continue
|
||||
}
|
||||
info := e.deptPageCall(&list, list[i])
|
||||
m = append(m, info)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *SysDept) deptPageCall(deptlist *[]models.SysDept, menu models.SysDept) models.SysDept {
|
||||
list := *deptlist
|
||||
childrenList := make([]models.SysDept, 0)
|
||||
for j := 0; j < len(list); j++ {
|
||||
if menu.DeptId != list[j].ParentId {
|
||||
continue
|
||||
}
|
||||
mi := models.SysDept{}
|
||||
mi.DeptId = list[j].DeptId
|
||||
mi.ParentId = list[j].ParentId
|
||||
mi.DeptPath = list[j].DeptPath
|
||||
mi.DeptName = list[j].DeptName
|
||||
mi.Sort = list[j].Sort
|
||||
mi.Leader = list[j].Leader
|
||||
mi.Phone = list[j].Phone
|
||||
mi.Email = list[j].Email
|
||||
mi.Status = list[j].Status
|
||||
mi.CreatedAt = list[j].CreatedAt
|
||||
mi.Children = []models.SysDept{}
|
||||
ms := e.deptPageCall(deptlist, mi)
|
||||
childrenList = append(childrenList, ms)
|
||||
}
|
||||
menu.Children = childrenList
|
||||
return menu
|
||||
}
|
||||
|
||||
// GetWithRoleId 获取角色的部门ID集合
|
||||
func (e *SysDept) GetWithRoleId(roleId int) ([]int, error) {
|
||||
deptIds := make([]int, 0)
|
||||
deptList := make([]dto.DeptIdList, 0)
|
||||
if err := e.Orm.Table("sys_role_dept").
|
||||
Select("sys_role_dept.dept_id").
|
||||
Joins("LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id").
|
||||
Where("role_id = ? ", roleId).
|
||||
Where(" sys_role_dept.dept_id not in(select sys_dept.parent_id from sys_role_dept LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id where role_id =? )", roleId).
|
||||
Find(&deptList).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < len(deptList); i++ {
|
||||
deptIds = append(deptIds, deptList[i].DeptId)
|
||||
}
|
||||
return deptIds, nil
|
||||
}
|
||||
|
||||
func (e *SysDept) SetDeptLabel() (m []dto.DeptLabel, err error) {
|
||||
list := make([]models.SysDept, 0)
|
||||
err = e.Orm.Find(&list).Error
|
||||
if err != nil {
|
||||
log.Error("find dept list error, %s", err.Error())
|
||||
return
|
||||
}
|
||||
m = make([]dto.DeptLabel, 0)
|
||||
var item dto.DeptLabel
|
||||
for i := range list {
|
||||
if list[i].ParentId != 0 {
|
||||
continue
|
||||
}
|
||||
item = dto.DeptLabel{}
|
||||
item.Id = list[i].DeptId
|
||||
item.Label = list[i].DeptName
|
||||
deptInfo := deptLabelCall(&list, item)
|
||||
m = append(m, deptInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// deptLabelCall
|
||||
func deptLabelCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel {
|
||||
list := *deptList
|
||||
var mi dto.DeptLabel
|
||||
childrenList := make([]dto.DeptLabel, 0)
|
||||
for j := 0; j < len(list); j++ {
|
||||
if dept.Id != list[j].ParentId {
|
||||
continue
|
||||
}
|
||||
mi = dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}}
|
||||
ms := deptLabelCall(deptList, mi)
|
||||
childrenList = append(childrenList, ms)
|
||||
}
|
||||
dept.Children = childrenList
|
||||
return dept
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
)
|
||||
|
||||
type SysDictData struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取列表
|
||||
func (e *SysDictData) GetPage(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData, count *int64) error {
|
||||
var err error
|
||||
var data models.SysDictData
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
).
|
||||
Find(list).Limit(-1).Offset(-1).
|
||||
Count(count).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取对象
|
||||
func (e *SysDictData) Get(d *dto.SysDictDataGetReq, model *models.SysDictData) error {
|
||||
var err error
|
||||
var data models.SysDictData
|
||||
|
||||
db := e.Orm.Model(&data).
|
||||
First(model, d.GetId())
|
||||
err = db.Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert 创建对象
|
||||
func (e *SysDictData) Insert(c *dto.SysDictDataInsertReq) error {
|
||||
var err error
|
||||
var data = new(models.SysDictData)
|
||||
c.Generate(data)
|
||||
err = e.Orm.Create(data).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 修改对象
|
||||
func (e *SysDictData) Update(c *dto.SysDictDataUpdateReq) error {
|
||||
var err error
|
||||
var model = models.SysDictData{}
|
||||
e.Orm.First(&model, c.GetId())
|
||||
c.Generate(&model)
|
||||
db := e.Orm.Save(model)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权更新该数据")
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除
|
||||
func (e *SysDictData) Remove(c *dto.SysDictDataDeleteReq) error {
|
||||
var err error
|
||||
var data models.SysDictData
|
||||
|
||||
db := e.Orm.Delete(&data, c.GetId())
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
err = errors.New("无权删除该数据")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有
|
||||
func (e *SysDictData) GetAll(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData) error {
|
||||
var err error
|
||||
var data models.SysDictData
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
)
|
||||
|
||||
type SysDictType struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取列表
|
||||
func (e *SysDictType) GetPage(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType, count *int64) error {
|
||||
var err error
|
||||
var data models.SysDictType
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
).
|
||||
Find(list).Limit(-1).Offset(-1).
|
||||
Count(count).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取对象
|
||||
func (e *SysDictType) Get(d *dto.SysDictTypeGetReq, model *models.SysDictType) error {
|
||||
var err error
|
||||
|
||||
db := e.Orm.First(model, d.GetId())
|
||||
err = db.Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert 创建对象
|
||||
func (e *SysDictType) Insert(c *dto.SysDictTypeInsertReq) error {
|
||||
var err error
|
||||
var data models.SysDictType
|
||||
c.Generate(&data)
|
||||
var count int64
|
||||
// The error was dropped, so a query that failed left count at zero and the
|
||||
// insert went ahead as though the name were free.
|
||||
if err = e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count).Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return fmt.Errorf("当前字典类型[%s]已经存在!", data.DictType)
|
||||
}
|
||||
err = e.Orm.Create(&data).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 修改对象
|
||||
func (e *SysDictType) Update(c *dto.SysDictTypeUpdateReq) error {
|
||||
var err error
|
||||
var model = models.SysDictType{}
|
||||
e.Orm.First(&model, c.GetId())
|
||||
c.Generate(&model)
|
||||
db := e.Orm.Save(&model)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权更新该数据")
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除
|
||||
func (e *SysDictType) Remove(d *dto.SysDictTypeDeleteReq) error {
|
||||
var err error
|
||||
var data models.SysDictType
|
||||
|
||||
db := e.Orm.Delete(&data, d.GetId())
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
err = errors.New("无权删除该数据")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有
|
||||
func (e *SysDictType) GetAll(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType) error {
|
||||
var err error
|
||||
var data models.SysDictType
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
)
|
||||
|
||||
type SysLoginLog struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取SysLoginLog列表
|
||||
func (e *SysLoginLog) GetPage(c *dto.SysLoginLogGetPageReq, list *[]models.SysLoginLog, count *int64) error {
|
||||
var err error
|
||||
var data models.SysLoginLog
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
).
|
||||
Find(list).Limit(-1).Offset(-1).
|
||||
Count(count).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取SysLoginLog对象
|
||||
func (e *SysLoginLog) Get(d *dto.SysLoginLogGetReq, model *models.SysLoginLog) error {
|
||||
var err error
|
||||
db := e.Orm.First(model, d.GetId())
|
||||
err = db.Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除SysLoginLog
|
||||
func (e *SysLoginLog) Remove(c *dto.SysLoginLogDeleteReq) error {
|
||||
var err error
|
||||
var data models.SysLoginLog
|
||||
|
||||
db := e.Orm.Delete(&data, c.GetId())
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
err = errors.New("无权删除该数据")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
cModels "go-admin/common/models"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
type SysMenu struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// GetPage 获取SysMenu列表
|
||||
func (e *SysMenu) GetPage(c *dto.SysMenuGetPageReq, menus *[]models.SysMenu) *SysMenu {
|
||||
var menu = make([]models.SysMenu, 0)
|
||||
err := e.getPage(c, &menu).Error
|
||||
if err != nil {
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
for i := 0; i < len(menu); i++ {
|
||||
if menu[i].ParentId != 0 {
|
||||
continue
|
||||
}
|
||||
menusInfo := menuCall(&menu, menu[i])
|
||||
*menus = append(*menus, menusInfo)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// getPage 菜单分页列表
|
||||
func (e *SysMenu) getPage(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) *SysMenu {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.OrderDest("sort", false),
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).Preload("SysApi").
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("getSysMenuPage error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Get 获取SysMenu对象
|
||||
func (e *SysMenu) Get(d *dto.SysMenuGetReq, model *models.SysMenu) *SysMenu {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
|
||||
db := e.Orm.Model(&data).Preload("SysApi").
|
||||
First(model, d.GetId())
|
||||
err = db.Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = errors.New("查看对象不存在或无权查看")
|
||||
e.Log.Errorf("GetSysMenu error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
apis := make([]int, 0)
|
||||
for _, v := range model.SysApi {
|
||||
apis = append(apis, v.Id)
|
||||
}
|
||||
model.Apis = apis
|
||||
return e
|
||||
}
|
||||
|
||||
// Insert 创建SysMenu对象
|
||||
func (e *SysMenu) Insert(c *dto.SysMenuInsertReq) *SysMenu {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
c.Generate(&data)
|
||||
tx := e.Orm.Debug().Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
err = tx.Where("id in ?", c.Apis).Find(&data.SysApi).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
err = tx.Create(&data).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
c.MenuId = data.MenuId
|
||||
err = e.initPaths(tx, &data)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
tx.Commit()
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *SysMenu) initPaths(tx *gorm.DB, menu *models.SysMenu) error {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
parentMenu := new(models.SysMenu)
|
||||
if menu.ParentId != 0 {
|
||||
err = tx.Model(&data).First(parentMenu, menu.ParentId).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parentMenu.Paths == "" {
|
||||
err = errors.New("父级paths异常,请尝试对当前节点父级菜单进行更新操作!")
|
||||
return err
|
||||
}
|
||||
menu.Paths = parentMenu.Paths + "/" + pkg.IntToString(menu.MenuId)
|
||||
} else {
|
||||
menu.Paths = "/0/" + pkg.IntToString(menu.MenuId)
|
||||
}
|
||||
err = tx.Model(&data).Where("menu_id = ?", menu.MenuId).Update("paths", menu.Paths).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// Update 修改SysMenu对象
|
||||
func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu {
|
||||
var err error
|
||||
tx := e.Orm.Debug().Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
var alist = make([]models.SysApi, 0)
|
||||
var model = models.SysMenu{}
|
||||
tx.Preload("SysApi").First(&model, c.GetId())
|
||||
oldPath := model.Paths
|
||||
tx.Where("id in ?", c.Apis).Find(&alist)
|
||||
err = tx.Model(&model).Association("SysApi").Delete(model.SysApi)
|
||||
if err != nil {
|
||||
e.Log.Errorf("delete policy error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
c.Generate(&model)
|
||||
model.SysApi = alist
|
||||
db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
_ = e.AddError(errors.New("无权更新该数据"))
|
||||
return e
|
||||
}
|
||||
var menuList []models.SysMenu
|
||||
tx.Where("paths like ?", oldPath+"%").Find(&menuList)
|
||||
for _, v := range menuList {
|
||||
v.Paths = strings.Replace(v.Paths, oldPath, model.Paths, 1)
|
||||
tx.Model(&v).Update("paths", v.Paths)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Remove 删除SysMenu
|
||||
func (e *SysMenu) Remove(d *dto.SysMenuDeleteReq) *SysMenu {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
|
||||
db := e.Orm.Model(&data).Delete(&data, d.Ids)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
err = errors.New("无权删除该数据")
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// GetList 获取菜单数据
|
||||
func (e *SysMenu) GetList(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) error {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
|
||||
err = e.Orm.Model(&data).
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLabel 修改角色中 设置菜单基础数据
|
||||
func (e *SysMenu) SetLabel() (m []dto.MenuLabel, err error) {
|
||||
var list []models.SysMenu
|
||||
err = e.GetList(&dto.SysMenuGetPageReq{}, &list)
|
||||
|
||||
m = make([]dto.MenuLabel, 0)
|
||||
for i := 0; i < len(list); i++ {
|
||||
if list[i].ParentId != 0 {
|
||||
continue
|
||||
}
|
||||
e := dto.MenuLabel{}
|
||||
e.Id = list[i].MenuId
|
||||
e.Label = list[i].Title
|
||||
deptsInfo := menuLabelCall(&list, e)
|
||||
|
||||
m = append(m, deptsInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetSysMenuByRoleName 左侧菜单
|
||||
func (e *SysMenu) GetSysMenuByRoleName(roleName ...string) ([]models.SysMenu, error) {
|
||||
var MenuList []models.SysMenu
|
||||
var role models.SysRole
|
||||
var err error
|
||||
admin := false
|
||||
for _, s := range roleName {
|
||||
if s == "admin" {
|
||||
admin = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(roleName) > 0 && admin {
|
||||
var data []models.SysMenu
|
||||
err = e.Orm.Where(" menu_type in ('M','C')").
|
||||
Order("sort").
|
||||
Find(&data).
|
||||
Error
|
||||
MenuList = data
|
||||
} else {
|
||||
err = e.Orm.Model(&role).Preload("SysMenu", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where(" menu_type in ('M','C')").Order("sort")
|
||||
}).Where("role_name in ?", roleName).Find(&role).
|
||||
Error
|
||||
MenuList = *role.SysMenu
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
}
|
||||
return MenuList, err
|
||||
}
|
||||
|
||||
// menuLabelCall 递归构造组织数据
|
||||
func menuLabelCall(eList *[]models.SysMenu, dept dto.MenuLabel) dto.MenuLabel {
|
||||
list := *eList
|
||||
|
||||
min := make([]dto.MenuLabel, 0)
|
||||
for j := 0; j < len(list); j++ {
|
||||
|
||||
if dept.Id != list[j].ParentId {
|
||||
continue
|
||||
}
|
||||
mi := dto.MenuLabel{}
|
||||
mi.Id = list[j].MenuId
|
||||
mi.Label = list[j].Title
|
||||
mi.Children = []dto.MenuLabel{}
|
||||
if list[j].MenuType != "F" {
|
||||
ms := menuLabelCall(eList, mi)
|
||||
min = append(min, ms)
|
||||
} else {
|
||||
min = append(min, mi)
|
||||
}
|
||||
}
|
||||
if len(min) > 0 {
|
||||
dept.Children = min
|
||||
} else {
|
||||
dept.Children = nil
|
||||
}
|
||||
return dept
|
||||
}
|
||||
|
||||
// menuCall 构建菜单树
|
||||
func menuCall(menuList *[]models.SysMenu, menu models.SysMenu) models.SysMenu {
|
||||
list := *menuList
|
||||
|
||||
min := make([]models.SysMenu, 0)
|
||||
for j := 0; j < len(list); j++ {
|
||||
|
||||
if menu.MenuId != list[j].ParentId {
|
||||
continue
|
||||
}
|
||||
mi := models.SysMenu{}
|
||||
mi.MenuId = list[j].MenuId
|
||||
mi.MenuName = list[j].MenuName
|
||||
mi.Title = list[j].Title
|
||||
mi.Icon = list[j].Icon
|
||||
mi.Path = list[j].Path
|
||||
mi.MenuType = list[j].MenuType
|
||||
mi.Action = list[j].Action
|
||||
mi.Permission = list[j].Permission
|
||||
mi.ParentId = list[j].ParentId
|
||||
mi.NoCache = list[j].NoCache
|
||||
mi.Breadcrumb = list[j].Breadcrumb
|
||||
mi.Component = list[j].Component
|
||||
mi.Sort = list[j].Sort
|
||||
mi.Visible = list[j].Visible
|
||||
mi.CreatedAt = list[j].CreatedAt
|
||||
mi.SysApi = list[j].SysApi
|
||||
mi.Children = []models.SysMenu{}
|
||||
|
||||
if mi.MenuType != cModels.Button {
|
||||
ms := menuCall(menuList, mi)
|
||||
min = append(min, ms)
|
||||
} else {
|
||||
min = append(min, mi)
|
||||
}
|
||||
}
|
||||
menu.Children = min
|
||||
return menu
|
||||
}
|
||||
|
||||
func menuDistinct(menuList []models.SysMenu) (result []models.SysMenu) {
|
||||
distinctMap := make(map[int]struct{}, len(menuList))
|
||||
for _, menu := range menuList {
|
||||
if _, ok := distinctMap[menu.MenuId]; !ok {
|
||||
distinctMap[menu.MenuId] = struct{}{}
|
||||
result = append(result, menu)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func recursiveSetMenu(orm *gorm.DB, mIds []int, menus *[]models.SysMenu) error {
|
||||
if len(mIds) == 0 || menus == nil {
|
||||
return nil
|
||||
}
|
||||
var subMenus []models.SysMenu
|
||||
err := orm.Where(fmt.Sprintf(" menu_type in ('%s', '%s', '%s') and menu_id in ?",
|
||||
cModels.Directory, cModels.Menu, cModels.Button), mIds).Order("sort").Find(&subMenus).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subIds := make([]int, 0)
|
||||
for _, menu := range subMenus {
|
||||
if menu.ParentId != 0 {
|
||||
subIds = append(subIds, menu.ParentId)
|
||||
}
|
||||
if menu.MenuType != cModels.Button {
|
||||
*menus = append(*menus, menu)
|
||||
}
|
||||
}
|
||||
return recursiveSetMenu(orm, subIds, menus)
|
||||
}
|
||||
|
||||
// SetMenuRole 获取左侧菜单树使用
|
||||
func (e *SysMenu) SetMenuRole(roleName string) (m []models.SysMenu, err error) {
|
||||
menus, err := e.getByRoleName(roleName)
|
||||
m = make([]models.SysMenu, 0)
|
||||
for i := 0; i < len(menus); i++ {
|
||||
if menus[i].ParentId != 0 {
|
||||
continue
|
||||
}
|
||||
menusInfo := menuCall(&menus, menus[i])
|
||||
m = append(m, menusInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *SysMenu) getByRoleName(roleName string) ([]models.SysMenu, error) {
|
||||
var role models.SysRole
|
||||
var err error
|
||||
data := make([]models.SysMenu, 0)
|
||||
|
||||
if roleName == "admin" {
|
||||
// The soft-delete condition is GORM's to add: it appends one for the
|
||||
// model's DeletedAt field on every query. Writing it by hand duplicates
|
||||
// that and hard-codes what "deleted" looks like — a column that stops
|
||||
// being nullable turns this clause into one that matches nothing.
|
||||
err = e.Orm.Where("menu_type in ('M','C')").
|
||||
Order("sort").
|
||||
Find(&data).
|
||||
Error
|
||||
err = errors.WithStack(err)
|
||||
} else {
|
||||
role.RoleKey = roleName
|
||||
err = e.Orm.Model(&role).Where("role_key = ? ", roleName).Preload("SysMenu").First(&role).Error
|
||||
|
||||
if role.SysMenu != nil {
|
||||
mIds := make([]int, 0)
|
||||
for _, menu := range *role.SysMenu {
|
||||
mIds = append(mIds, menu.MenuId)
|
||||
}
|
||||
if err := recursiveSetMenu(e.Orm, mIds, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = menuDistinct(data)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(models.SysMenuSlice(data))
|
||||
return data, err
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
)
|
||||
|
||||
// The admin branch of getSysMenuByRoleName carried "deleted_at is null" in its
|
||||
// where clause. GORM adds that condition itself for a model with a DeletedAt
|
||||
// field, so the clause was a duplicate — and one written in terms of a column
|
||||
// being null, which stops being true the moment the column stops being
|
||||
// nullable. This pins the behaviour the clause was there for.
|
||||
func TestSoftDeletedMenusAreNotReturned(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.SysMenu{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
|
||||
live := models.SysMenu{MenuName: "live", MenuType: "M"}
|
||||
gone := models.SysMenu{MenuName: "gone", MenuType: "M"}
|
||||
if err := db.Create(&live).Error; err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if err := db.Create(&gone).Error; err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if err := db.Delete(&gone).Error; err != nil {
|
||||
t.Fatalf("delete: %v", err)
|
||||
}
|
||||
|
||||
// Through getByRoleName rather than a copy of its query: a test that
|
||||
// reissues the statement passes whether or not the production line still
|
||||
// says what it is supposed to, which is what the first version of this
|
||||
// test did.
|
||||
e := &SysMenu{}
|
||||
e.Orm = db
|
||||
got, err := e.getByRoleName("admin")
|
||||
if err != nil {
|
||||
t.Fatalf("getByRoleName: %v", err)
|
||||
}
|
||||
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("got %d rows, want 1", len(got))
|
||||
}
|
||||
if got[0].MenuName != "live" {
|
||||
t.Errorf("got %q, want the row that was not deleted", got[0].MenuName)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user