Compare commits
655 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847f5c7c90 | ||
|
|
f23b243fc5 | ||
|
|
3768cef62d | ||
|
|
1e1ded4db8 | ||
|
|
13f76f4b35 | ||
|
|
c796e05232 | ||
|
|
f207517d35 | ||
|
|
6f5069567e | ||
|
|
13ce0e9396 | ||
|
|
e0144f7310 | ||
|
|
8ba87c1895 | ||
|
|
414e4b0b36 | ||
|
|
bfa41c3621 | ||
|
|
84ab496308 | ||
|
|
1f27283ab7 | ||
|
|
f91b89f38a | ||
|
|
9bb9861d88 | ||
|
|
403d1c45e5 | ||
|
|
400db0402a | ||
|
|
f0ae178183 | ||
|
|
4641e448d2 | ||
|
|
f0de65b7ce | ||
|
|
0472c5101f | ||
|
|
185cd6f82b | ||
|
|
aa6ad39b83 | ||
|
|
047b57f890 | ||
|
|
a18417ab26 | ||
|
|
20fcf557d5 | ||
|
|
5598ddf93c | ||
|
|
3017460cc7 | ||
|
|
4836a770c4 | ||
|
|
e6c89fad1b | ||
|
|
dba19b1e66 | ||
|
|
4e30bdb7cc | ||
|
|
4ac57cd140 | ||
|
|
c4d52ce47a | ||
|
|
54d0688571 | ||
|
|
66d5fd6ca4 | ||
|
|
25195b6360 | ||
|
|
e02ecf053f | ||
|
|
c86f2ad412 | ||
|
|
82fd97e06a | ||
|
|
614a144f60 | ||
|
|
7d344c71e1 | ||
|
|
6ad6c69660 | ||
|
|
e96379b6c0 | ||
|
|
f7480f3bac | ||
|
|
54d3a5b368 | ||
|
|
7eb4d064ea | ||
|
|
cc66fcddf5 | ||
|
|
aac4c2b42b | ||
|
|
7a17042276 | ||
|
|
42fbfd3c47 | ||
|
|
e273ade0b0 | ||
|
|
bcaa4563ac | ||
|
|
e0c01d4561 | ||
|
|
d6280ea280 | ||
|
|
666b191b6c | ||
|
|
778cb7f4de | ||
|
|
142bbd265d | ||
|
|
f676ec9e7b | ||
|
|
44d379a016 | ||
|
|
2170509d92 | ||
|
|
798ab7d18b | ||
|
|
abd2b4bac0 | ||
|
|
585cbbed23 | ||
|
|
1b40d345eb | ||
|
|
3c0292b56e | ||
|
|
bc21ba7c1e | ||
|
|
c7c3fd7f7e | ||
|
|
547e31eae6 | ||
|
|
6072bcb111 | ||
|
|
aa393590b2 | ||
|
|
efb2b7368c | ||
|
|
30ea36a722 | ||
|
|
5a6e9d81a7 | ||
|
|
8d24c2a4fa | ||
|
|
1be7a0ec79 | ||
|
|
f30841209c | ||
|
|
e4d949a64b | ||
|
|
3f6fb5afef | ||
|
|
68f553f4b0 | ||
|
|
7f2a49ba3c | ||
|
|
e56788af3e | ||
|
|
ebc89e056f | ||
|
|
d07cd74a8c | ||
|
|
6cc15ebeda | ||
|
|
2b712cd548 | ||
|
|
cda2963e1c | ||
|
|
bffa9c2676 | ||
|
|
9366a76b84 | ||
|
|
e03ceecd9a | ||
|
|
99a746085b | ||
|
|
74ae031853 | ||
|
|
af14be9801 | ||
|
|
df7413a9ea | ||
|
|
e967e02095 | ||
|
|
c1d09f447d | ||
|
|
5e5afd49dc | ||
|
|
2118acf244 | ||
|
|
44a1bd626e | ||
|
|
ea3c70a8a8 | ||
|
|
6343173cf8 | ||
|
|
6837a9c867 | ||
|
|
a726927a28 | ||
|
|
e135e4ce64 | ||
|
|
43edef412c | ||
|
|
2deb3109c2 | ||
|
|
a80221a950 | ||
|
|
10630847df | ||
|
|
f43851698e | ||
|
|
73884bb693 | ||
|
|
1b5bb1de8b | ||
|
|
4814793546 | ||
|
|
d85bbff270 | ||
|
|
bb1522f4dc | ||
|
|
a7632fbf58 | ||
|
|
c4cb4234fd | ||
|
|
89e12678eb | ||
|
|
137ebb8e9e | ||
|
|
05625bd8c1 | ||
|
|
4afeac5fdd | ||
|
|
1d0e91f1af | ||
|
|
cf5111a325 | ||
|
|
78957a8ebd | ||
|
|
4ed892a656 | ||
|
|
3486b07003 | ||
|
|
a5cd7caf19 | ||
|
|
f2c7ef78c0 | ||
|
|
653953ee76 | ||
|
|
a831614d5a | ||
|
|
ebe73e2f19 | ||
|
|
29fd5a25d2 | ||
|
|
44805ce580 | ||
|
|
2a6d620830 | ||
|
|
01d3e1ad28 | ||
|
|
f4162c38db | ||
|
|
1a4626c24d | ||
|
|
d6eb9683d1 | ||
|
|
e2b524dadb | ||
|
|
8998a21626 | ||
|
|
abc015aec0 | ||
|
|
4ef8d27b1e | ||
|
|
40b6e603fc | ||
|
|
21498584b1 | ||
|
|
408bac09a1 | ||
|
|
582d879a77 | ||
|
|
38ff5152e0 | ||
|
|
d1d372e1bf | ||
|
|
5e4793433b | ||
|
|
54ad19f97e | ||
|
|
fc166650b3 | ||
|
|
2acc295259 | ||
|
|
4b3ed1310d | ||
|
|
b2cfd1517c | ||
|
|
b13d27ccd6 | ||
|
|
68e0088016 | ||
|
|
bd1e83989d | ||
|
|
263dfa6be7 | ||
|
|
eb55f93864 | ||
|
|
8589105e44 | ||
|
|
986b187f0a | ||
|
|
008d34c453 | ||
|
|
49d3f988c9 | ||
|
|
76475e807e | ||
|
|
f93231da61 | ||
|
|
bf75483a3c | ||
|
|
b56b0187cf | ||
|
|
7e7f02b502 | ||
|
|
878985f7c5 | ||
|
|
2133d9b737 | ||
|
|
d711a36749 | ||
|
|
9dbf104ef1 | ||
|
|
20eb06fb28 | ||
|
|
9c20bdef39 | ||
|
|
3fdd98a390 | ||
|
|
d4f456c0cf | ||
|
|
f2b6e15cf4 | ||
|
|
6be0ea6aed | ||
|
|
eee08be2cc | ||
|
|
252fc553f2 | ||
|
|
ac2ceed3f9 | ||
|
|
3f828cc5b0 | ||
|
|
fc1b9ef35d | ||
|
|
d0b71a1c40 | ||
|
|
a743a6a05a | ||
|
|
0e6b9713ce | ||
|
|
b9afbc764d | ||
|
|
923e183a67 | ||
|
|
7e9a381641 | ||
|
|
bed95254d0 | ||
|
|
e4d13f3377 | ||
|
|
d530365ef9 | ||
|
|
070d4ea104 | ||
|
|
3fc86f0fae | ||
|
|
3b77ab2727 | ||
|
|
76cb991282 | ||
|
|
9efd20f1b9 | ||
|
|
de5b9e46d3 | ||
|
|
f27d3d200f | ||
|
|
f4a64b96a9 | ||
|
|
9a59749763 | ||
|
|
b017b902f8 | ||
|
|
7c53353c60 | ||
|
|
63f0615445 | ||
|
|
94da6df33e | ||
|
|
cc3981d99c | ||
|
|
8332d9b354 | ||
|
|
493925064c | ||
|
|
c0232c4c75 | ||
|
|
b873855b44 | ||
|
|
9c524292f0 | ||
|
|
bfd346e65a | ||
|
|
bc811cbd49 | ||
|
|
bbec3eca0d | ||
|
|
3857d674ba | ||
|
|
25b0ae4d2f | ||
|
|
b7aa281611 | ||
|
|
3c89a285f5 | ||
|
|
d3d26c85c3 | ||
|
|
a764c4f974 | ||
|
|
af454f7d5d | ||
|
|
eea759e10e | ||
|
|
e158422091 | ||
|
|
5ada63d4a1 | ||
|
|
5ef35001cc | ||
|
|
0be50f0995 | ||
|
|
61e1b0ca70 | ||
|
|
85910bf440 | ||
|
|
7a7a7020b4 | ||
|
|
ae3d2659aa | ||
|
|
76fd6675b5 | ||
|
|
664118a709 | ||
|
|
e344722794 | ||
|
|
1a7d425f60 | ||
|
|
a0582192bf | ||
|
|
4ac9f02d6a | ||
|
|
7e0febef8f | ||
|
|
94ed4b77d6 | ||
|
|
2b419bca11 | ||
|
|
54a0f0b3c7 | ||
|
|
86bccc3b3d | ||
|
|
0e601b5033 | ||
|
|
85d745fcee | ||
|
|
550631c03b | ||
|
|
f29a1560aa | ||
|
|
8c4c41cf0b | ||
|
|
f5c90277b1 | ||
|
|
2ae0cd7ab4 | ||
|
|
1f6c14ee2f | ||
|
|
574d27f6da | ||
|
|
7d62841783 | ||
|
|
970d74bd70 | ||
|
|
0c797f8da9 | ||
|
|
68f8603c75 | ||
|
|
06bce33c48 | ||
|
|
f8837f28c3 | ||
|
|
61aed08dde | ||
|
|
a5a813f95f | ||
|
|
18cf2e54c4 | ||
|
|
5c72b1de57 | ||
|
|
6e44e90d67 | ||
|
|
0e699ba20e | ||
|
|
cf24c2671f | ||
|
|
d3b99ec88d | ||
|
|
0b5ab090a4 | ||
|
|
454698286c | ||
|
|
14e0aadbba | ||
|
|
73986a834c | ||
|
|
8faf1831d9 | ||
|
|
d86ef0a9ab | ||
|
|
c2bb0c589e | ||
|
|
9994f20a2c | ||
|
|
b5014c307f | ||
|
|
75bd4ca3df | ||
|
|
d00bd2ed72 | ||
|
|
e444500835 | ||
|
|
6709135a0b | ||
|
|
59a7ff9ac7 | ||
|
|
172c729535 | ||
|
|
ac5198db1c | ||
|
|
5c5c2c2037 | ||
|
|
1db990b554 | ||
|
|
70c887a16a | ||
|
|
2430c4f6aa | ||
|
|
84fd14c129 | ||
|
|
a376a82240 | ||
|
|
e1e03dc09a | ||
|
|
790d644c34 | ||
|
|
9de8dae954 | ||
|
|
57361d8241 | ||
|
|
b347bd7ef5 | ||
|
|
070c8ac0da | ||
|
|
e221c2f42e | ||
|
|
c7bab3a71b | ||
|
|
82c17a51a2 | ||
|
|
e4447e6bc2 | ||
|
|
b9570d9a5f | ||
|
|
01e8a2c14d | ||
|
|
64bd51c3b0 | ||
|
|
54ab34df3f | ||
|
|
206490ba3e | ||
|
|
16612d2c9c | ||
|
|
6b65605360 | ||
|
|
bb37ed3b95 | ||
|
|
d102cc8c08 | ||
|
|
a6df74d63d | ||
|
|
f79760943e | ||
|
|
a40ec21a05 | ||
|
|
43230267b6 | ||
|
|
0ae99cdaf9 | ||
|
|
f234c72514 | ||
|
|
76527d95bd | ||
|
|
27c53385f2 | ||
|
|
a1b25e9766 | ||
|
|
abad0ed481 | ||
|
|
eddda41291 | ||
|
|
d9adf0fd25 | ||
|
|
0ce82b41ba | ||
|
|
37026f3269 | ||
|
|
3155380f16 | ||
|
|
f2b0f294d8 | ||
|
|
12f63ef3dd | ||
|
|
a1303b52eb | ||
|
|
10f6b03fb5 | ||
|
|
45d2449221 | ||
|
|
9e5f146e05 | ||
|
|
2b91bbe185 | ||
|
|
747ea6404d | ||
|
|
ccfc6bd1df | ||
|
|
361eafedae | ||
|
|
a64b894b08 | ||
|
|
0ad805c170 | ||
|
|
ba82b5b516 | ||
|
|
f04b82c933 | ||
|
|
23b137ab9b | ||
|
|
a4d3a4627a | ||
|
|
77ae6e3bab | ||
|
|
e0f1f40ba0 | ||
|
|
d300f604f1 | ||
|
|
2c2c0ff40b | ||
|
|
b4ddbbd38f | ||
|
|
7544288451 | ||
|
|
41443dccc0 | ||
|
|
22e218fc5f | ||
|
|
4da0d1abaa | ||
|
|
6563b53436 | ||
|
|
fac71a4794 | ||
|
|
92dff6fdc3 | ||
|
|
a1eca3d691 | ||
|
|
6681dc1057 | ||
|
|
829a68feaa | ||
|
|
72677e270d | ||
|
|
dd4ac390de | ||
|
|
0bd7d38c23 | ||
|
|
ead3b0d0d8 | ||
|
|
4b973b22a4 | ||
|
|
e4e68d02bc | ||
|
|
ef8822d671 | ||
|
|
8e75e1f6ef | ||
|
|
08c381fa60 | ||
|
|
d7a10d4032 | ||
|
|
c324a030f9 | ||
|
|
b618b8f93b | ||
|
|
4d2e110e1e | ||
|
|
ecd79a2e15 | ||
|
|
f4f297d3f7 | ||
|
|
b5549c0fae | ||
|
|
929bfb3200 | ||
|
|
7d3593a944 | ||
|
|
9e0db2bc99 | ||
|
|
25b0d276b3 | ||
|
|
0cb7a7cf83 | ||
|
|
52f72400ba | ||
|
|
0eaff33168 | ||
|
|
086dbf278b | ||
|
|
57a5e237ae | ||
|
|
eee6cf7b14 | ||
|
|
b9c6ac8d6d | ||
|
|
618d782af3 | ||
|
|
d0ac7de4cb | ||
|
|
baf8053613 | ||
|
|
b973d63331 | ||
|
|
85b64d7e8d | ||
|
|
86ad183c41 | ||
|
|
f7b685cfad | ||
|
|
649116a0b8 | ||
|
|
899a3a8243 | ||
|
|
d51cd4b289 | ||
|
|
537b179e78 | ||
|
|
1e5b1868ab | ||
|
|
245406673c | ||
|
|
51fa197af6 | ||
|
|
649b2bb165 | ||
|
|
3634c902d0 | ||
|
|
756e580469 | ||
|
|
4e1350d1cc | ||
|
|
2e969d46fb | ||
|
|
a5bcbe151d | ||
|
|
c4abba361a | ||
|
|
24b46b1133 | ||
|
|
3ae7e0de75 | ||
|
|
c2ee4f9955 | ||
|
|
2479412334 | ||
|
|
6da8d7fd67 | ||
|
|
0f596a712d | ||
|
|
8f37b71d7f | ||
|
|
5083b2bdfe | ||
|
|
155ae65b4a | ||
|
|
ffacfc3ae8 | ||
|
|
b1ab66ecf9 | ||
|
|
f5bb0cad3e | ||
|
|
a0de5afcb0 | ||
|
|
358d33d60e | ||
|
|
062d28b6e6 | ||
|
|
513f8ea012 | ||
|
|
179b58e557 | ||
|
|
b7450f8869 | ||
|
|
7f9e972828 | ||
|
|
7b51705f4e | ||
|
|
6bd9e5333d | ||
|
|
112d735ac0 | ||
|
|
52553ed53f | ||
|
|
70d84e32d1 | ||
|
|
3269dfa5d6 | ||
|
|
183a6e4905 | ||
|
|
5463ae9d7e | ||
|
|
f25bdb07ce | ||
|
|
aa5c08d564 | ||
|
|
dc9a2985f3 | ||
|
|
f4ac6d8360 | ||
|
|
3266039aaf | ||
|
|
e3f4c298b0 | ||
|
|
fa58f6d2de | ||
|
|
ae5a1fd7de | ||
|
|
c240079df4 | ||
|
|
aca4e6751e | ||
|
|
ce32fc7f2c | ||
|
|
d423572e01 | ||
|
|
d9807b1bf0 | ||
|
|
1bc53b4c80 | ||
|
|
6bc2603a4d | ||
|
|
e2c929aae1 | ||
|
|
0d155d592b | ||
|
|
ae510ff1ff | ||
|
|
5b0654ad2c | ||
|
|
466f97ecbe | ||
|
|
27a14c22d7 | ||
|
|
4709edcd1c | ||
|
|
414de9f2eb | ||
|
|
a53e7e7dab | ||
|
|
7fa6628dc5 | ||
|
|
62c25afea8 | ||
|
|
481b622e3b | ||
|
|
64f8f9a200 | ||
|
|
0eca951465 | ||
|
|
ef4e34c584 | ||
|
|
d91acbc7ee | ||
|
|
b397d1022e | ||
|
|
b42a98aff5 | ||
|
|
adc65439e4 | ||
|
|
445cf3716b | ||
|
|
f4b59b8503 | ||
|
|
4f08975df2 | ||
|
|
570db453d7 | ||
|
|
b93984bf6f | ||
|
|
e483db1b97 | ||
|
|
17d96acceb | ||
|
|
9900b236ef | ||
|
|
4fa52412c1 | ||
|
|
0076869deb | ||
|
|
af55193591 | ||
|
|
1d858118d5 | ||
|
|
8e64ba67fa | ||
|
|
58fb11b78f | ||
|
|
f6e9076a40 | ||
|
|
2fefa43aea | ||
|
|
f43b0467ba | ||
|
|
14da4d77e0 | ||
|
|
cefad86b85 | ||
|
|
738ff86344 | ||
|
|
110abc4ac7 | ||
|
|
5f1aaa40d8 | ||
|
|
0695ad9a85 | ||
|
|
7c086bbec8 | ||
|
|
c75fe7135a | ||
|
|
edf29976dd | ||
|
|
7b5f963ec4 | ||
|
|
f22badb861 | ||
|
|
8c501c90cd | ||
|
|
11eebdfcf0 | ||
|
|
46e331783f | ||
|
|
7e33e64659 | ||
|
|
7e4152ad6d | ||
|
|
d189ee7c22 | ||
|
|
641c2abb24 | ||
|
|
3ab4ac891b | ||
|
|
70b586e45a | ||
|
|
77aa724003 | ||
|
|
db7b50d96a | ||
|
|
00fee24a85 | ||
|
|
fa0cb73ec9 | ||
|
|
6ee815b71d | ||
|
|
311a048af5 | ||
|
|
3da9ecfaa3 | ||
|
|
4c2c6f613e | ||
|
|
ba63736871 | ||
|
|
ed3dbafc35 | ||
|
|
fdeffbd495 | ||
|
|
9870812e6b | ||
|
|
46f35e14c4 | ||
|
|
e89cf96ff4 | ||
|
|
5cd19bf38d | ||
|
|
a6f69f2b62 | ||
|
|
e34b9adada | ||
|
|
9f43f731b5 | ||
|
|
594ca43505 | ||
|
|
2a91cdb67a | ||
|
|
11148e720b | ||
|
|
c5835d6d8c | ||
|
|
4a26bb3ba5 | ||
|
|
4fec38724d | ||
|
|
85349df8a1 | ||
|
|
ffe250f8a9 | ||
|
|
eeb310a1d2 | ||
|
|
a42c606d20 | ||
|
|
4e64d46cd2 | ||
|
|
9886ee6828 | ||
|
|
0e1bde09c3 | ||
|
|
dda600709b | ||
|
|
15f38491b2 | ||
|
|
4b140732f7 | ||
|
|
4cb9ff3f14 | ||
|
|
e4f3e2c4c1 | ||
|
|
a80adb7dd8 | ||
|
|
195127a9d4 | ||
|
|
f2119b2c52 | ||
|
|
f15c45793b | ||
|
|
24f543e667 | ||
|
|
772995705f | ||
|
|
3475c39fe6 | ||
|
|
e6e393379f | ||
|
|
03cc91c3e5 | ||
|
|
f82f7bec6a | ||
|
|
4afd5bbd5e | ||
|
|
86aac2bf08 | ||
|
|
70c8b25a67 | ||
|
|
231af72444 | ||
|
|
480e930385 | ||
|
|
debc34f0fb | ||
|
|
99ce3bd099 | ||
|
|
99431cf9a2 | ||
|
|
83711c69f9 | ||
|
|
9e67032280 | ||
|
|
fa37937410 | ||
|
|
1be2cad78e | ||
|
|
2b1e687ed4 | ||
|
|
881009321b | ||
|
|
aed99b63b8 | ||
|
|
dfa34ba371 | ||
|
|
20beb30dd8 | ||
|
|
4475972af3 | ||
|
|
a843c65783 | ||
|
|
c2de1d3fa2 | ||
|
|
c8d091da06 | ||
|
|
553208ba57 | ||
|
|
072028699a | ||
|
|
9cdcf145a5 | ||
|
|
4df1c19e81 | ||
|
|
ac26a214bc | ||
|
|
ad616496d1 | ||
|
|
9870582779 | ||
|
|
20cc696b33 | ||
|
|
d7263f2b3c | ||
|
|
74e5ee41fb | ||
|
|
f936331dff | ||
|
|
ba311c3504 | ||
|
|
03291594b1 | ||
|
|
a6d9a4b5ae | ||
|
|
875de022c1 | ||
|
|
2c863a2774 | ||
|
|
f2f086a82c | ||
|
|
936ca61f94 | ||
|
|
87ae2f81fa | ||
|
|
ecf67db2b1 | ||
|
|
2e5589e112 | ||
|
|
2598a60898 | ||
|
|
0de226bbf3 | ||
|
|
422f0d8491 | ||
|
|
b028708b94 | ||
|
|
812c0d0f6a | ||
|
|
46df5293dd | ||
|
|
ab42b3e90b | ||
|
|
1378259cc7 | ||
|
|
c8f0b0a83f | ||
|
|
acec760ec1 | ||
|
|
2fe70d49f6 | ||
|
|
9013fff804 | ||
|
|
e925a808c4 | ||
|
|
6c197edddd | ||
|
|
c35e91b7b6 | ||
|
|
575947795a | ||
|
|
51f116c7d2 | ||
|
|
c28254855c | ||
|
|
e8f3671ffb | ||
|
|
ac62767a18 | ||
|
|
2db4c20dd3 | ||
|
|
cfb7fd5b29 | ||
|
|
22c401f9d8 | ||
|
|
be00b90c1d | ||
|
|
fb3f89c594 | ||
|
|
e7a66378ea | ||
|
|
2f88b48973 | ||
|
|
7761fe0288 | ||
|
|
09e6bdcf7e | ||
|
|
61a4d87f59 | ||
|
|
c219ec33b0 | ||
|
|
fd86f36218 | ||
|
|
efac41f392 | ||
|
|
52df61ae0d | ||
|
|
cf2bc6785c | ||
|
|
98a4c92576 | ||
|
|
b1ee9b65ff | ||
|
|
99cc4c5e5e | ||
|
|
226bb8f089 | ||
|
|
37ed5134e8 | ||
|
|
0f54d4a472 | ||
|
|
64805360d6 | ||
|
|
7f69fe2ad9 | ||
|
|
f913510d3c | ||
|
|
f2d9e7786d | ||
|
|
e1afb1ed54 | ||
|
|
12f8cf0111 | ||
|
|
daa2ef5203 | ||
|
|
1e3e183930 | ||
|
|
366563a0fe | ||
|
|
577802e5ad | ||
|
|
76d6fc3ba5 | ||
|
|
f0540559bb | ||
|
|
802e379f60 | ||
|
|
8c9253da80 | ||
|
|
5271bd21e8 | ||
|
|
db554ebdc9 | ||
|
|
1c18a01bf6 | ||
|
|
729a3d7028 | ||
|
|
b88923a128 | ||
|
|
fe8cd93c78 | ||
|
|
64b49dae2e | ||
|
|
edbbbca5f9 | ||
|
|
5ad0d90038 | ||
|
|
9b9173dea7 | ||
|
|
f58331c1c1 | ||
|
|
b2dc9dff0b | ||
|
|
51d06ab206 | ||
|
|
799e0ac9fc |
16
.gitignore
vendored
@@ -15,4 +15,18 @@
|
||||
*.sum
|
||||
|
||||
*/node_modules/
|
||||
**/vendor/
|
||||
**/vendor/
|
||||
.idea
|
||||
.vscode
|
||||
.qoder
|
||||
out
|
||||
|
||||
server/docs/docker-compose
|
||||
server/config.yml
|
||||
server/ip2region.xdb
|
||||
mayfly-go.log
|
||||
|
||||
mayfly-go-linux-amd64/
|
||||
|
||||
.DS_Store
|
||||
__debug_*
|
||||
18
.vscode/launch.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "mayfly-go",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}/main.go",
|
||||
"env": {},
|
||||
"args": []
|
||||
},
|
||||
]
|
||||
}
|
||||
47
AGENTS.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Mayfly-Go
|
||||
|
||||
你是一位全栈开发工程师,参与 Mayfly-Go 项目的开发。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **后端**: Go 1.26+, GORM, Gin, 自定义 IOC 依赖注入框架
|
||||
- **前端**: Vue 3 (Composition API) + TypeScript 6.x + Vite 8.x + Element Plus + Tailwind CSS 4.x + Pinia
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 后端
|
||||
cd server && go run main.go
|
||||
cd server && go test ./...
|
||||
|
||||
# 前端
|
||||
cd frontend && pnpm dev
|
||||
cd frontend && pnpm build
|
||||
cd frontend && pnpm lint
|
||||
```
|
||||
|
||||
## 全局边界
|
||||
|
||||
- ✅ **Always**: 后端遵循 Clean Architecture 分层(api → application → domain → infra)
|
||||
- ✅ **Always**: 所有错误必须处理,禁止 `result, _ := doSomething()`
|
||||
- ✅ **Always**: 前端所有展示文本使用 i18n(`$t()` / `t()`),禁止硬编码
|
||||
- ⚠️ **Ask first**: 修改 pkg/ 或 common/ 下的公共接口
|
||||
- 🚫 **Never**: 在 application/domain/infra 层使用 `biz.ErrIsNil`,必须返回 error
|
||||
- 🚫 **Never**: 前端直接调用 axios,必须通过 API 封装
|
||||
|
||||
## 详细规范
|
||||
|
||||
- @./docs/server/architecture.md — 分层架构与目录规范
|
||||
- @./docs/server/api.md — API 层规范
|
||||
- @./docs/server/application.md — Application 层规范
|
||||
- @./docs/server/domain.md — Domain 层规范
|
||||
- @./docs/server/infrastructure.md — Infrastructure 层规范
|
||||
- @./docs/server/concurrent.md — 并发与 Panic 处理
|
||||
- @./docs/server/security.md — 安全与权限
|
||||
- @./docs/server/quality.md — 代码质量与 Git 提交
|
||||
- @./docs/server/i18n.md — 后端国际化规范
|
||||
- @./docs/frontend/overview.md — 前端综合示例与技术栈
|
||||
- @./docs/frontend/component.md — 组件开发规范
|
||||
- @./docs/frontend/api.md — API 定义与调用
|
||||
- @./docs/frontend/i18n.md — 国际化规范
|
||||
- @./docs/frontend/style.md — 样式与 UI 规范
|
||||
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
ARG BASEIMAGES=m.daocloud.io/docker.io/alpine:3.20.2
|
||||
|
||||
FROM ${BASEIMAGES} AS builder
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG MAYFLY_GO_VERSION
|
||||
ARG MAYFLY_GO_DIR_NAME=mayfly-go-linux-${TARGETARCH}
|
||||
ARG MAYFLY_GO_URL=https://gitee.com/dromara/mayfly-go/releases/download/${MAYFLY_GO_VERSION}/${MAYFLY_GO_DIR_NAME}.zip
|
||||
|
||||
RUN wget -cO mayfly-go.zip ${MAYFLY_GO_URL} && \
|
||||
unzip mayfly-go.zip && \
|
||||
mv ${MAYFLY_GO_DIR_NAME}/* /opt
|
||||
|
||||
|
||||
FROM ${BASEIMAGES}
|
||||
|
||||
ARG TZ=Asia/Shanghai
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
COPY --from=builder /opt/mayfly-go /usr/local/bin/mayfly-go
|
||||
|
||||
WORKDIR /mayfly-go
|
||||
|
||||
EXPOSE 18888
|
||||
|
||||
CMD ["mayfly-go"]
|
||||
39
Dockerfile.sourcebuild
Normal file
@@ -0,0 +1,39 @@
|
||||
# 构建前端资源
|
||||
FROM m.daocloud.io/docker.io/node:22-bookworm-slim AS fe-builder
|
||||
|
||||
WORKDIR /mayfly
|
||||
|
||||
COPY frontend .
|
||||
|
||||
RUN npm config set registry 'https://registry.npmmirror.com' && \
|
||||
npm install && \
|
||||
npm run build
|
||||
|
||||
# 构建后端资源
|
||||
FROM m.daocloud.io/docker.io/golang:1.26 AS be-builder
|
||||
|
||||
ENV GOPROXY https://goproxy.cn
|
||||
WORKDIR /mayfly
|
||||
|
||||
# Copy the go source for building server
|
||||
COPY server .
|
||||
|
||||
RUN go mod tidy && go mod download
|
||||
|
||||
COPY --from=fe-builder /mayfly/dist /mayfly/static/static
|
||||
|
||||
# Build
|
||||
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
|
||||
go build -a -ldflags=-w \
|
||||
-o mayfly-go main.go
|
||||
|
||||
FROM m.daocloud.io/docker.io/alpine:3.20.2
|
||||
|
||||
ARG TZ=Asia/Shanghai
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
WORKDIR /mayfly-go
|
||||
|
||||
COPY --from=be-builder /mayfly/mayfly-go /usr/local/bin/mayfly-go
|
||||
|
||||
CMD ["mayfly-go"]
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
|
||||
36
README.en.md
@@ -1,36 +0,0 @@
|
||||
# mayfly-go
|
||||
|
||||
#### Description
|
||||
golang实现linux运维等
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
114
README.md
@@ -1,33 +1,109 @@
|
||||
# mayfly-go
|
||||
# 🌈Dromara mayfly-go
|
||||
|
||||
#### 介绍
|
||||
简单基于DDD(领域驱动设计)分层架构实现web版mysql,redis,linux统一操作管理平台
|
||||
<p align="center">
|
||||
<a href="./README_EN.md">English</a> |
|
||||
<a href="https://www.yuque.com/may-fly/mayfly-go">项目文档</a> |
|
||||
<a href="https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854">操作视频</a> |
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/mayfly-go" target="_blank">
|
||||
<img src="https://gitee.com/dromara/mayfly-go/badge/star.svg?theme=white" alt="star"/>
|
||||
<img src="https://gitee.com/dromara/mayfly-go/badge/fork.svg" alt="fork"/>
|
||||
</a>
|
||||
<a href="https://github.com/dromara/mayfly-go" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/dromara/mayfly-go.svg?style=social" alt="github star"/>
|
||||
<img src="https://img.shields.io/github/forks/dromara/mayfly-go.svg?style=social" alt="github fork"/>
|
||||
</a>
|
||||
<a href="https://github.com/dromara/mayfly-go" target="_blank">
|
||||
<img src="https://gitcode.com/dromara/mayfly-go/star/badge.svg" alt="github star"/>
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/mayflygo/mayfly-go/tags" target="_blank">
|
||||
<img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
|
||||
</a>
|
||||
<a href="https://github.com/golang/go" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Golang-1.24%2B-yellow.svg" alt="golang"/>
|
||||
</a>
|
||||
<a href="https://cn.vuejs.org" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 前言
|
||||
|
||||
Web 版 **统一管理操作平台**,集成了对 Linux 系统的全面操作支持(包括终端管理[终端回放、命令过滤]、文件管理、脚本执行、进程监控及计划任务设置),同时提供了多种数据库(如 MySQL、PostgreSQL、Oracle、SQL Server、达梦、高斯、SQLite、ClickHouse 等)的数据操作、数据同步与数据迁移功能。此外,还支持 Redis(单机、哨兵、集群模式)、MongoDB、Elasticsearch、Kafka、Milvus 的操作管理,并结合工单流程审批功能,为企业提供一站式的运维与管理解决方案。
|
||||
|
||||
## 开发语言与主要框架
|
||||
|
||||
### 开发语言与主要框架
|
||||
- 前端:typescript、vue3、element-plus
|
||||
- 后端:golang、gin、gorm
|
||||
|
||||
## 交流及问题反馈加 QQ 群
|
||||
|
||||
### 交流及问题反馈加 QQ 群
|
||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?jump_from=webapi">119699946</a>
|
||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=IdJSHW0jTMhmWFHBUS9a83wxtrxDDhFj&jump_from=webapi">119699946</a>
|
||||
|
||||
### 系统相关资料
|
||||
- 项目文档: https://objs.gitee.io/mayfly-go-docs
|
||||
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
|
||||
## 演示环境
|
||||
|
||||
### 系统功能
|
||||
http://go.mayfly.run
|
||||
账号/密码:test/test123.
|
||||
|
||||
记录操作记录
|
||||

|
||||
## 系统核心功能截图
|
||||
|
||||
#### 首页
|
||||
|
||||

|
||||
|
||||
#### 资源管理
|
||||
|
||||

|
||||
|
||||
#### 资源操作
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### 工单流程审批
|
||||
|
||||

|
||||
|
||||
#### 系统管理
|
||||
账号管理
|
||||

|
||||
|
||||
角色管理
|
||||

|
||||
##### 账号管理
|
||||
|
||||
资源管理
|
||||

|
||||

|
||||
|
||||
**其他更多功能&操作指南可查看在线文档**: https://objs.gitee.io/mayfly-go-docs
|
||||
##### 角色管理
|
||||
|
||||

|
||||
|
||||
##### 菜单资源管理
|
||||
|
||||

|
||||
|
||||
**其他更多功能&操作指南可查看上述项目文档**
|
||||
|
||||
## 💌 支持作者
|
||||
|
||||
如果觉得项目不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/dromara/mayfly-go">Github</a> 或 <a target="_blank" href="https://gitee.com/dromara/mayfly-go">Gitee</a> 或 <a target="_blank" href="https://gitcode.com/dromara/mayfly-go">Gitcode</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
|
||||
|
||||
> 喝杯咖啡 ☕️ 或者来杯奶茶 🧋,让作者更有精神,写出更棒的代码!
|
||||
|
||||
<img class="no-margin" src="https://foruda.gitee.com/images/1744113367791412282/36a3c23b_1240250.png" alt="微信打赏" width="200" height="200">
|
||||
|
||||
> **特别感谢:**
|
||||
> 赞助金额达 199 元以上,加微信(wx-error),可受邀进入付费交流群,享受更快、更优先的技术支持与交流服务!
|
||||
|
||||
95
README_EN.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 🌈Dromara mayfly-go
|
||||
|
||||
<p align="center">
|
||||
<a href="./README.md">中文介绍</a> |
|
||||
<a href="https://www.yuque.com/may-fly/mayfly-go">Documentation</a> |
|
||||
<a href="https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854">Operate Video</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/mayfly-go" target="_blank">
|
||||
<img src="https://gitee.com/dromara/mayfly-go/badge/star.svg?theme=white" alt="star"/>
|
||||
<img src="https://gitee.com/dromara/mayfly-go/badge/fork.svg" alt="fork"/>
|
||||
</a>
|
||||
<a href="https://github.com/dromara/mayfly-go" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/dromara/mayfly-go.svg?style=social" alt="github star"/>
|
||||
<img src="https://img.shields.io/github/forks/dromara/mayfly-go.svg?style=social" alt="github fork"/>
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/mayflygo/mayfly-go/tags" target="_blank">
|
||||
<img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
|
||||
</a>
|
||||
<a href="https://github.com/golang/go" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Golang-1.22%2B-yellow.svg" alt="golang"/>
|
||||
</a>
|
||||
<a href="https://cn.vuejs.org" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Preface
|
||||
|
||||
Web-based **Unified Management and Operation Platform**, integrating comprehensive operation support for Linux systems (including terminal management [terminal playback, command filtering], file management, script execution, process monitoring, and cronjob settings). It also provides data operation, data synchronization, and data migration for multiple databases (such as MySQL, PostgreSQL, Oracle, SQL Server, Dameng, Gauss, SQLite, ClickHouse, etc.). Additionally, it supports Redis operations (standalone, sentinel, and cluster modes) and MongoDB, Elasticsearch, Kafka, Milvus management, combined with work order process approval functionality to offer enterprises an all-in-one solution for operations and management.
|
||||
|
||||
## Development languages and major frameworks
|
||||
|
||||
- frontend:typescript、vue3、element-plus
|
||||
- backend:golang、gin、gorm
|
||||
|
||||
## Demo
|
||||
|
||||
http://go.mayfly.run
|
||||
account/password:test/test123.
|
||||
|
||||
## Screenshots of core features
|
||||
|
||||
#### Home page
|
||||
|
||||

|
||||
|
||||
#### Resource Manage
|
||||
|
||||

|
||||
|
||||
#### Resource Operation
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### Work order process approval
|
||||
|
||||

|
||||
|
||||
#### System Management
|
||||
|
||||
##### Account
|
||||
|
||||

|
||||
|
||||
##### Role
|
||||
|
||||

|
||||
|
||||
##### Menu & Permission
|
||||
|
||||

|
||||
|
||||
**Additional features & instructions can be found in the project documentation above.**
|
||||
|
||||
## 💌 Supporting Author
|
||||
|
||||
If you think the project is good, or you are already using it, I hope you can go to <a target="_blank" href="https://github.com/dromara/mayfly-go">Github</a> to help me click ⭐ Star, which will be a great encouragement and support for me.
|
||||
@@ -1,74 +0,0 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/global"
|
||||
"mayfly-go/base/utils"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func ErrIsNil(err error, msg string, params ...interface{}) {
|
||||
if err != nil {
|
||||
global.Log.Error(msg + ": " + err.Error())
|
||||
panic(NewBizErr(fmt.Sprintf(msg, params...)))
|
||||
}
|
||||
}
|
||||
|
||||
func ErrIsNilAppendErr(err error, msg string) {
|
||||
if err != nil {
|
||||
panic(NewBizErr(fmt.Sprintf(msg, err.Error())))
|
||||
}
|
||||
}
|
||||
|
||||
func IsNil(err error) {
|
||||
switch t := err.(type) {
|
||||
case *BizError:
|
||||
panic(t)
|
||||
case error:
|
||||
global.Log.Error("非业务异常: " + err.Error())
|
||||
panic(NewBizErr(fmt.Sprintf("非业务异常: %s", err.Error())))
|
||||
}
|
||||
}
|
||||
|
||||
func IsTrue(exp bool, msg string, params ...interface{}) {
|
||||
if !exp {
|
||||
panic(NewBizErr(fmt.Sprintf(msg, params...)))
|
||||
}
|
||||
}
|
||||
|
||||
func IsTrueBy(exp bool, err BizError) {
|
||||
if !exp {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func NotEmpty(str string, msg string, params ...interface{}) {
|
||||
if str == "" {
|
||||
panic(NewBizErr(fmt.Sprintf(msg, params...)))
|
||||
}
|
||||
}
|
||||
|
||||
func NotNil(data interface{}, msg string) {
|
||||
if reflect.ValueOf(data).IsNil() {
|
||||
panic(NewBizErr(msg))
|
||||
}
|
||||
}
|
||||
|
||||
func NotBlank(data interface{}, msg string) {
|
||||
if utils.IsBlank(reflect.ValueOf(data)) {
|
||||
panic(NewBizErr(msg))
|
||||
}
|
||||
}
|
||||
|
||||
func IsEquals(data interface{}, data1 interface{}, msg string) {
|
||||
if data != data1 {
|
||||
panic(NewBizErr(msg))
|
||||
}
|
||||
}
|
||||
|
||||
func Nil(data interface{}, msg string) {
|
||||
if !reflect.ValueOf(data).IsNil() {
|
||||
panic(NewBizErr(msg))
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package biz
|
||||
|
||||
// 业务错误
|
||||
type BizError struct {
|
||||
code int16
|
||||
err string
|
||||
}
|
||||
|
||||
var (
|
||||
Success *BizError = NewBizErrCode(200, "success")
|
||||
BizErr *BizError = NewBizErrCode(400, "biz error")
|
||||
ServerError *BizError = NewBizErrCode(500, "server error")
|
||||
PermissionErr *BizError = NewBizErrCode(501, "token error")
|
||||
)
|
||||
|
||||
// 错误消息
|
||||
func (e *BizError) Error() string {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// 错误码
|
||||
func (e *BizError) Code() int16 {
|
||||
return e.code
|
||||
}
|
||||
|
||||
// 创建业务逻辑错误结构体,默认为业务逻辑错误
|
||||
func NewBizErr(msg string) *BizError {
|
||||
return &BizError{code: BizErr.code, err: msg}
|
||||
}
|
||||
|
||||
// 创建业务逻辑错误结构体,可设置指定错误code
|
||||
func NewBizErrCode(code int16, msg string) *BizError {
|
||||
return &BizError{code: code, err: msg}
|
||||
}
|
||||
25
base/cache/cache.go
vendored
@@ -1,25 +0,0 @@
|
||||
package cache
|
||||
|
||||
type Cache interface {
|
||||
// 添加缓存,如果缓存则返回错误
|
||||
Add(k string, v interface{}) error
|
||||
|
||||
// 如果不存在则添加缓存值,否则直接返回
|
||||
AddIfAbsent(k string, v interface{})
|
||||
|
||||
// 如果存在则直接返回,否则调用getValue回调函数获取值并添加该缓存值
|
||||
// @return 缓存值
|
||||
ComputeIfAbsent(k string, getValueFunc func(string) (interface{}, error)) (interface{}, error)
|
||||
|
||||
// 获取缓存值,参数1为值,参数2->是否存在该缓存
|
||||
Get(k string) (interface{}, bool)
|
||||
|
||||
// 缓存数量
|
||||
Count() int
|
||||
|
||||
// 删除缓存
|
||||
Delete(k string)
|
||||
|
||||
// 清空所有缓存
|
||||
Clear()
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"mayfly-go/base/biz"
|
||||
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
var store = base64Captcha.DefaultMemStore
|
||||
var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit
|
||||
|
||||
// 生成验证码
|
||||
func Generate() (string, string) {
|
||||
c := base64Captcha.NewCaptcha(driver, store)
|
||||
// 获取
|
||||
id, b64s, err := c.Generate()
|
||||
biz.ErrIsNilAppendErr(err, "获取验证码错误: %s")
|
||||
return id, b64s
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
func Verify(id string, val string) bool {
|
||||
if id == "" || val == "" {
|
||||
return false
|
||||
}
|
||||
// 同时清理掉这个图片
|
||||
return store.Verify(id, val, true)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
type App struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
func (a *App) GetAppInfo() string {
|
||||
return fmt.Sprintf("[%s:%s]", a.Name, a.Version)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"mayfly-go/base/utils"
|
||||
"mayfly-go/base/utils/assert"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 配置文件映射对象
|
||||
var Conf *Config
|
||||
|
||||
func init() {
|
||||
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
|
||||
flag.Parse()
|
||||
// 获取启动参数中,配置文件的绝对路径
|
||||
path, _ := filepath.Abs(*configFilePath)
|
||||
startConfigParam = &CmdConfigParam{ConfigFilePath: path}
|
||||
// 读取配置文件信息
|
||||
yc := &Config{}
|
||||
if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
|
||||
panic(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))
|
||||
}
|
||||
// 校验配置文件内容信息
|
||||
yc.Valid()
|
||||
Conf = yc
|
||||
}
|
||||
|
||||
// 启动配置参数
|
||||
type CmdConfigParam struct {
|
||||
ConfigFilePath string // -e 配置文件路径
|
||||
}
|
||||
|
||||
// 启动可执行文件时的参数
|
||||
var startConfigParam *CmdConfigParam
|
||||
|
||||
// yaml配置文件映射对象
|
||||
type Config struct {
|
||||
App *App `yaml:"app"`
|
||||
Server *Server `yaml:"server"`
|
||||
Jwt *Jwt `yaml:"jwt"`
|
||||
Redis *Redis `yaml:"redis"`
|
||||
Mysql *Mysql `yaml:"mysql"`
|
||||
Log *Log `yaml:"log"`
|
||||
}
|
||||
|
||||
// 配置文件内容校验
|
||||
func (c *Config) Valid() {
|
||||
assert.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空")
|
||||
c.Jwt.Valid()
|
||||
}
|
||||
|
||||
// 获取执行可执行文件时,指定的启动参数
|
||||
func getStartConfig() *CmdConfigParam {
|
||||
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
|
||||
flag.Parse()
|
||||
// 获取配置文件绝对路径
|
||||
path, _ := filepath.Abs(*configFilePath)
|
||||
sc := &CmdConfigParam{ConfigFilePath: path}
|
||||
return sc
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package config
|
||||
|
||||
import "mayfly-go/base/utils/assert"
|
||||
|
||||
type Jwt struct {
|
||||
Key string `yaml:"key"`
|
||||
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
||||
}
|
||||
|
||||
func (j *Jwt) Valid() {
|
||||
assert.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空")
|
||||
assert.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空")
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package config
|
||||
|
||||
import "path"
|
||||
|
||||
type Log struct {
|
||||
Level string `yaml:"level"`
|
||||
File *LogFile `yaml:"file"`
|
||||
}
|
||||
|
||||
type LogFile struct {
|
||||
Name string `yaml:"name"`
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
// 获取完整路径文件名
|
||||
func (l *LogFile) GetFilename() string {
|
||||
var filepath, filename string
|
||||
if fp := l.Path; fp == "" {
|
||||
filepath = "./"
|
||||
} else {
|
||||
filepath = fp
|
||||
}
|
||||
if fn := l.Name; fn == "" {
|
||||
filename = "default.log"
|
||||
} else {
|
||||
filename = fn
|
||||
}
|
||||
|
||||
return path.Join(filepath, filename)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package config
|
||||
|
||||
type Mysql struct {
|
||||
Host string `mapstructure:"path" json:"host" yaml:"host"`
|
||||
Config string `mapstructure:"config" json:"config" yaml:"config"`
|
||||
Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`
|
||||
Username string `mapstructure:"username" json:"username" yaml:"username"`
|
||||
Password string `mapstructure:"password" json:"password" yaml:"password"`
|
||||
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"`
|
||||
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"`
|
||||
LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`
|
||||
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
|
||||
}
|
||||
|
||||
func (m *Mysql) Dsn() string {
|
||||
return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package config
|
||||
|
||||
type Redis struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Password string `yaml:"password"`
|
||||
Db int `yaml:"db"`
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Server struct {
|
||||
Port int `yaml:"port"`
|
||||
Model string `yaml:"model"`
|
||||
Cors bool `yaml:"cors"`
|
||||
Tls *Tls `yaml:"tls"`
|
||||
Static *[]*Static `yaml:"static"`
|
||||
StaticFile *[]*StaticFile `yaml:"static-file"`
|
||||
}
|
||||
|
||||
func (s *Server) GetPort() string {
|
||||
return fmt.Sprintf(":%d", s.Port)
|
||||
}
|
||||
|
||||
type Static struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Root string `yaml:"root"`
|
||||
}
|
||||
|
||||
type StaticFile struct {
|
||||
RelativePath string `yaml:"relative-path"`
|
||||
Filepath string `yaml:"filepath"`
|
||||
}
|
||||
|
||||
type Tls struct {
|
||||
Enable bool `yaml:"enable"` // 是否启用tls
|
||||
KeyFile string `yaml:"key-file"` // 私钥文件路径
|
||||
CertFile string `yaml:"cert-file"` // 证书文件路径
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/logger"
|
||||
"mayfly-go/base/utils"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type LogInfo struct {
|
||||
LogResp bool // 是否记录返回结果
|
||||
Description string // 请求描述
|
||||
}
|
||||
|
||||
func NewLogInfo(description string) *LogInfo {
|
||||
return &LogInfo{Description: description, LogResp: false}
|
||||
}
|
||||
|
||||
func (i *LogInfo) WithLogResp(logResp bool) *LogInfo {
|
||||
i.LogResp = logResp
|
||||
return i
|
||||
}
|
||||
|
||||
func LogHandler(rc *ReqCtx) error {
|
||||
li := rc.LogInfo
|
||||
if li == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lfs := logrus.Fields{}
|
||||
if la := rc.LoginAccount; la != nil {
|
||||
lfs["uid"] = la.Id
|
||||
lfs["uname"] = la.Username
|
||||
}
|
||||
|
||||
req := rc.GinCtx.Request
|
||||
lfs[req.Method] = req.URL.Path
|
||||
|
||||
if err := rc.Err; err != nil {
|
||||
logger.Log.WithFields(lfs).Error(getErrMsg(rc, err))
|
||||
return nil
|
||||
}
|
||||
logger.Log.WithFields(lfs).Info(getLogMsg(rc))
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLogMsg(rc *ReqCtx) string {
|
||||
msg := rc.LogInfo.Description + fmt.Sprintf(" ->%dms", rc.timed)
|
||||
if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
|
||||
rb, _ := json.Marshal(rc.ReqParam)
|
||||
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
|
||||
}
|
||||
|
||||
// 返回结果不为空,则记录返回结果
|
||||
if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) {
|
||||
respB, _ := json.Marshal(rc.ResData)
|
||||
msg = msg + fmt.Sprintf("\n<-- %s", string(respB))
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func getErrMsg(rc *ReqCtx, err interface{}) string {
|
||||
msg := rc.LogInfo.Description
|
||||
if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
|
||||
rb, _ := json.Marshal(rc.ReqParam)
|
||||
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
|
||||
}
|
||||
|
||||
var errMsg string
|
||||
switch t := err.(type) {
|
||||
case *biz.BizError:
|
||||
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
|
||||
case error:
|
||||
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack()))
|
||||
case string:
|
||||
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack()))
|
||||
}
|
||||
return (msg + errMsg)
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/cache"
|
||||
"mayfly-go/base/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Permission struct {
|
||||
NeedToken bool // 是否需要token
|
||||
Code string // 权限code
|
||||
}
|
||||
|
||||
func NewPermission(code string) *Permission {
|
||||
return &Permission{NeedToken: true, Code: code}
|
||||
}
|
||||
|
||||
func (p *Permission) WithNeedToken(needToken bool) *Permission {
|
||||
p.NeedToken = needToken
|
||||
return p
|
||||
}
|
||||
|
||||
type PermissionCodeRegistry interface {
|
||||
// 保存用户权限code
|
||||
SaveCodes(userId uint64, codes []string)
|
||||
|
||||
// 判断用户是否拥有该code的权限
|
||||
HasCode(userId uint64, code string) bool
|
||||
|
||||
Remove(userId uint64)
|
||||
}
|
||||
|
||||
type DefaultPermissionCodeRegistry struct {
|
||||
cache *cache.TimedCache
|
||||
}
|
||||
|
||||
func (r *DefaultPermissionCodeRegistry) SaveCodes(userId uint64, codes []string) {
|
||||
if r.cache == nil {
|
||||
r.cache = cache.NewTimedCache(time.Minute*time.Duration(config.Conf.Jwt.ExpireTime), 5*time.Second)
|
||||
}
|
||||
r.cache.Put(fmt.Sprintf("%v", userId), codes)
|
||||
}
|
||||
|
||||
func (r *DefaultPermissionCodeRegistry) HasCode(userId uint64, code string) bool {
|
||||
if r.cache == nil {
|
||||
return false
|
||||
}
|
||||
codes, found := r.cache.Get(fmt.Sprintf("%v", userId))
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
for _, v := range codes.([]string) {
|
||||
if v == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *DefaultPermissionCodeRegistry) Remove(userId uint64) {
|
||||
r.cache.Delete(fmt.Sprintf("%v", userId))
|
||||
}
|
||||
|
||||
// 保存用户权限code
|
||||
func SavePermissionCodes(userId uint64, codes []string) {
|
||||
permissionCodeRegistry.SaveCodes(userId, codes)
|
||||
}
|
||||
|
||||
// 删除用户权限code
|
||||
func DeletePermissionCodes(userId uint64) {
|
||||
permissionCodeRegistry.Remove(userId)
|
||||
}
|
||||
|
||||
// 设置权限code注册器
|
||||
func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) {
|
||||
permissionCodeRegistry = pcr
|
||||
}
|
||||
|
||||
var (
|
||||
permissionCodeRegistry PermissionCodeRegistry = &DefaultPermissionCodeRegistry{}
|
||||
// permissionError = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg)
|
||||
)
|
||||
|
||||
func PermissionHandler(rc *ReqCtx) error {
|
||||
permission := rc.RequiredPermission
|
||||
// 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑
|
||||
if permission != nil && !permission.NeedToken {
|
||||
return nil
|
||||
}
|
||||
tokenStr := rc.GinCtx.Request.Header.Get("Authorization")
|
||||
// header不存在则从查询参数token中获取
|
||||
if tokenStr == "" {
|
||||
tokenStr = rc.GinCtx.Query("token")
|
||||
}
|
||||
if tokenStr == "" {
|
||||
return biz.PermissionErr
|
||||
}
|
||||
loginAccount, err := ParseToken(tokenStr)
|
||||
if err != nil || loginAccount == nil {
|
||||
return biz.PermissionErr
|
||||
}
|
||||
// 权限不为nil,并且permission code不为空,则校验是否有权限code
|
||||
if permission != nil && permission.Code != "" {
|
||||
if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) {
|
||||
return biz.PermissionErr
|
||||
}
|
||||
}
|
||||
|
||||
rc.LoginAccount = loginAccount
|
||||
return nil
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mayfly-go/base/ginx"
|
||||
"mayfly-go/base/model"
|
||||
"mayfly-go/base/utils/assert"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 处理函数
|
||||
type HandlerFunc func(*ReqCtx)
|
||||
|
||||
type ReqCtx struct {
|
||||
GinCtx *gin.Context // gin context
|
||||
|
||||
// NeedToken bool // 是否需要token
|
||||
RequiredPermission *Permission // 需要的权限信息,默认为nil,需要校验token
|
||||
LoginAccount *model.LoginAccount // 登录账号信息,只有校验token后才会有值
|
||||
|
||||
LogInfo *LogInfo // 日志相关信息
|
||||
ReqParam interface{} // 请求参数,主要用于记录日志
|
||||
ResData interface{} // 响应结果
|
||||
Err interface{} // 请求错误
|
||||
|
||||
timed int64 // 执行时间
|
||||
noRes bool // 无需返回结果,即文件下载等
|
||||
}
|
||||
|
||||
func (rc *ReqCtx) Handle(handler HandlerFunc) {
|
||||
ginCtx := rc.GinCtx
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
rc.Err = err
|
||||
ginx.ErrorRes(ginCtx, err)
|
||||
}
|
||||
// 应用所有请求后置处理器
|
||||
ApplyHandlerInterceptor(afterHandlers, rc)
|
||||
}()
|
||||
assert.IsTrue(ginCtx != nil, "ginContext == nil")
|
||||
|
||||
// 默认为不记录请求参数,可在handler回调函数中覆盖赋值
|
||||
rc.ReqParam = 0
|
||||
// 默认响应结果为nil,可在handler中赋值
|
||||
rc.ResData = nil
|
||||
|
||||
// 调用请求前所有处理器
|
||||
err := ApplyHandlerInterceptor(beforeHandlers, rc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
begin := time.Now()
|
||||
handler(rc)
|
||||
rc.timed = time.Now().Sub(begin).Milliseconds()
|
||||
if !rc.noRes {
|
||||
ginx.SuccessRes(ginCtx, rc.ResData)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *ReqCtx) Download(reader io.Reader, filename string) {
|
||||
rc.noRes = true
|
||||
ginx.Download(rc.GinCtx, reader, filename)
|
||||
}
|
||||
|
||||
// 新建请求上下文,默认需要校验token
|
||||
func NewReqCtx() *ReqCtx {
|
||||
return &ReqCtx{}
|
||||
}
|
||||
|
||||
func NewReqCtxWithGin(g *gin.Context) *ReqCtx {
|
||||
return &ReqCtx{GinCtx: g}
|
||||
}
|
||||
|
||||
// 调用该方法设置请求描述,则默认记录日志,并不记录响应结果
|
||||
func (r *ReqCtx) WithLog(li *LogInfo) *ReqCtx {
|
||||
r.LogInfo = li
|
||||
return r
|
||||
}
|
||||
|
||||
// 设置请求上下文需要的权限信息
|
||||
func (r *ReqCtx) WithRequiredPermission(permission *Permission) *ReqCtx {
|
||||
r.RequiredPermission = permission
|
||||
return r
|
||||
}
|
||||
|
||||
// 是否需要token
|
||||
func (r *ReqCtx) WithNeedToken(needToken bool) *ReqCtx {
|
||||
r.RequiredPermission = &Permission{NeedToken: false}
|
||||
return r
|
||||
}
|
||||
|
||||
// 处理器拦截器函数
|
||||
type HandlerInterceptorFunc func(*ReqCtx) error
|
||||
type HandlerInterceptors []HandlerInterceptorFunc
|
||||
|
||||
var (
|
||||
beforeHandlers HandlerInterceptors
|
||||
afterHandlers HandlerInterceptors
|
||||
)
|
||||
|
||||
// 使用前置处理器函数
|
||||
func UseBeforeHandlerInterceptor(b HandlerInterceptorFunc) {
|
||||
beforeHandlers = append(beforeHandlers, b)
|
||||
}
|
||||
|
||||
// 使用后置处理器函数
|
||||
func UseAfterHandlerInterceptor(b HandlerInterceptorFunc) {
|
||||
afterHandlers = append(afterHandlers, b)
|
||||
}
|
||||
|
||||
// 应用指定处理器拦截器,如果有一个错误则直接返回错误
|
||||
func ApplyHandlerInterceptor(his HandlerInterceptors, rc *ReqCtx) interface{} {
|
||||
for _, handler := range his {
|
||||
if err := handler(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/model"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
var (
|
||||
JwtKey = config.Conf.Jwt.Key
|
||||
ExpTime = config.Conf.Jwt.ExpireTime
|
||||
)
|
||||
|
||||
// 创建用户token
|
||||
func CreateToken(userId uint64, username string) string {
|
||||
// 带权限创建令牌
|
||||
// 设置有效期,过期需要重新登录获取token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": userId,
|
||||
"username": username,
|
||||
"exp": time.Now().Add(time.Minute * time.Duration(ExpTime)).Unix(),
|
||||
})
|
||||
|
||||
// 使用自定义字符串加密 and get the complete encoded token as a string
|
||||
tokenString, err := token.SignedString([]byte(JwtKey))
|
||||
biz.ErrIsNil(err, "token创建失败")
|
||||
return tokenString
|
||||
}
|
||||
|
||||
// 解析token,并返回登录者账号信息
|
||||
func ParseToken(tokenStr string) (*model.LoginAccount, error) {
|
||||
if tokenStr == "" {
|
||||
return nil, errors.New("token error")
|
||||
}
|
||||
// Parse token
|
||||
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(JwtKey), nil
|
||||
})
|
||||
if err != nil || token == nil {
|
||||
return nil, err
|
||||
}
|
||||
i := token.Claims.(jwt.MapClaims)
|
||||
return &model.LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package ginx
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/global"
|
||||
"mayfly-go/base/model"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 绑定并校验请求结构体参数
|
||||
func BindJsonAndValid(g *gin.Context, data interface{}) {
|
||||
if err := g.ShouldBindJSON(data); err != nil {
|
||||
panic(biz.NewBizErr(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定查询字符串到
|
||||
func BindQuery(g *gin.Context, data interface{}) {
|
||||
if err := g.ShouldBindQuery(data); err != nil {
|
||||
panic(biz.NewBizErr(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分页参数
|
||||
func GetPageParam(g *gin.Context) *model.PageParam {
|
||||
return &model.PageParam{PageNum: QueryInt(g, "pageNum", 1), PageSize: QueryInt(g, "pageSize", 10)}
|
||||
}
|
||||
|
||||
// 获取查询参数中指定参数值,并转为int
|
||||
func QueryInt(g *gin.Context, qm string, defaultInt int) int {
|
||||
qv := g.Query(qm)
|
||||
if qv == "" {
|
||||
return defaultInt
|
||||
}
|
||||
qvi, err := strconv.Atoi(qv)
|
||||
biz.ErrIsNil(err, "query param not int")
|
||||
return qvi
|
||||
}
|
||||
|
||||
// 获取路径参数
|
||||
func PathParamInt(g *gin.Context, pm string) int {
|
||||
value, _ := strconv.Atoi(g.Param(pm))
|
||||
return value
|
||||
}
|
||||
|
||||
// 文件下载
|
||||
func Download(g *gin.Context, reader io.Reader, filename string) {
|
||||
g.Header("Content-Type", "application/octet-stream")
|
||||
g.Header("Content-Disposition", "attachment; filename="+filename)
|
||||
io.Copy(g.Writer, reader)
|
||||
}
|
||||
|
||||
// 返回统一成功结果
|
||||
func SuccessRes(g *gin.Context, data interface{}) {
|
||||
g.JSON(http.StatusOK, model.Success(data))
|
||||
}
|
||||
|
||||
// 返回失败结果集
|
||||
func ErrorRes(g *gin.Context, err interface{}) {
|
||||
switch t := err.(type) {
|
||||
case *biz.BizError:
|
||||
g.JSON(http.StatusOK, model.Error(t))
|
||||
case error:
|
||||
g.JSON(http.StatusOK, model.ServerError())
|
||||
global.Log.Error(t)
|
||||
// panic(err)
|
||||
case string:
|
||||
g.JSON(http.StatusOK, model.ServerError())
|
||||
global.Log.Error(t)
|
||||
// panic(err)
|
||||
default:
|
||||
global.Log.Error(t)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
Log *logrus.Logger // 日志
|
||||
Db *gorm.DB // gorm
|
||||
RedisCli *redis.Client // redis
|
||||
)
|
||||
@@ -1,228 +0,0 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = &http.Client{}
|
||||
|
||||
// 默认超时
|
||||
const DefTimeout = 60
|
||||
|
||||
type RequestWrapper struct {
|
||||
url string
|
||||
method string
|
||||
timeout int
|
||||
body io.Reader
|
||||
header map[string]string
|
||||
}
|
||||
|
||||
type MultipartFile struct {
|
||||
FieldName string // 字段名
|
||||
FileName string // 文件名
|
||||
FilePath string // 文件路径,文件路径不为空,则优先读取文件路径的内容
|
||||
Bytes []byte // 文件内容
|
||||
}
|
||||
|
||||
// 创建一个请求
|
||||
func NewRequest(url string) *RequestWrapper {
|
||||
return &RequestWrapper{url: url}
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Url(url string) *RequestWrapper {
|
||||
r.url = url
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Header(name, value string) *RequestWrapper {
|
||||
if r.header == nil {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header[name] = value
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Timeout(timeout int) *RequestWrapper {
|
||||
r.timeout = timeout
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) GetByParam(paramMap map[string]string) *ResponseWrapper {
|
||||
var params string
|
||||
for k, v := range paramMap {
|
||||
if params != "" {
|
||||
params += "&"
|
||||
} else {
|
||||
params += "?"
|
||||
}
|
||||
params += k + "=" + v
|
||||
}
|
||||
r.url += "?" + params
|
||||
return r.Get()
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Get() *ResponseWrapper {
|
||||
r.method = "GET"
|
||||
r.body = nil
|
||||
return request(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
|
||||
buf := bytes.NewBufferString(body)
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
if r.header == nil {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = "application/json"
|
||||
return request(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostObj(body interface{}) *ResponseWrapper {
|
||||
marshal, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return createRequestError(errors.New("解析json obj错误"))
|
||||
}
|
||||
return r.PostJson(string(marshal))
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostParams(params string) *ResponseWrapper {
|
||||
buf := bytes.NewBufferString(params)
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
if r.header == nil {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = "application/x-www-form-urlencoded"
|
||||
return request(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[string]string) *ResponseWrapper {
|
||||
buf := &bytes.Buffer{}
|
||||
// 文件写入 buf
|
||||
writer := multipart.NewWriter(buf)
|
||||
for _, uploadFile := range files {
|
||||
var reader io.Reader
|
||||
// 如果文件路径不为空,则读取该路径文件,否则使用bytes
|
||||
if uploadFile.FilePath != "" {
|
||||
file, err := os.Open(uploadFile.FilePath)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
defer file.Close()
|
||||
reader = file
|
||||
} else {
|
||||
reader = bytes.NewBuffer(uploadFile.Bytes)
|
||||
}
|
||||
|
||||
part, err := writer.CreateFormFile(uploadFile.FieldName, uploadFile.FileName)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
_, err = io.Copy(part, reader)
|
||||
}
|
||||
// 如果有其他参数,则写入body
|
||||
for k, v := range reqParams {
|
||||
if err := writer.WriteField(k, v); err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
if r.header == nil {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = writer.FormDataContentType()
|
||||
return request(r)
|
||||
}
|
||||
|
||||
type ResponseWrapper struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) IsSuccess() bool {
|
||||
return r.StatusCode == 200
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToObj(objPtr interface{}) error {
|
||||
_ = json.Unmarshal(r.Body, &objPtr)
|
||||
return r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToString() (string, error) {
|
||||
return string(r.Body), r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToMap() (map[string]interface{}, error) {
|
||||
var res map[string]interface{}
|
||||
err := json.Unmarshal(r.Body, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) getError() error {
|
||||
if !r.IsSuccess() {
|
||||
return errors.New(string(r.Body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func request(rw *RequestWrapper) *ResponseWrapper {
|
||||
wrapper := &ResponseWrapper{StatusCode: 0, Header: make(http.Header)}
|
||||
timeout := rw.timeout
|
||||
if timeout > 0 {
|
||||
client.Timeout = time.Duration(timeout) * time.Second
|
||||
} else {
|
||||
timeout = DefTimeout
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(rw.method, rw.url, rw.body)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
}
|
||||
setRequestHeader(req, rw.header)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
wrapper.Body = []byte(fmt.Sprintf("执行HTTP请求错误-%s", err.Error()))
|
||||
return wrapper
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
wrapper.Body = []byte(fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error()))
|
||||
return wrapper
|
||||
}
|
||||
wrapper.StatusCode = resp.StatusCode
|
||||
wrapper.Body = body
|
||||
wrapper.Header = resp.Header
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
func setRequestHeader(req *http.Request, header map[string]string) {
|
||||
req.Header.Set("User-Agent", "golang/mayfly")
|
||||
for k, v := range header {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func createRequestError(err error) *ResponseWrapper {
|
||||
return &ResponseWrapper{0, []byte(fmt.Sprintf("创建HTTP请求错误-%s", err.Error())), make(http.Header)}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var Log = logrus.New()
|
||||
|
||||
func init() {
|
||||
Log.SetFormatter(new(LogFormatter))
|
||||
Log.SetReportCaller(true)
|
||||
|
||||
logConf := config.Conf.Log
|
||||
// 如果不存在日志配置信息,则默认debug级别
|
||||
if logConf == nil {
|
||||
Log.SetLevel(logrus.DebugLevel)
|
||||
return
|
||||
}
|
||||
|
||||
// 根据配置文件设置日志级别
|
||||
if level := logConf.Level; level != "" {
|
||||
l, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("日志级别不存在: %s", level))
|
||||
}
|
||||
Log.SetLevel(l)
|
||||
} else {
|
||||
Log.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
if logFile := logConf.File; logFile != nil {
|
||||
//写入文件
|
||||
file, err := os.OpenFile(logFile.GetFilename(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
|
||||
}
|
||||
|
||||
Log.Out = file
|
||||
}
|
||||
|
||||
global.Log = Log
|
||||
}
|
||||
|
||||
type LogFormatter struct{}
|
||||
|
||||
func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
timestamp := time.Now().Local().Format("2006-01-02 15:04:05.000")
|
||||
level := entry.Level
|
||||
logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String()))
|
||||
// 如果存在调用信息,记录方法信息及行号
|
||||
if caller := entry.Caller; caller != nil {
|
||||
logMsg = logMsg + fmt.Sprintf(" [%s:%d]", caller.Function, caller.Line)
|
||||
}
|
||||
for k, v := range entry.Data {
|
||||
logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v)
|
||||
}
|
||||
logMsg = logMsg + fmt.Sprintf(" : %s\n", entry.Message)
|
||||
return []byte(logMsg), nil
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package model
|
||||
|
||||
type AppContext struct {
|
||||
}
|
||||
|
||||
type LoginAccount struct {
|
||||
Id uint64
|
||||
Username string
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
CheckToken bool // 是否检查token
|
||||
Code string // 权限码
|
||||
Name string // 描述
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/global"
|
||||
"strconv"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id uint64 `json:"id"`
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
CreatorId uint64 `json:"creatorId"`
|
||||
Creator string `json:"creator"`
|
||||
UpdateTime *time.Time `json:"updateTime"`
|
||||
ModifierId uint64 `json:"modifierId"`
|
||||
Modifier string `json:"modifier"`
|
||||
}
|
||||
|
||||
// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息
|
||||
func (m *Model) SetBaseInfo(account *LoginAccount) {
|
||||
nowTime := time.Now()
|
||||
isCreate := m.Id == 0
|
||||
if isCreate {
|
||||
m.CreateTime = &nowTime
|
||||
}
|
||||
m.UpdateTime = &nowTime
|
||||
|
||||
if account == nil {
|
||||
return
|
||||
}
|
||||
id := account.Id
|
||||
name := account.Username
|
||||
if isCreate {
|
||||
m.CreatorId = id
|
||||
m.Creator = name
|
||||
}
|
||||
m.Modifier = name
|
||||
m.ModifierId = id
|
||||
}
|
||||
|
||||
func Tx(funcs ...func(db *gorm.DB) error) (err error) {
|
||||
tx := global.Db.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
err = fmt.Errorf("%v", err)
|
||||
}
|
||||
}()
|
||||
for _, f := range funcs {
|
||||
err = f(tx)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
}
|
||||
err = tx.Commit().Error
|
||||
return
|
||||
}
|
||||
|
||||
// 根据id获取实体对象。model需为指针类型(需要将查询出来的值赋值给model)
|
||||
//
|
||||
// 若error不为nil则为不存在该记录
|
||||
func GetById(model interface{}, id uint64, cols ...string) error {
|
||||
return global.Db.Select(cols).Where("id = ?", id).First(model).Error
|
||||
}
|
||||
|
||||
// 根据id列表查询
|
||||
func GetByIdIn(model interface{}, list interface{}, ids []uint64, orderBy ...string) {
|
||||
var orderByStr string
|
||||
if orderBy == nil {
|
||||
orderByStr = "id desc"
|
||||
} else {
|
||||
orderByStr = strings.Join(orderBy, ",")
|
||||
}
|
||||
global.Db.Model(model).Where("id in (?)", ids).Order(orderByStr).Find(list)
|
||||
}
|
||||
|
||||
// 根据id列表查询
|
||||
func CountBy(model interface{}) int64 {
|
||||
var count int64
|
||||
global.Db.Model(model).Where(model).Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
// 根据id更新model,更新字段为model中不为空的值,即int类型不为0,ptr类型不为nil这类字段值
|
||||
func UpdateById(model interface{}) error {
|
||||
return global.Db.Model(model).Updates(model).Error
|
||||
}
|
||||
|
||||
// 根据id删除model
|
||||
func DeleteById(model interface{}, id uint64) error {
|
||||
return global.Db.Delete(model, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// 根据条件删除
|
||||
func DeleteByCondition(model interface{}) error {
|
||||
return global.Db.Where(model).Delete(model).Error
|
||||
}
|
||||
|
||||
// 插入model
|
||||
func Insert(model interface{}) error {
|
||||
return global.Db.Create(model).Error
|
||||
}
|
||||
|
||||
// 获取满足model中不为空的字段值条件的所有数据.
|
||||
//
|
||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体,即只包含需要返回的字段结构体
|
||||
func ListBy(model interface{}, list interface{}, cols ...string) {
|
||||
global.Db.Model(model).Select(cols).Where(model).Find(list)
|
||||
}
|
||||
|
||||
// 获取满足model中不为空的字段值条件的所有数据.
|
||||
//
|
||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体
|
||||
func ListByOrder(model interface{}, list interface{}, order ...string) {
|
||||
var orderByStr string
|
||||
if order == nil {
|
||||
orderByStr = "id desc"
|
||||
} else {
|
||||
orderByStr = strings.Join(order, ",")
|
||||
}
|
||||
global.Db.Model(model).Where(model).Order(orderByStr).Find(list)
|
||||
}
|
||||
|
||||
// 获取满足model中不为空的字段值条件的单个对象。model需为指针类型(需要将查询出来的值赋值给model)
|
||||
//
|
||||
// 若 error不为nil,则为不存在该记录
|
||||
func GetBy(model interface{}, cols ...string) error {
|
||||
return global.Db.Select(cols).Where(model).First(model).Error
|
||||
}
|
||||
|
||||
// 获取满足conditionModel中不为空的字段值条件的单个对象。model需为指针类型(需要将查询出来的值赋值给model)
|
||||
// @param toModel 需要查询的字段
|
||||
// 若 error不为nil,则为不存在该记录
|
||||
func GetByConditionTo(conditionModel interface{}, toModel interface{}) error {
|
||||
return global.Db.Model(conditionModel).Where(conditionModel).First(toModel).Error
|
||||
}
|
||||
|
||||
// 获取分页结果
|
||||
func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interface{}, orderBy ...string) *PageResult {
|
||||
var count int64
|
||||
err := global.Db.Model(conditionModel).Where(conditionModel).Count(&count).Error
|
||||
biz.ErrIsNilAppendErr(err, " 查询错误:%s")
|
||||
if count == 0 {
|
||||
return &PageResult{Total: 0, List: []string{}}
|
||||
}
|
||||
|
||||
page := pageParam.PageNum
|
||||
pageSize := pageParam.PageSize
|
||||
var orderByStr string
|
||||
if orderBy == nil {
|
||||
orderByStr = "id desc"
|
||||
} else {
|
||||
orderByStr = strings.Join(orderBy, ",")
|
||||
}
|
||||
err = global.Db.Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error
|
||||
biz.ErrIsNil(err, "查询失败")
|
||||
return &PageResult{Total: count, List: toModels}
|
||||
}
|
||||
|
||||
// 根据sql获取分页对象
|
||||
func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...interface{}) *PageResult {
|
||||
db := global.Db
|
||||
selectIndex := strings.Index(sql, "SELECT ") + 7
|
||||
fromIndex := strings.Index(sql, " FROM")
|
||||
selectCol := sql[selectIndex:fromIndex]
|
||||
countSql := strings.Replace(sql, selectCol, "COUNT(*) AS total ", 1)
|
||||
// 查询count
|
||||
var count int
|
||||
err := db.Raw(countSql, args...).Scan(&count).Error
|
||||
biz.ErrIsNilAppendErr(err, "查询失败: %s")
|
||||
if count == 0 {
|
||||
return &PageResult{Total: 0, List: []string{}}
|
||||
}
|
||||
// 分页查询
|
||||
limitSql := sql + " LIMIT " + strconv.Itoa((param.PageNum-1)*param.PageSize) + ", " + strconv.Itoa(param.PageSize)
|
||||
err = db.Raw(limitSql).Scan(toModel).Error
|
||||
biz.ErrIsNil(err, "查询失败: %s")
|
||||
return &PageResult{Total: int64(count), List: toModel}
|
||||
}
|
||||
|
||||
func GetListBySql(sql string, params ...interface{}) []map[string]interface{} {
|
||||
var maps []map[string]interface{}
|
||||
global.Db.Raw(sql, params...).Scan(&maps)
|
||||
return maps
|
||||
}
|
||||
|
||||
func GetListBySql2Model(sql string, toEntity interface{}, params ...interface{}) error {
|
||||
return global.Db.Raw(sql, params...).Find(toEntity).Error
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package model
|
||||
|
||||
// 分页参数
|
||||
type PageParam struct {
|
||||
PageNum int `json:"pageNum"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
// 分页结果
|
||||
type PageResult struct {
|
||||
Total int64 `json:"total"`
|
||||
List interface{} `json:"list"`
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package rediscli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
var cli *redis.Client
|
||||
|
||||
func SetCli(client *redis.Client) {
|
||||
cli = client
|
||||
}
|
||||
|
||||
func GetCli() *redis.Client {
|
||||
return cli
|
||||
}
|
||||
|
||||
// get key value
|
||||
func Get(key string) string {
|
||||
val, err := cli.Get(key).Result()
|
||||
switch {
|
||||
case err == redis.Nil:
|
||||
fmt.Println("key does not exist")
|
||||
case err != nil:
|
||||
fmt.Println("Get failed", err)
|
||||
case val == "":
|
||||
fmt.Println("value is empty")
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// set key value
|
||||
func Set(key string, val string, expiration time.Duration) {
|
||||
cli.Set(key, val, expiration)
|
||||
}
|
||||
|
||||
func HSet(key string, field string, val interface{}) {
|
||||
cli.HSet(key, field, val)
|
||||
}
|
||||
|
||||
// hget
|
||||
func HGet(key string, field string) string {
|
||||
val, _ := cli.HGet(key, field).Result()
|
||||
return val
|
||||
}
|
||||
|
||||
// hget
|
||||
func HExist(key string, field string) bool {
|
||||
val, _ := cli.HExists(key, field).Result()
|
||||
return val
|
||||
}
|
||||
|
||||
// hgetall
|
||||
func HGetAll(key string) map[string]string {
|
||||
vals, _ := cli.HGetAll(key).Result()
|
||||
return vals
|
||||
}
|
||||
|
||||
// hdel
|
||||
func HDel(key string, fields ...string) int {
|
||||
return int(cli.HDel(key, fields...).Val())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/base/global"
|
||||
)
|
||||
|
||||
func PrintBanner() {
|
||||
global.Log.Print(`
|
||||
__ _
|
||||
_ __ ___ __ _ _ _ / _| |_ _ __ _ ___
|
||||
| '_ ' _ \ / _' | | | | |_| | | | |_____ / _' |/ _ \
|
||||
| | | | | | (_| | |_| | _| | |_| |_____| (_| | (_) |
|
||||
|_| |_| |_|\__,_|\__, |_| |_|\__, | \__, |\___/
|
||||
|___/ |___/ |___/
|
||||
`)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func InitDb() {
|
||||
global.Db = GormMysql()
|
||||
}
|
||||
|
||||
func GormMysql() *gorm.DB {
|
||||
m := config.Conf.Mysql
|
||||
if m == nil || m.Dbname == "" {
|
||||
global.Log.Panic("未找到数据库配置信息")
|
||||
return nil
|
||||
}
|
||||
global.Log.Infof("连接mysql [%s]", m.Host)
|
||||
mysqlConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
|
||||
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
|
||||
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
ormConfig := &gorm.Config{NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
}, Logger: logger.Default.LogMode(logger.Silent)}
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig); err != nil {
|
||||
global.Log.Panicf("连接mysql失败! [%s]", err.Error())
|
||||
return nil
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
|
||||
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
|
||||
return db
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/global"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
func InitRedis() {
|
||||
global.RedisCli = ConnRedis()
|
||||
}
|
||||
|
||||
func ConnRedis() *redis.Client {
|
||||
// 设置redis客户端
|
||||
redisConf := config.Conf.Redis
|
||||
if redisConf == nil {
|
||||
global.Log.Panic("未找到redis配置信息")
|
||||
return nil
|
||||
}
|
||||
global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port)
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", redisConf.Host, redisConf.Port),
|
||||
Password: redisConf.Password, // no password set
|
||||
DB: redisConf.Db, // use default DB
|
||||
})
|
||||
// 测试连接
|
||||
_, e := rdb.Ping().Result()
|
||||
if e != nil {
|
||||
global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d]", redisConf.Host, redisConf.Port))
|
||||
}
|
||||
return rdb
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"mayfly-go/base/biz"
|
||||
"mayfly-go/base/config"
|
||||
"mayfly-go/base/ctx"
|
||||
"mayfly-go/base/global"
|
||||
"mayfly-go/server/initialize"
|
||||
)
|
||||
|
||||
func RunWebServer() {
|
||||
// 权限处理器
|
||||
ctx.UseBeforeHandlerInterceptor(ctx.PermissionHandler)
|
||||
// 日志处理器
|
||||
ctx.UseAfterHandlerInterceptor(ctx.LogHandler)
|
||||
// 注册路由
|
||||
web := initialize.InitRouter()
|
||||
|
||||
server := config.Conf.Server
|
||||
port := server.GetPort()
|
||||
if app := config.Conf.App; app != nil {
|
||||
global.Log.Infof("%s- Listening and serving HTTP on %s", app.GetAppInfo(), port)
|
||||
} else {
|
||||
global.Log.Infof("Listening and serving HTTP on %s", port)
|
||||
}
|
||||
|
||||
var err error
|
||||
if server.Tls != nil && server.Tls.Enable {
|
||||
err = web.RunTLS(port, server.Tls.CertFile, server.Tls.KeyFile)
|
||||
} else {
|
||||
err = web.Run(port)
|
||||
}
|
||||
biz.ErrIsNilAppendErr(err, "服务启动失败: %s")
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package utils
|
||||
|
||||
// 数组比较
|
||||
// 依次返回,新增值,删除值,以及不变值
|
||||
func ArrayCompare(newArr []interface{}, oldArr []interface{}, compareFun func(interface{}, interface{}) bool) ([]interface{}, []interface{}, []interface{}) {
|
||||
var unmodifierValue []interface{}
|
||||
ni, oi := 0, 0
|
||||
for {
|
||||
if ni >= len(newArr) {
|
||||
break
|
||||
}
|
||||
nv := newArr[ni]
|
||||
for {
|
||||
if oi >= len(oldArr) {
|
||||
oi = 0
|
||||
break
|
||||
}
|
||||
ov := oldArr[oi]
|
||||
if compareFun(nv, ov) {
|
||||
unmodifierValue = append(unmodifierValue, nv)
|
||||
// 新数组移除该位置值
|
||||
newArr = append(newArr[:ni], newArr[ni+1:]...)
|
||||
oldArr = append(oldArr[:oi], oldArr[oi+1:]...)
|
||||
ni = ni - 1
|
||||
oi = oi - 1
|
||||
}
|
||||
oi = oi + 1
|
||||
}
|
||||
ni = ni + 1
|
||||
}
|
||||
|
||||
return newArr, oldArr, unmodifierValue
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArrayCompare(t *testing.T) {
|
||||
newArr := []interface{}{1, 2, 3, 5}
|
||||
oldArr := []interface{}{3, 6}
|
||||
add, del, unmodifier := ArrayCompare(newArr, oldArr, func(i1, i2 interface{}) bool {
|
||||
return i1.(int) == i2.(int)
|
||||
})
|
||||
fmt.Println(add...)
|
||||
fmt.Println(del...)
|
||||
fmt.Println(unmodifier...)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// md5
|
||||
func Md5(str string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func Json2Map(jsonStr string) map[string]interface{} {
|
||||
var res map[string]interface{}
|
||||
if jsonStr == "" {
|
||||
return res
|
||||
}
|
||||
_ = json.Unmarshal([]byte(jsonStr), &res)
|
||||
return res
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetString4Map(m map[string]interface{}, key string) string {
|
||||
return m[key].(string)
|
||||
}
|
||||
|
||||
func GetInt4Map(m map[string]interface{}, key string) int {
|
||||
i := m[key]
|
||||
iKind := reflect.TypeOf(i).Kind()
|
||||
if iKind == reflect.Int {
|
||||
return i.(int)
|
||||
}
|
||||
if iKind == reflect.String {
|
||||
i, _ := strconv.Atoi(i.(string))
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// map构造器
|
||||
type mapBuilder struct {
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
func MapBuilder(key string, value interface{}) *mapBuilder {
|
||||
mb := new(mapBuilder)
|
||||
mb.m = make(map[string]interface{}, 4)
|
||||
mb.m[key] = value
|
||||
return mb
|
||||
}
|
||||
|
||||
func (mb *mapBuilder) Put(key string, value interface{}) *mapBuilder {
|
||||
mb.m[key] = value
|
||||
return mb
|
||||
}
|
||||
|
||||
func (mb *mapBuilder) ToMap() map[string]interface{} {
|
||||
return mb.m
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package utils
|
||||
|
||||
import "runtime"
|
||||
|
||||
// 获取调用堆栈信息
|
||||
func GetStackTrace() string {
|
||||
var buf [2 << 10]byte
|
||||
return string(buf[:runtime.Stack(buf[:], false)])
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// 可判断中文
|
||||
func StrLen(str string) int {
|
||||
return len([]rune(str))
|
||||
}
|
||||
|
||||
// 去除字符串左右空字符
|
||||
func StrTrim(str string) string {
|
||||
return strings.Trim(str, " ")
|
||||
}
|
||||
|
||||
func SubString(str string, begin, end int) (substr string) {
|
||||
// 将字符串的转换成[]rune
|
||||
rs := []rune(str)
|
||||
lth := len(rs)
|
||||
|
||||
// 简单的越界判断
|
||||
if begin < 0 {
|
||||
begin = 0
|
||||
}
|
||||
if begin >= lth {
|
||||
begin = lth
|
||||
}
|
||||
if end > lth {
|
||||
end = lth
|
||||
}
|
||||
|
||||
// 返回子串
|
||||
return string(rs[begin:end])
|
||||
}
|
||||
|
||||
func Camel2Underline(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
temp := strings.Split(name, "_")
|
||||
var s string
|
||||
for _, v := range temp {
|
||||
vv := []rune(v)
|
||||
if len(vv) > 0 {
|
||||
if bool(vv[0] >= 'a' && vv[0] <= 'z') { //首字母大写
|
||||
vv[0] -= 32
|
||||
}
|
||||
s += string(vv)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func UnicodeIndex(str, substr string) int {
|
||||
// 子串在字符串的字节位置
|
||||
result := strings.Index(str, substr)
|
||||
if result >= 0 {
|
||||
// 获得子串之前的字符串并转换成[]byte
|
||||
prefix := []byte(str)[0:result]
|
||||
// 将子串之前的字符串转换成[]rune
|
||||
rs := []rune(string(prefix))
|
||||
// 获得子串之前的字符串的长度,便是子串在字符串的字符位置
|
||||
result = len(rs)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 字符串模板解析
|
||||
func TemplateResolve(temp string, data interface{}) string {
|
||||
t, _ := template.New("string-temp").Parse(temp)
|
||||
var tmplBytes bytes.Buffer
|
||||
|
||||
err := t.Execute(&tmplBytes, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tmplBytes.String()
|
||||
}
|
||||
|
||||
func ReverStrTemplate(temp, str string, res map[string]interface{}) {
|
||||
index := UnicodeIndex(temp, "{")
|
||||
ei := UnicodeIndex(temp, "}") + 1
|
||||
next := StrTrim(temp[ei:])
|
||||
nextContain := UnicodeIndex(next, "{")
|
||||
nextIndexValue := next
|
||||
if nextContain != -1 {
|
||||
nextIndexValue = SubString(next, 0, nextContain)
|
||||
}
|
||||
key := temp[index+1 : ei-1]
|
||||
// 如果后面没有内容了,则取字符串的长度即可
|
||||
var valueLastIndex int
|
||||
if nextIndexValue == "" {
|
||||
valueLastIndex = StrLen(str)
|
||||
} else {
|
||||
valueLastIndex = UnicodeIndex(str, nextIndexValue)
|
||||
}
|
||||
value := StrTrim(SubString(str, index, valueLastIndex))
|
||||
res[key] = value
|
||||
// 如果后面的还有需要解析的,则递归调用解析
|
||||
if nextContain != -1 {
|
||||
ReverStrTemplate(next, StrTrim(SubString(str, UnicodeIndex(str, value)+StrLen(value), StrLen(str))), res)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func parse(t *template.Template, vars interface{}) string {
|
||||
var tmplBytes bytes.Buffer
|
||||
|
||||
err := t.Execute(&tmplBytes, vars)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tmplBytes.String()
|
||||
}
|
||||
|
||||
// 模板字符串解析
|
||||
// @param str 模板字符串
|
||||
// @param vars 参数变量
|
||||
func TemplateParse(str string, vars interface{}) string {
|
||||
tmpl, err := template.New("tmpl").Parse(str)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parse(tmpl, vars)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package ws
|
||||
|
||||
const SuccessMsgType = 1
|
||||
const ErrorMsgType = 0
|
||||
const InfoMsgType = 2
|
||||
|
||||
// websocket消息
|
||||
type Msg struct {
|
||||
Type int `json:"type"` // 消息类型
|
||||
Title string `json:"title"` // 消息标题
|
||||
Msg string `json:"msg"` // 消息内容
|
||||
}
|
||||
|
||||
// 普通消息
|
||||
func NewMsg(title, msg string) *Msg {
|
||||
return &Msg{Type: InfoMsgType, Title: title, Msg: msg}
|
||||
}
|
||||
|
||||
// 成功消息
|
||||
func SuccessMsg(title, msg string) *Msg {
|
||||
return &Msg{Type: SuccessMsgType, Title: title, Msg: msg}
|
||||
}
|
||||
|
||||
// 错误消息
|
||||
func ErrMsg(title, msg string) *Msg {
|
||||
return &Msg{Type: ErrorMsgType, Title: title, Msg: msg}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"mayfly-go/base/global"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var Upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024 * 1024 * 10,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
var conns = make(map[uint64]*websocket.Conn, 100)
|
||||
|
||||
func init() {
|
||||
checkConn()
|
||||
}
|
||||
|
||||
// 放置ws连接
|
||||
func Put(userId uint64, conn *websocket.Conn) {
|
||||
existConn := conns[userId]
|
||||
if existConn != nil {
|
||||
Delete(userId)
|
||||
}
|
||||
|
||||
conn.SetCloseHandler(func(code int, text string) error {
|
||||
Delete(userId)
|
||||
return nil
|
||||
})
|
||||
conns[userId] = conn
|
||||
}
|
||||
|
||||
func checkConn() {
|
||||
heartbeat := time.Duration(60) * time.Second
|
||||
tick := time.NewTicker(heartbeat)
|
||||
go func() {
|
||||
for range tick.C {
|
||||
// 遍历所有连接,ping失败的则删除掉
|
||||
for uid, conn := range conns {
|
||||
err := conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(heartbeat/2))
|
||||
if err != nil {
|
||||
Delete(uid)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 删除ws连接
|
||||
func Delete(userid uint64) {
|
||||
global.Log.Info("移除websocket连接:uid = ", userid)
|
||||
conn := conns[userid]
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
delete(conns, userid)
|
||||
}
|
||||
}
|
||||
|
||||
// 对指定用户发送消息
|
||||
func SendMsg(userId uint64, msg *Msg) {
|
||||
conn := conns[userId]
|
||||
if conn != nil {
|
||||
bytes, _ := json.Marshal(msg)
|
||||
conn.WriteMessage(websocket.TextMessage, bytes)
|
||||
}
|
||||
}
|
||||
312
build_release.sh
Executable file
@@ -0,0 +1,312 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================
|
||||
# Mayfly-Go Release Build Tool
|
||||
# 前后端打包编译至指定目录,快速制作发行版
|
||||
#==============================================
|
||||
|
||||
set -e # 遇到错误立即退出
|
||||
|
||||
#----------------------------------------------
|
||||
# 全局配置
|
||||
#----------------------------------------------
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SERVER_DIR="${PROJECT_ROOT}/server"
|
||||
FRONTEND_DIR="${PROJECT_ROOT}/frontend"
|
||||
BINARY_NAME="mayfly-go"
|
||||
|
||||
# 构建目标配置:名称|操作系统|架构
|
||||
BUILD_TARGETS=(
|
||||
"linux-amd64|linux|amd64"
|
||||
"linux-arm64|linux|arm64"
|
||||
"windows|windows|amd64"
|
||||
"mac|darwin|amd64"
|
||||
)
|
||||
|
||||
#----------------------------------------------
|
||||
# 工具函数
|
||||
#----------------------------------------------
|
||||
print_header() {
|
||||
echo -e "\033[1;33m$1\033[0m"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "\033[1;32m$1\033[0m"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "\033[1;31m$1\033[0m" >&2
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "\033[1;34m$1\033[0m"
|
||||
}
|
||||
|
||||
to_lower() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
#----------------------------------------------
|
||||
# 构建函数
|
||||
#----------------------------------------------
|
||||
build_frontend() {
|
||||
print_header "\n>>> Building frontend..."
|
||||
|
||||
cd "${FRONTEND_DIR}"
|
||||
npm run build
|
||||
|
||||
# 拷贝到 server 静态目录
|
||||
print_success ">>> Copying frontend assets to server/static/static"
|
||||
rm -rf "${SERVER_DIR}/static/static"
|
||||
mkdir -p "${SERVER_DIR}/static/static"
|
||||
cp -r "${FRONTEND_DIR}/dist/"* "${SERVER_DIR}/static/static/"
|
||||
|
||||
cd "${PROJECT_ROOT}"
|
||||
}
|
||||
|
||||
build_backend() {
|
||||
local output_dir="$1"
|
||||
local os_name="$2"
|
||||
local arch="$3"
|
||||
local copy_resources="$4"
|
||||
|
||||
local binary_file="${BINARY_NAME}"
|
||||
local target_name="${os_name}-${arch}"
|
||||
|
||||
print_header "\n>>> Building backend: ${target_name}"
|
||||
|
||||
# Windows 需要 .exe 后缀
|
||||
if [ "${os_name}" = "windows" ]; then
|
||||
binary_file="${BINARY_NAME}.exe"
|
||||
fi
|
||||
|
||||
# 编译
|
||||
cd "${SERVER_DIR}"
|
||||
go mod tidy
|
||||
CGO_ENABLED=0 GOOS="${os_name}" GOARCH="${arch}" \
|
||||
go build -trimpath -ldflags="-w" -o "${binary_file}" main.go
|
||||
|
||||
# 准备输出目录
|
||||
local bin_dir="${output_dir}/bin"
|
||||
if [ -d "${output_dir}" ]; then
|
||||
print_info " Output directory exists, cleaning..."
|
||||
rm -rf "${output_dir}"
|
||||
fi
|
||||
mkdir -p "${bin_dir}"
|
||||
|
||||
# 移动二进制文件到 bin 目录
|
||||
mv "${SERVER_DIR}/${binary_file}" "${bin_dir}/"
|
||||
|
||||
# 拷贝资源文件
|
||||
if [ "${copy_resources}" = "1" ]; then
|
||||
print_info " Copying config and scripts..."
|
||||
cp "${SERVER_DIR}/config.yml.example" "${output_dir}/config.yml"
|
||||
cp "${SERVER_DIR}/readme.txt" "${output_dir}/"
|
||||
cp "${SERVER_DIR}/readme_en.txt" "${output_dir}/"
|
||||
cp "${SERVER_DIR}/resources/script/mayfly-go.sh" "${output_dir}/"
|
||||
chmod +x "${output_dir}/mayfly-go.sh"
|
||||
fi
|
||||
|
||||
print_success ">>> Build complete: ${target_name}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
}
|
||||
|
||||
build_docker() {
|
||||
local version="$1"
|
||||
local use_buildx="$2"
|
||||
local image_name
|
||||
local build_cmd
|
||||
|
||||
if [ "${use_buildx}" = "1" ]; then
|
||||
image_name="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${version}"
|
||||
build_cmd="docker buildx build --no-cache --push --platform linux/amd64,linux/arm64"
|
||||
print_header "\n>>> Building Docker image (multi-arch): ${image_name}"
|
||||
else
|
||||
image_name="mayfly/mayfly-go:${version}"
|
||||
build_cmd="docker build --no-cache --platform linux/amd64"
|
||||
print_header "\n>>> Building Docker image: ${image_name}"
|
||||
fi
|
||||
|
||||
${build_cmd} --build-arg MAYFLY_GO_VERSION="${version}" -t "${image_name}" "${PROJECT_ROOT}"
|
||||
print_success ">>> Docker image built: ${image_name}"
|
||||
}
|
||||
|
||||
cleanup_frontend() {
|
||||
print_info "\n>>> Cleaning up temporary frontend assets..."
|
||||
rm -rf "${SERVER_DIR}/static/static/"{assets,config.js,index.html}
|
||||
print_success ">>> Cleanup complete"
|
||||
}
|
||||
|
||||
compress_package() {
|
||||
local source_dir="$1"
|
||||
local output_dir="$2"
|
||||
local package_name
|
||||
|
||||
package_name="$(basename "${source_dir}")"
|
||||
|
||||
print_header "\n>>> Compressing package: ${package_name}"
|
||||
|
||||
cd "${output_dir}"
|
||||
|
||||
# 统一使用 zip 格式,跨平台兼容性最好
|
||||
zip -r "${package_name}.zip" "${package_name}"/
|
||||
rm -rf "${package_name}"
|
||||
print_success ">>> Compressed: ${package_name}.zip"
|
||||
|
||||
cd "${PROJECT_ROOT}"
|
||||
}
|
||||
|
||||
#----------------------------------------------
|
||||
# 主流程
|
||||
#----------------------------------------------
|
||||
main() {
|
||||
# 显示菜单
|
||||
print_header "========================================"
|
||||
print_header " Mayfly-Go Release Build Tool"
|
||||
print_header "========================================"
|
||||
echo ""
|
||||
echo "Build Options:"
|
||||
echo " [0] All Platforms (linux-amd64, linux-arm64, windows, mac)"
|
||||
echo " [1] Linux AMD64"
|
||||
echo " [2] Linux ARM64"
|
||||
echo " [3] Windows"
|
||||
echo " [4] macOS"
|
||||
echo " [5] Docker Image"
|
||||
echo " [6] Docker Multi-arch (buildx)"
|
||||
echo ""
|
||||
|
||||
read -p "Select build option [0-6] (default: 0): " build_type
|
||||
build_type=${build_type:-0}
|
||||
|
||||
# 验证输入
|
||||
if ! [[ "${build_type}" =~ ^[0-6]$ ]]; then
|
||||
print_error "Error: Invalid option. Please enter a number between 0 and 6."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 初始化配置
|
||||
local output_dir="."
|
||||
local docker_version="latest"
|
||||
local copy_resources="1"
|
||||
local compress_output="0"
|
||||
local is_docker=0
|
||||
|
||||
# Docker 构建
|
||||
if [[ "${build_type}" == "5" || "${build_type}" == "6" ]]; then
|
||||
is_docker=1
|
||||
echo ""
|
||||
read -p "Enter Docker image version (default: latest): " docker_version
|
||||
docker_version=${docker_version:-latest}
|
||||
else
|
||||
# 二进制构建
|
||||
echo ""
|
||||
read -p "Enter output directory (default: current): " output_dir
|
||||
output_dir=${output_dir:-.}
|
||||
|
||||
# 验证并获取绝对路径
|
||||
if [ "${output_dir}" != "." ] && [ ! -d "${output_dir}" ]; then
|
||||
print_error "Error: Directory '${output_dir}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
output_dir="$(cd "${output_dir}" && pwd)"
|
||||
|
||||
echo ""
|
||||
read -p "Copy config & scripts? [Y/n] (default: Y): " copy_input
|
||||
if [ "$(to_lower "${copy_input}")" = "n" ]; then
|
||||
copy_resources="0"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Compress package? [y/N] (default: N): " compress_input
|
||||
if [ "$(to_lower "${compress_input}")" = "y" ]; then
|
||||
compress_output="1"
|
||||
fi
|
||||
|
||||
# 构建前端
|
||||
echo ""
|
||||
build_frontend
|
||||
fi
|
||||
|
||||
# 显示配置摘要
|
||||
echo ""
|
||||
print_header "Build Configuration:"
|
||||
|
||||
# 获取构建类型名称
|
||||
local type_names=("All Platforms" "Linux AMD64" "Linux ARM64" "Windows" "macOS" "Docker Image" "Docker Multi-arch")
|
||||
echo " Type: ${type_names[${build_type}]}"
|
||||
|
||||
if [ "${is_docker}" = "1" ]; then
|
||||
echo " Version: ${docker_version}"
|
||||
else
|
||||
echo " Output: ${output_dir}"
|
||||
echo " Resources: $([ "${copy_resources}" = "1" ] && echo "Yes" || echo "No")"
|
||||
echo " Compress: $([ "${compress_output}" = "1" ] && echo "Yes" || echo "No")"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 确认构建
|
||||
read -p "Continue? [Y/n] (default: Y): " confirm
|
||||
if [ "$(to_lower "${confirm}")" = "n" ]; then
|
||||
print_info "Build cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 执行构建
|
||||
echo ""
|
||||
print_header "Starting build..."
|
||||
|
||||
case "${build_type}" in
|
||||
"1"|"2"|"3"|"4")
|
||||
# 单个平台构建
|
||||
local target="${BUILD_TARGETS[$((build_type-1))]}"
|
||||
IFS='|' read -r name os arch <<< "${target}"
|
||||
build_backend "${output_dir}/mayfly-go-${name}" "${os}" "${arch}" "${copy_resources}"
|
||||
;;
|
||||
"5")
|
||||
build_docker "${docker_version}" "0"
|
||||
;;
|
||||
"6")
|
||||
build_docker "${docker_version}" "1"
|
||||
;;
|
||||
*)
|
||||
# 构建所有平台
|
||||
print_info "Building all platforms..."
|
||||
for target in "${BUILD_TARGETS[@]}"; do
|
||||
IFS='|' read -r name os arch <<< "${target}"
|
||||
build_backend "${output_dir}/mayfly-go-${name}" "${os}" "${arch}" "${copy_resources}"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
# 清理临时文件
|
||||
if [ "${is_docker}" = "0" ]; then
|
||||
cleanup_frontend
|
||||
fi
|
||||
|
||||
# 压缩输出
|
||||
if [ "${compress_output}" = "1" ] && [ "${is_docker}" = "0" ]; then
|
||||
case "${build_type}" in
|
||||
"1"|"2"|"3"|"4")
|
||||
local target="${BUILD_TARGETS[$((build_type-1))]}"
|
||||
IFS='|' read -r name os arch <<< "${target}"
|
||||
compress_package "${output_dir}/mayfly-go-${name}" "${output_dir}"
|
||||
;;
|
||||
*)
|
||||
print_info "Compressing all packages..."
|
||||
for target in "${BUILD_TARGETS[@]}"; do
|
||||
IFS='|' read -r name os arch <<< "${target}"
|
||||
compress_package "${output_dir}/mayfly-go-${name}" "${output_dir}"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# 完成
|
||||
echo ""
|
||||
print_success "========================================"
|
||||
print_success " Build Completed Successfully!"
|
||||
print_success "========================================"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main
|
||||
31
docker-compose.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: "mysql:8"
|
||||
container_name: mayfly-go-mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 111049
|
||||
MYSQL_DATABASE: mayfly-go
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- ./server/docs/docker-compose/mysql/data/mydir:/mydir
|
||||
- ./server/docs/docker-compose/mysql/data/datadir:/var/lib/mysql
|
||||
restart: always
|
||||
|
||||
server:
|
||||
image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: mayfly-go-server
|
||||
ports:
|
||||
- "18888:18888"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
WAIT_HOSTS: mysql:3306
|
||||
volumes:
|
||||
- ./server/config.yml:/mayfly/config.yml
|
||||
depends_on:
|
||||
- mysql
|
||||
restart: always
|
||||
37
docs/frontend/api.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# API 定义与调用规范
|
||||
|
||||
## API 定义
|
||||
|
||||
```typescript
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const accountApi = {
|
||||
list: Api.newGet('/sys/accounts'),
|
||||
save: Api.newPost('/sys/accounts'),
|
||||
update: Api.newPut('/sys/accounts/{id}'),
|
||||
del: Api.newDelete('/sys/accounts/{id}'),
|
||||
changeStatus: Api.newPut('/sys/accounts/change-status/{id}/{status}'),
|
||||
};
|
||||
```
|
||||
|
||||
## 调用模式
|
||||
|
||||
```typescript
|
||||
// 简单请求
|
||||
await accountApi.del.request({ id: row.id });
|
||||
|
||||
// 响应式(用于 loading 状态)
|
||||
const { execute, isFetching } = accountApi.list.useApi();
|
||||
|
||||
// 表格集成
|
||||
<page-table :page-api="accountApi.list" />
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: API 定义集中放在 `api.ts`
|
||||
- 🚫 **Never**: 直接调用 axios,必须通过 API 封装
|
||||
164
docs/frontend/component.md
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 组件开发规范
|
||||
|
||||
## 代码组织顺序
|
||||
|
||||
```
|
||||
Imports
|
||||
Props/Emits
|
||||
常量定义 (as const)
|
||||
类型定义
|
||||
响应式数据
|
||||
计算属性
|
||||
监听器
|
||||
工具函数
|
||||
事件处理方法 (on 开头)
|
||||
```
|
||||
|
||||
## 命名规范
|
||||
|
||||
- **事件方法**: 必须以 `on` 开头(`onSubmit`, `onDelete`, `onEdit`)
|
||||
- **变量/函数**: camelCase
|
||||
- **常量**: UPPER_SNAKE_CASE + `as const`
|
||||
- **组件**: PascalCase
|
||||
- **文件**: 组件用 PascalCase,其他用小写
|
||||
|
||||
## Props & Emits
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
visible?: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
data: null,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'success'): void;
|
||||
}>();
|
||||
</script>
|
||||
```
|
||||
|
||||
## 双向绑定规范
|
||||
|
||||
### 使用 defineModel (Vue 3.4+)
|
||||
|
||||
**必须使用 `defineModel` 实现双向绑定**,替代旧的 `computed` + `emit('update:xxx')` 模式。
|
||||
|
||||
#### 基本用法
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
// 单个 v-model
|
||||
const modelValue = defineModel<string>('modelValue', {
|
||||
default: '',
|
||||
});
|
||||
|
||||
// 命名 v-model
|
||||
const authCertName = defineModel<string>('authCertName');
|
||||
const machineId = defineModel<number>('machineId');
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 内部字段联动更新
|
||||
|
||||
当组件内部有多个字段,需要联动更新外部的 `modelValue` 时,使用 `watch` 监听:
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
|
||||
const authCertName = defineModel<string>('authCertName');
|
||||
const machineName = defineModel<string>('machineName');
|
||||
const selectNode = defineModel<string>('modelValue', { default: '' });
|
||||
|
||||
// 监听内部字段变化,自动更新 selectNode
|
||||
watch(
|
||||
[authCertName, machineName],
|
||||
() => {
|
||||
selectNode.value = authCertName.value
|
||||
? `${machineName.value} > ${authCertName.value}`
|
||||
: '';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 规范要点
|
||||
|
||||
- ✅ **Always**: 使用 `defineModel` 替代 `computed` + `emit('update:xxx')`
|
||||
- ✅ **Always**: 为 `defineModel` 提供合适的 `default` 值
|
||||
- 🚫 **Never**: 使用旧的 `computed` getter/setter 模式实现双向绑定
|
||||
|
||||
## 图标使用规范
|
||||
|
||||
### 统一使用 SvgIcon 组件
|
||||
|
||||
**所有图标必须使用 `SvgIcon` 组件**,禁止使用 `<el-icon>` 配合导入图标组件。
|
||||
|
||||
```vue
|
||||
<!-- ✅ 正确:使用 SvgIcon -->
|
||||
<SvgIcon name="Monitor" :size="20" />
|
||||
<SvgIcon name="check" class="text-success" />
|
||||
|
||||
<!-- ❌ 错误:使用 el-icon + 导入 -->
|
||||
<el-icon><Check /></el-icon>
|
||||
```
|
||||
|
||||
**规范要点**:
|
||||
- ✅ 使用 `name` 属性指定图标,`size` 属性控制大小
|
||||
- ✅ 图标名称使用 PascalCase 或 kebab-case
|
||||
- 🚫 禁止使用 `<el-icon>` 和导入 `@element-plus/icons-vue`
|
||||
- 🚫 禁止通过 class 设置图标大小
|
||||
|
||||
### 自定义 SVG 图标
|
||||
|
||||
项目支持在 `assets/icon` 目录下添加自定义 SVG 图标。
|
||||
|
||||
#### 目录结构
|
||||
|
||||
```
|
||||
frontend/src/assets/icon/
|
||||
├── db/ # 数据库图标(mysql.svg, postgres.svg...)
|
||||
├── machine/ # 机器图标
|
||||
└── ...
|
||||
```
|
||||
|
||||
#### 使用方法
|
||||
|
||||
**格式**: `name="icon {目录}/{文件名}"`(不含 .svg)
|
||||
|
||||
```vue
|
||||
<SvgIcon name="icon db/mysql" :size="20" />
|
||||
```
|
||||
|
||||
#### 添加步骤
|
||||
|
||||
1. 将 SVG 文件放到 `frontend/src/assets/icon/` 对应子目录
|
||||
2. 文件名使用小写 + 连字符(如 `mysql.svg`)
|
||||
3. 使用 `name="icon db/mysql"` 引用
|
||||
|
||||
#### 注意事项
|
||||
|
||||
- ✅ SVG 必须有 `viewBox` 属性
|
||||
- ✅ 使用 `size` 属性控制大小
|
||||
- ✅ 图标颜色继承当前元素的 `color`
|
||||
- 🚫 不要在 SVG 中硬编码颜色值
|
||||
- 🚫 文件名不要使用大写或下划线
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: 使用 Composition API + `<script setup>`
|
||||
- ✅ **Always**: 事件方法以 `on` 开头
|
||||
- ✅ **Always**: 移除无用的导入(import)和无用的字段、变量、函数
|
||||
- 🚫 **Never**: 保留未使用的代码或注释掉的代码
|
||||
- 🚫 **Never**: 使用固定高度计算,优先用 Flexbox
|
||||
45
docs/frontend/i18n.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 国际化规范
|
||||
|
||||
## 文件组织
|
||||
|
||||
```
|
||||
src/i18n/
|
||||
├── zh-cn/
|
||||
│ ├── common.ts
|
||||
│ ├── system.ts
|
||||
│ ├── ai.ts
|
||||
│ └── ...
|
||||
└── en/
|
||||
├── common.ts
|
||||
├── system.ts
|
||||
└── ...
|
||||
```
|
||||
|
||||
- 按模块拆分,每个业务模块一个文件
|
||||
- 命名空间以模块名为根 key(如 `system.account.name`)
|
||||
|
||||
## 使用方式
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<h1>{{ $t('system.account.name') }}</h1>
|
||||
<el-button>{{ $t('common.save') }}</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const message = t('common.success');
|
||||
</script>
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: 所有展示文本通过 `$t()` 或 `t()` 获取
|
||||
- ✅ **Always**: 枚举的 label 必须是国际化 key
|
||||
- ✅ **Always**: 新增模块时在 `zh-cn/` 和 `en/` 下同时创建语言文件
|
||||
- 🚫 **Never**: 在组件中直接写中文/英文文本
|
||||
134
docs/frontend/overview.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 前端开发规范
|
||||
|
||||
## 技术栈
|
||||
|
||||
Vue 3 (Composition API) + TypeScript 5.x + Vite 5.x + Element Plus + Tailwind CSS 3.x + Pinia
|
||||
|
||||
## 综合示例:列表页 + 编辑对话框
|
||||
|
||||
### 枚举定义
|
||||
|
||||
```typescript
|
||||
// src/views/system/enums.ts
|
||||
import { EnumValue } from '@/common/Enum';
|
||||
|
||||
export const AccountStatusEnum = {
|
||||
Enable: EnumValue.of(1, 'system.account.statusEnable').tagTypeSuccess(),
|
||||
Disable: EnumValue.of(-1, 'system.account.statusDisable').tagTypeDanger(),
|
||||
};
|
||||
```
|
||||
|
||||
### API 定义
|
||||
|
||||
```typescript
|
||||
// src/views/system/api.ts
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const accountApi = {
|
||||
list: Api.newGet('/sys/accounts'),
|
||||
save: Api.newPost('/sys/accounts'),
|
||||
update: Api.newPut('/sys/accounts/{id}'),
|
||||
del: Api.newDelete('/sys/accounts/{id}'),
|
||||
};
|
||||
```
|
||||
|
||||
### 列表页
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<page-table ref="pageTableRef" :page-api="accountApi.list" :search-items="searchItems" v-model:query-form="query" :columns="columns">
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="'account:add'" type="primary" @click="onAdd">
|
||||
{{ $t('common.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #action="{ data }">
|
||||
<el-button link v-auth="'account:edit'" @click="onEdit(data)">
|
||||
{{ $t('common.edit') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</page-table>
|
||||
<AccountEdit v-model:visible="editVisible" :data="editData" @success="onEditSuccess" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { accountApi } from '../api';
|
||||
import { AccountStatusEnum } from '../enums';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { SearchItem, TableColumn } from '@/components/pagetable';
|
||||
import AccountEdit from './AccountEdit.vue';
|
||||
|
||||
const pageTableRef = ref();
|
||||
const editVisible = ref(false);
|
||||
const editData = ref<any>(null);
|
||||
|
||||
const query = ref({ username: '', status: null as number | null, pageNum: 1, pageSize: 10 });
|
||||
|
||||
const searchItems = [SearchItem.input('username', 'common.username'), SearchItem.select('status', 'common.status', AccountStatusEnum)];
|
||||
const columns = [
|
||||
TableColumn.new('username', 'common.username'),
|
||||
TableColumn.new('status', 'common.status').typeTag(AccountStatusEnum),
|
||||
TableColumn.new('action', 'common.operation').isSlot().fixedRight(),
|
||||
];
|
||||
|
||||
const onAdd = () => { editData.value = null; editVisible.value = true; };
|
||||
const onEdit = (row: any) => { editData.value = row; editVisible.value = true; };
|
||||
const onEditSuccess = () => { editVisible.value = false; pageTableRef.value?.search(); };
|
||||
</script>
|
||||
```
|
||||
|
||||
### 编辑对话框
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="dialogTitle" width="500px" @close="onDialogClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item :label="$t('common.username')" prop="username">
|
||||
<el-input v-model="form.username" :disabled="!!form.id" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onCancel">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">{{ $t('common.confirm') }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { accountApi } from '../api';
|
||||
import { useI18nOperateSuccessMsg } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{ visible?: boolean; data?: any }>();
|
||||
const emit = defineEmits(['update:visible', 'success']);
|
||||
|
||||
const formRef = ref();
|
||||
const submitting = ref(false);
|
||||
const form = reactive({ id: undefined, username: '', name: '', status: 1 });
|
||||
const visible = computed({ get: () => props.visible, set: (val) => emit('update:visible', val) });
|
||||
const { t } = useI18n();
|
||||
const dialogTitle = computed(() => (form.id ? t('system.account.editAccount') : t('system.account.addAccount')));
|
||||
|
||||
watch(() => props.data, (newVal) => { newVal ? Object.assign(form, newVal) : resetForm(); }, { immediate: true });
|
||||
|
||||
const resetForm = () => { form.id = undefined; form.username = ''; form.name = ''; form.status = 1; formRef.value?.clearValidate(); };
|
||||
const onSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
submitting.value = true;
|
||||
try {
|
||||
form.id ? await accountApi.update.request(form) : await accountApi.save.request(form);
|
||||
useI18nOperateSuccessMsg();
|
||||
visible.value = false;
|
||||
emit('success');
|
||||
} finally { submitting.value = false; }
|
||||
};
|
||||
const onCancel = () => { visible.value = false; };
|
||||
const onDialogClose = () => { resetForm(); };
|
||||
</script>
|
||||
```
|
||||
35
docs/frontend/style.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 样式与 UI 规范
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
优先使用 Tailwind 工具类,支持 `dark:` 前缀:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="flex items-center justify-between p-4 bg-white dark:bg-gray-800">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-300">Label</span>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 权限控制
|
||||
|
||||
按钮权限使用 `v-auth` 指令:
|
||||
|
||||
```vue
|
||||
<el-button v-auth="'account:add'" type="primary" @click="onAdd">新增</el-button>
|
||||
```
|
||||
|
||||
## 类型安全
|
||||
|
||||
- 避免 `any`,使用可选链 `?.`
|
||||
- 使用 TypeScript 严格模式
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: 优先使用 Tailwind CSS
|
||||
- 🚫 **Never**: 使用固定高度计算,优先用 Flexbox
|
||||
59
docs/server/api.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# API 层规范
|
||||
|
||||
## Handler 标准结构
|
||||
|
||||
```go
|
||||
type Db struct {
|
||||
dbApp application.Db `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
|
||||
// @router /api/dbs [get]
|
||||
func (d *Db) Dbs(rc *req.Ctx) {
|
||||
queryCond := req.BindQuery[entity.DbQuery](rc) // 1. 绑定参数
|
||||
loginAccount := rc.GetLoginAccount() // 2. 获取上下文
|
||||
result, err := d.dbApp.GetPageList(queryCond) // 3. 调用应用层
|
||||
biz.ErrIsNil(err) // 4. 断言错误(仅API层)
|
||||
rc.ResData = result // 5. 返回结果
|
||||
}
|
||||
```
|
||||
|
||||
## 路由配置
|
||||
|
||||
```go
|
||||
func (d *Db) ReqConfs() *req.Confs {
|
||||
return req.NewConfs("/dbs",
|
||||
req.NewGet("", d.Dbs),
|
||||
req.NewPost("", d.Save).Log(req.NewLogSaveI(imsg.LogDbSave)),
|
||||
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSaveI(imsg.LogDbDelete)),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 断言边界
|
||||
|
||||
**✅ API 层可用断言**:
|
||||
|
||||
```go
|
||||
func (d *Db) Save(rc *req.Ctx) {
|
||||
form := req.BindFormAndValid[form.DbForm](rc)
|
||||
biz.IsTrue(form.InstanceId > 0, "实例ID不能为空")
|
||||
biz.ErrIsNil(d.dbApp.SaveDb(rc, &entity.Db{Name: form.Name}))
|
||||
rc.ResData = "保存成功"
|
||||
}
|
||||
```
|
||||
|
||||
**🚫 Application 层禁止断言,必须返回 error**:
|
||||
|
||||
```go
|
||||
func (d *dbAppImpl) SaveDb(ctx context.Context, db *entity.Db) error {
|
||||
if db.Name == "" {
|
||||
return errorx.NewBiz("名称不能为空")
|
||||
}
|
||||
return d.Save(ctx, db)
|
||||
}
|
||||
```
|
||||
51
docs/server/application.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# Application 层规范
|
||||
|
||||
## 接口与实现
|
||||
|
||||
```go
|
||||
type Db interface {
|
||||
base.App[*entity.Db]
|
||||
GetPageList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error)
|
||||
SaveDb(ctx context.Context, entity *entity.Db) error
|
||||
}
|
||||
|
||||
type dbAppImpl struct {
|
||||
base.AppImpl[*entity.Db, repository.Db]
|
||||
dbInstanceApp Instance `inject:"T"`
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
var _ Db = (*dbAppImpl)(nil)
|
||||
|
||||
func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
|
||||
// 1. 参数校验(返回error)
|
||||
if dbEntity.Name == "" {
|
||||
return errorx.NewBiz("名称不能为空")
|
||||
}
|
||||
// 2. 业务检查
|
||||
oldDb := &entity.Db{Name: dbEntity.Name, InstanceId: dbEntity.InstanceId}
|
||||
if dbEntity.Id == 0 && d.GetByCond(oldDb) == nil {
|
||||
return errorx.NewBizI(ctx, imsg.ErrDbNameExist)
|
||||
}
|
||||
// 3. 持久化
|
||||
return d.Save(ctx, dbEntity)
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
```go
|
||||
// 普通业务错误
|
||||
return errorx.NewBiz("数据库名称已存在")
|
||||
|
||||
// 国际化错误
|
||||
return errorx.NewBizI(ctx, imsg.ErrDbNameExist)
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: 参数校验后返回 error,禁止 panic
|
||||
- 🚫 **Never**: 在 application 层使用 `biz.ErrIsNil` 或 `biz.IsTrue`
|
||||
59
docs/server/architecture.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# Go 分层架构与目录规范
|
||||
|
||||
## 分层目录
|
||||
|
||||
```
|
||||
internal/{module}/
|
||||
├── api/ # HTTP请求处理、参数绑定、响应返回
|
||||
│ ├── form/ # 请求表单结构体
|
||||
│ └── vo/ # 响应视图对象
|
||||
├── application/ # 业务逻辑编排、事务控制
|
||||
│ └── dto/ # 数据传输对象
|
||||
├── domain/ # 核心业务逻辑、实体定义
|
||||
│ ├── entity/ # 领域实体
|
||||
│ └── repository/ # 仓储接口定义
|
||||
├── infra/ # 数据持久化、外部服务调用
|
||||
│ └── persistence/ # 仓储实现
|
||||
├── imsg/ # 国际化消息定义
|
||||
└── init/ # 模块初始化(依赖注册、路由注册)
|
||||
```
|
||||
|
||||
## 命名规范
|
||||
|
||||
- **模块/包名**: 小写无分隔符(`machine`, `dbinstance`)
|
||||
- **文件名**: 小写+下划线(`db.go`, `db_sql_exec.go`)
|
||||
- **结构体/常量**: PascalCase
|
||||
- **接口**: 以 `er` 结尾或名词(`Reader`, `Repository`)
|
||||
- **变量/函数**: camelCase
|
||||
|
||||
## IOC 依赖注入
|
||||
|
||||
```go
|
||||
// 1. 定义接口
|
||||
type Db interface {
|
||||
base.App[*entity.Db]
|
||||
GetPageList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error)
|
||||
}
|
||||
|
||||
// 2. 实现接口并注入依赖
|
||||
type dbAppImpl struct {
|
||||
base.AppImpl[*entity.Db, repository.Db]
|
||||
dbInstanceApp Instance `inject:"T"` // T=按类型注入
|
||||
tagApp tagapp.TagTree `inject:"T"`
|
||||
}
|
||||
var _ Db = (*dbAppImpl)(nil)
|
||||
|
||||
// 3. 模块初始化时注册
|
||||
func init() {
|
||||
ioc.Register(&dbAppImpl{})
|
||||
}
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: 依赖单向流动,上层依赖下层接口,禁止反向依赖
|
||||
- 🚫 **Never**: 跨层直接调用具体实现,必须通过接口
|
||||
69
docs/server/concurrent.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 并发与 Panic 处理规范
|
||||
|
||||
## 统一 Panic 捕获(gox.Recover)
|
||||
|
||||
**核心原则**:严禁手动编写 `defer func() { recover() }`,必须使用 `gox.Recover()`
|
||||
|
||||
### 场景1:仅记录日志
|
||||
|
||||
```go
|
||||
func (s *Service) ProcessData(data []byte) {
|
||||
defer gox.Recover()
|
||||
result := parseData(data)
|
||||
saveToDB(result)
|
||||
}
|
||||
```
|
||||
|
||||
### 场景2:Panic 转 Error 返回
|
||||
|
||||
```go
|
||||
func (s *Service) SaveUser(ctx context.Context, user *entity.User) (err error) {
|
||||
defer gox.Recover(func(e error) {
|
||||
err = fmt.Errorf("保存用户失败: %w", e)
|
||||
})
|
||||
if err := validateUser(user); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.repo.Insert(ctx, user)
|
||||
}
|
||||
```
|
||||
|
||||
### 场景3:Goroutine 安全启动
|
||||
|
||||
```go
|
||||
// ✅ 推荐
|
||||
gox.Go(func() {
|
||||
sendNotification(userId, message)
|
||||
})
|
||||
|
||||
// 🚫 禁止
|
||||
go func() {
|
||||
sendNotification(userId, message)
|
||||
}()
|
||||
```
|
||||
|
||||
## Context 传递
|
||||
|
||||
所有阻塞操作必须接受 `context.Context`:
|
||||
|
||||
```go
|
||||
func (d *dbAppImpl) SaveDb(ctx context.Context, entity *entity.Db) error {
|
||||
return d.GetRepo().Insert(ctx, entity)
|
||||
}
|
||||
```
|
||||
|
||||
## 错误组使用
|
||||
|
||||
```go
|
||||
eg, ctx := errgroup.WithContext(context.Background())
|
||||
for _, task := range tasks {
|
||||
eg.Go(func() error {
|
||||
return process(ctx, task)
|
||||
})
|
||||
}
|
||||
err := eg.Wait()
|
||||
```
|
||||
44
docs/server/domain.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# Domain 层规范
|
||||
|
||||
## 实体定义
|
||||
|
||||
```go
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
type Db struct {
|
||||
model.Model // 必须嵌入基础模型
|
||||
model.ExtraData // 辅助字段(展示用、非查询条件)
|
||||
|
||||
Code string `json:"code" gorm:"size:32;not null;index:idx_db_code"`
|
||||
Name string `json:"name" gorm:"size:255;not null;"`
|
||||
InstanceId uint64 `json:"instanceId" gorm:"not null;"`
|
||||
}
|
||||
|
||||
type Status int8
|
||||
const (
|
||||
StatusActive Status = 1
|
||||
StatusInactive Status = 0
|
||||
)
|
||||
```
|
||||
|
||||
## ExtraData 使用原则
|
||||
|
||||
- ✅ **使用 ExtraData**: 前端展示字段、关联名称、状态文本、可选扩展信息
|
||||
- 🚫 **必须独立字段**: 查询条件、排序字段、分组统计、索引字段、核心业务字段
|
||||
|
||||
## Repository 接口
|
||||
|
||||
```go
|
||||
package repository
|
||||
|
||||
type Db interface {
|
||||
base.Repo[*entity.Db]
|
||||
GetDbList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error)
|
||||
}
|
||||
```
|
||||
113
docs/server/i18n.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 后端国际化(i18n)规范
|
||||
|
||||
## 文件组织
|
||||
|
||||
每个业务模块在 `internal/{module}/imsg/` 目录下维护国际化消息:
|
||||
|
||||
```
|
||||
internal/{module}/imsg/
|
||||
├── imsg.go # 消息ID常量定义(MsgId)
|
||||
├── zh_cn.go # 中文语言包
|
||||
└── en.go # 英文语言包
|
||||
```
|
||||
|
||||
### imsg.go — 常量定义
|
||||
|
||||
```go
|
||||
package imsg
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/pkg/consts"
|
||||
"mayfly-go/pkg/i18n"
|
||||
)
|
||||
|
||||
func init() {
|
||||
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
|
||||
i18n.AppendLangMsg(i18n.En, En)
|
||||
}
|
||||
|
||||
const (
|
||||
LogDbSave = iota + consts.ImsgNumDb
|
||||
LogDbDelete
|
||||
ErrDbNameExist
|
||||
)
|
||||
```
|
||||
|
||||
### zh_cn.go — 中文语言包
|
||||
|
||||
```go
|
||||
package imsg
|
||||
|
||||
import "mayfly-go/pkg/i18n"
|
||||
|
||||
var Zh_CN = map[i18n.MsgId]string{
|
||||
LogDbSave: "保存数据库配置",
|
||||
LogDbDelete: "删除数据库配置",
|
||||
ErrDbNameExist: "该实例下数据库名已存在",
|
||||
}
|
||||
```
|
||||
|
||||
### en.go — 英文语言包
|
||||
|
||||
```go
|
||||
package imsg
|
||||
|
||||
import "mayfly-go/pkg/i18n"
|
||||
|
||||
var En = map[i18n.MsgId]string{
|
||||
LogDbSave: "Save database configuration",
|
||||
LogDbDelete: "Delete database configuration",
|
||||
ErrDbNameExist: "The database name already exists in this instance",
|
||||
}
|
||||
```
|
||||
|
||||
## 消息ID编号规则
|
||||
|
||||
各模块起始编号定义在 `internal/pkg/consts/consts.go`,新增模块需注册唯一起始值:
|
||||
|
||||
```go
|
||||
const (
|
||||
ImsgNumSys = 10000
|
||||
ImsgNumAuth = 20000
|
||||
ImsgNumDb = 60000
|
||||
ImsgNumAi = 140000
|
||||
// ...
|
||||
)
|
||||
```
|
||||
|
||||
模块内使用 `iota + consts.ImsgNum{Xxx}` 自增,避免全局冲突。
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 国际化业务错误
|
||||
|
||||
```go
|
||||
return errorx.NewBizI(ctx, imsg.ErrDbNameExist)
|
||||
```
|
||||
|
||||
### 国际化操作日志
|
||||
|
||||
```go
|
||||
req.NewPost("", d.Save).Log(req.NewLogSaveI(imsg.LogDbSave))
|
||||
```
|
||||
|
||||
### 模板变量替换
|
||||
|
||||
```go
|
||||
// 定义:ErrDbNotAccess = "未配置数据库【{{.dbName}}】的操作权限"
|
||||
errorx.NewBizI(ctx, imsg.ErrDbNotAccess, "dbName", dbName)
|
||||
|
||||
// 或直接使用 i18n 包
|
||||
i18n.T(imsg.DataSyncSuccessMsg, "count", 100)
|
||||
i18n.TC(ctx, imsg.DataSyncSuccessMsg, "count", 100)
|
||||
```
|
||||
|
||||
## 边界
|
||||
|
||||
- ✅ **Always**: 新增模块时必须同步创建 `imsg.go`、`zh_cn.go`、`en.go` 三个文件
|
||||
- ✅ **Always**: 操作日志消息以 `Log` 开头,业务错误以 `Err` 开头
|
||||
- 🚫 **Never**: 在 `errorx.NewBiz("硬编码中文")` 中直接使用硬编码文本,必须走国际化
|
||||
45
docs/server/infrastructure.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# Infrastructure 层规范
|
||||
|
||||
## Repository 实现
|
||||
|
||||
```go
|
||||
package persistence
|
||||
|
||||
type dbRepoImpl struct {
|
||||
base.RepoImpl[*entity.Db]
|
||||
}
|
||||
|
||||
func newDbRepo() repository.Db {
|
||||
return &dbRepoImpl{}
|
||||
}
|
||||
|
||||
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, orderBy ...string) (*model.PageResult[*entity.DbListPO], error) {
|
||||
pd := model.NewCond().
|
||||
Eq("instance_id", condition.InstanceId).
|
||||
In("code", condition.Codes).
|
||||
Like("name", condition.Name)
|
||||
|
||||
list := []*entity.DbListPO{}
|
||||
return gormx.PageByCond(d.GetModel(), pd, condition.PageParam, list)
|
||||
}
|
||||
```
|
||||
|
||||
## GORMX 常用操作
|
||||
|
||||
```go
|
||||
// 条件构建
|
||||
pd := model.NewCond().Eq("status", 1).In("id", ids).Like("name", keyword)
|
||||
|
||||
// 分页查询
|
||||
result, err := gormx.PageByCond(repo.GetModel(), pd, pageParam, &list)
|
||||
|
||||
// 单条查询
|
||||
err := gormx.GetByCond(repo.GetModel(), pd, &entity)
|
||||
|
||||
// 更新
|
||||
err := gormx.UpdateByCond(repo.GetModel(), values, pd)
|
||||
```
|
||||
62
docs/server/quality.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 代码质量与 Git 规范
|
||||
|
||||
## 函数长度
|
||||
|
||||
- 单个函数不超过 100 行
|
||||
- 复杂逻辑拆分为私有方法
|
||||
|
||||
## Error 处理
|
||||
|
||||
```go
|
||||
// ✅ 完整处理
|
||||
result, err := doSomething()
|
||||
if err != nil {
|
||||
logx.Errorf("操作失败: %v", err)
|
||||
return errorx.NewBiz("操作失败")
|
||||
}
|
||||
|
||||
// 🚫 忽略错误
|
||||
result, _ := doSomething()
|
||||
```
|
||||
|
||||
## 资源释放
|
||||
|
||||
```go
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
```
|
||||
|
||||
## 魔法数字
|
||||
|
||||
```go
|
||||
const MaxRetryCount = 3
|
||||
if retry > MaxRetryCount { ... } // ✅
|
||||
if retry > 3 { ... } // 🚫
|
||||
```
|
||||
|
||||
## Git 提交格式
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
```
|
||||
|
||||
**Type 类型**: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
|
||||
|
||||
**示例**:
|
||||
```
|
||||
feat(db): 添加数据库备份功能
|
||||
|
||||
- 实现定时备份任务
|
||||
- 支持增量备份和全量备份
|
||||
|
||||
Closes #123
|
||||
```
|
||||
26
docs/server/security.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# 安全与权限规范
|
||||
|
||||
## 权限控制
|
||||
|
||||
```go
|
||||
// 路由级别
|
||||
req.NewPost(":dbId/exec-sql", d.ExecSql).RequiredPermissionCode("db:sqlscript:run")
|
||||
|
||||
// 代码级别
|
||||
biz.IsTrue(account.HasPermission("db:sqlscript:run"), "无权限执行SQL")
|
||||
```
|
||||
|
||||
## 敏感信息
|
||||
|
||||
- 资源密码使用 AES 加密存储
|
||||
- `aes.key` 和 `jwt.key` 必须使用随机字符串
|
||||
|
||||
## OWASP 安全准则
|
||||
|
||||
- 防范 SQL 注入:使用参数化查询
|
||||
- 防范 XSS:输出转义
|
||||
- 防范 CSRF:配合前端同源策略
|
||||
@@ -5,4 +5,10 @@ VITE_PORT = 8889
|
||||
VITE_OPEN = false
|
||||
|
||||
# public path 配置线上环境路径(打包)
|
||||
VITE_PUBLIC_PATH = ''
|
||||
VITE_PUBLIC_PATH = './'
|
||||
|
||||
VITE_EDITOR=idea
|
||||
|
||||
# 路由模式
|
||||
# Optional: hash | history
|
||||
VITE_ROUTER_MODE = hash
|
||||
@@ -1,5 +1,7 @@
|
||||
# 本地环境
|
||||
ENV = 'development'
|
||||
|
||||
VITE_OPEN = true
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = '/api'
|
||||
@@ -2,4 +2,6 @@
|
||||
ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = 'http://api.mayflygo.1yue.net/api'
|
||||
VITE_API_URL = '/api'
|
||||
|
||||
VITE_ROUTER_MODE = history
|
||||
@@ -1,7 +1,8 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
*.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
106
frontend/eslint.config.js
Normal file
@@ -0,0 +1,106 @@
|
||||
// import js from '@eslint/js';
|
||||
// import tseslint from 'typescript-eslint';
|
||||
// import vuePlugin from 'eslint-plugin-vue';
|
||||
// import vueParser from 'vue-eslint-parser';
|
||||
|
||||
// export default tseslint.config(
|
||||
// {
|
||||
// ignores: [
|
||||
// '*.sh',
|
||||
// 'node_modules',
|
||||
// 'lib',
|
||||
// '*.md',
|
||||
// '*.scss',
|
||||
// '*.woff',
|
||||
// '*.ttf',
|
||||
// '.vscode',
|
||||
// '.idea',
|
||||
// 'dist',
|
||||
// 'mock',
|
||||
// 'public',
|
||||
// 'bin',
|
||||
// 'build',
|
||||
// 'config',
|
||||
// 'index.html',
|
||||
// 'src/assets',
|
||||
// ],
|
||||
// },
|
||||
// js.configs.recommended,
|
||||
// ...tseslint.configs.recommended,
|
||||
// ...vuePlugin.configs['flat/recommended'],
|
||||
// {
|
||||
// files: ['**/*.{js,ts,tsx,vue}'],
|
||||
// languageOptions: {
|
||||
// ecmaVersion: 2021,
|
||||
// sourceType: 'module',
|
||||
// parser: vueParser,
|
||||
// parserOptions: {
|
||||
// parser: tseslint.parser,
|
||||
// },
|
||||
// globals: {
|
||||
// browser: true,
|
||||
// es2021: true,
|
||||
// node: true,
|
||||
// console: true,
|
||||
// window: true,
|
||||
// document: true,
|
||||
// setTimeout: true,
|
||||
// },
|
||||
// },
|
||||
// plugins: {
|
||||
// vue: vuePlugin,
|
||||
// },
|
||||
// rules: {
|
||||
// '@typescript-eslint/ban-ts-ignore': 'off',
|
||||
// '@typescript-eslint/explicit-function-return-type': 'off',
|
||||
// '@typescript-eslint/no-explicit-any': 'off',
|
||||
// '@typescript-eslint/no-var-requires': 'off',
|
||||
// '@typescript-eslint/no-empty-function': 'off',
|
||||
// '@typescript-eslint/no-use-before-define': 'off',
|
||||
// '@typescript-eslint/ban-ts-comment': 'off',
|
||||
// '@typescript-eslint/ban-types': 'off',
|
||||
// '@typescript-eslint/no-non-null-assertion': 'off',
|
||||
// '@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
// '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
||||
// '@typescript-eslint/no-unused-vars': 'off',
|
||||
|
||||
// // Vue rules
|
||||
// 'vue/html-indent': ['error', 4],
|
||||
// 'vue/script-indent': ['error', 4],
|
||||
// 'vue/custom-event-name-casing': 'off',
|
||||
// 'vue/attributes-order': 'off',
|
||||
// 'vue/one-component-per-file': 'off',
|
||||
// 'vue/html-closing-bracket-newline': 'off',
|
||||
// 'vue/max-attributes-per-line': 'off',
|
||||
// 'vue/multiline-html-element-content-newline': 'off',
|
||||
// 'vue/singleline-html-element-content-newline': 'off',
|
||||
// 'vue/attribute-hyphenation': 'off',
|
||||
// 'vue/html-self-closing': 'off',
|
||||
// 'vue/no-multiple-template-root': 'off',
|
||||
// 'vue/require-default-prop': 'off',
|
||||
// 'vue/no-v-model-argument': 'off',
|
||||
// 'vue/no-arrow-functions-in-watch': 'off',
|
||||
// 'vue/no-template-key': 'off',
|
||||
// 'vue/no-v-html': 'off',
|
||||
// 'vue/no-unused-vars': 'off',
|
||||
// 'vue/comment-directive': 'off',
|
||||
// 'vue/no-parsing-error': 'off',
|
||||
// 'vue/no-deprecated-v-on-native-modifier': 'off',
|
||||
// 'vue/multi-word-component-names': 'off',
|
||||
|
||||
// // JavaScript rules
|
||||
// 'no-useless-escape': 'off',
|
||||
// 'no-sparse-arrays': 'off',
|
||||
// 'no-prototype-builtins': 'off',
|
||||
// 'no-constant-condition': 'off',
|
||||
// 'no-use-before-define': 'off',
|
||||
// 'no-restricted-globals': 'off',
|
||||
// 'no-restricted-syntax': 'off',
|
||||
// 'generator-star-spacing': 'off',
|
||||
// 'no-unreachable': 'off',
|
||||
// 'no-unused-vars': 'off',
|
||||
// 'no-case-declarations': 'off',
|
||||
// 'no-redeclare': 'off',
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
24
frontend/index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh_CN">
|
||||
|
||||
<app-config />
|
||||
|
||||
<head>
|
||||
<base href="/" />
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="keywords" content="mayfly-go" />
|
||||
<meta name="description" content="" />
|
||||
|
||||
<link type="favicon" rel="shortcut icon" href="favicon.ico" />
|
||||
<title>mayfly-go</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="application/javascript" src="/config.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
82
frontend/package.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "mayfly-go-frontend",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"build-preview": "npm run build && npm run preview",
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@logicflow/core": "^2.2.1",
|
||||
"@logicflow/extension": "^2.2.1",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-search": "^0.16.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"asciinema-player": "^3.15.1",
|
||||
"axios": "^1.16.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.20",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.14.0",
|
||||
"js-base64": "^3.7.8",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"json-bigint": "^1.0.0",
|
||||
"mermaid": "^11.15.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-sql-languages": "^1.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"qrcode.vue": "^3.9.0",
|
||||
"screenfull": "^6.0.2",
|
||||
"shiki": "^4.0.2",
|
||||
"shiki-stream": "^0.1.4",
|
||||
"sortablejs": "^1.15.7",
|
||||
"sql-formatter": "^15.7.3",
|
||||
"trzsz": "^1.1.6",
|
||||
"uuid": "^13.0.2",
|
||||
"vue": "3.6.0-beta.11",
|
||||
"vue-element-plus-x": "^2.0.2",
|
||||
"vue-i18n": "^11.4.2",
|
||||
"vue-router": "^5.0.6",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"x-markdown-vue": "0.0.200",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/node": "^22.19.18",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/sortablejs": "^1.15.9",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
||||
"@typescript-eslint/parser": "^8.59.2",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"@vue/compiler-sfc": "^3.5.34",
|
||||
"autoprefixer": "^10.5.0",
|
||||
"code-inspector-plugin": "^1.5.1",
|
||||
"eslint": "^10.3.0",
|
||||
"eslint-plugin-vue": "^10.9.1",
|
||||
"postcss": "^8.5.14",
|
||||
"prettier": "^3.8.3",
|
||||
"sass": "^1.99.0",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vite": "^8.0.12",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vue-eslint-parser": "^10.4.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
6
frontend/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
39
frontend/prettier.config.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export default {
|
||||
// 一行最多多少个字符
|
||||
printWidth: 160,
|
||||
// 指定每个缩进级别的空格数
|
||||
tabWidth: 4,
|
||||
// 使用制表符而不是空格缩进行
|
||||
useTabs: false,
|
||||
// 在语句末尾打印分号
|
||||
semi: true,
|
||||
// 使用单引号而不是双引号
|
||||
singleQuote: true,
|
||||
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
|
||||
quoteProps: 'as-needed',
|
||||
// 在JSX中使用单引号而不是双引号
|
||||
jsxSingleQuote: false,
|
||||
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
|
||||
trailingComma: 'es5',
|
||||
// 在对象文字中的括号之间打印空格
|
||||
bracketSpacing: true,
|
||||
// jsx 标签的反尖括号需要换行
|
||||
jsxBracketSameLine: false,
|
||||
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
|
||||
arrowParens: 'always',
|
||||
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
// 指定要使用的解析器,不需要写文件开头的 @prettier
|
||||
requirePragma: false,
|
||||
// 不需要自动在文件开头插入 @prettier
|
||||
insertPragma: false,
|
||||
// 使用默认的折行标准 always\never\preserve
|
||||
proseWrap: 'preserve',
|
||||
// 指定HTML文件的全局空格敏感度 css\strict\ignore
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
// Vue文件脚本和样式标签缩进
|
||||
vueIndentScriptAndStyle: false,
|
||||
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
|
||||
endOfLine: 'lf',
|
||||
};
|
||||
25
frontend/public/config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
window.globalConfig = {
|
||||
// 默认为空,以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
|
||||
BaseApiUrl: '',
|
||||
BaseWsUrl: '',
|
||||
};
|
||||
|
||||
// index.html添加百秒级时间戳,防止被浏览器缓存
|
||||
// !(function () {
|
||||
// let t = 't=' + new Date().getTime().toString().substring(0, 8);
|
||||
// let search = location.search;
|
||||
// let m = search && search.match(/t=\d*/g);
|
||||
|
||||
// console.log(location);
|
||||
// if (m[0]) {
|
||||
// if (m[0] !== t) {
|
||||
// location.search = search.replace(m[0], t);
|
||||
// }
|
||||
// } else {
|
||||
// if (search.indexOf('?') > -1) {
|
||||
// location.search = search + '&' + t;
|
||||
// } else {
|
||||
// location.search = t;
|
||||
// }
|
||||
// }
|
||||
// })();
|
||||
BIN
frontend/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
122
frontend/src/App.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<el-config-provider
|
||||
:size="getGlobalComponentSize"
|
||||
:locale="getGlobalI18n"
|
||||
:button="{ autoInsertSpace: false, round: true }"
|
||||
:dialog="{ alignCenter: true, transition: 'dialog-bounce' }"
|
||||
>
|
||||
<el-watermark
|
||||
:zIndex="100000"
|
||||
:width="210"
|
||||
v-if="themeConfig.isWatermark"
|
||||
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
|
||||
:content="themeConfig.watermarkText"
|
||||
class="h-full!"
|
||||
>
|
||||
<router-view />
|
||||
</el-watermark>
|
||||
<router-view v-if="!themeConfig.isWatermark" />
|
||||
|
||||
<Setings />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="app">
|
||||
import { onMounted, nextTick, watch, computed, defineAsyncComponent } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useIntervalFn } from '@vueuse/core';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import EnumValue from './common/Enum';
|
||||
import { I18nEnum } from './common/commonEnum';
|
||||
import { saveThemeConfig } from './common/utils/storage';
|
||||
|
||||
const Setings = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/setings.vue'));
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const themeConfigStores = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(themeConfigStores);
|
||||
|
||||
// 定义变量内容
|
||||
const { locale, t } = useI18n();
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 初始化系统主题
|
||||
themeConfigStores.initThemeConfig();
|
||||
});
|
||||
});
|
||||
|
||||
// 监听 themeConfig isWartermark配置文件的变化
|
||||
watch(
|
||||
() => themeConfig.value.isWatermark,
|
||||
(val) => {
|
||||
if (val) {
|
||||
setTimeout(() => {
|
||||
setWatermarkContent();
|
||||
refreshWatermarkTime();
|
||||
resume();
|
||||
}, 500);
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => themeConfig.value.globalI18n,
|
||||
(val) => {
|
||||
locale.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
themeConfig,
|
||||
(val) => {
|
||||
saveThemeConfig(val);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 获取全局组件大小
|
||||
const getGlobalComponentSize = computed(() => {
|
||||
return themeConfig.value.globalComponentSize;
|
||||
});
|
||||
|
||||
// 获取全局 i18n
|
||||
const getGlobalI18n = computed(() => {
|
||||
return EnumValue.getEnumByValue(I18nEnum, locale.value)?.extra.el;
|
||||
});
|
||||
|
||||
// 刷新水印时间
|
||||
const { pause, resume } = useIntervalFn(() => {
|
||||
if (!themeConfig.value.isWatermark) {
|
||||
pause();
|
||||
}
|
||||
refreshWatermarkTime();
|
||||
}, 60000);
|
||||
|
||||
const setWatermarkContent = () => {
|
||||
themeConfigStores.setWatermarkUser();
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新水印时间
|
||||
*/
|
||||
const refreshWatermarkTime = () => {
|
||||
themeConfigStores.setWatermarkNowTime();
|
||||
};
|
||||
|
||||
// 监听路由的变化,设置网站标题
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
document.title = `${t((route.meta.title as string) || '')} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
|
||||
});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
1
frontend/src/assets/icon/ai/ai.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1775984689718" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9497" width="48" height="48"><path d="M710.8 98.1H318.5c-120 0-217.6 97.6-217.6 217.6V708c0 120 97.6 217.6 217.6 217.6h392.3c120 0 217.6-97.6 217.6-217.6V315.7c0.1-120-97.6-217.6-217.6-217.6z m-30 57.4L652.6 214c-8.8 18.3-27.7 30.2-48.1 30.2H424.9c-20.4 0-39.2-11.9-48.1-30.2l-28.2-58.6h332.2zM871.1 708c0 88.4-71.9 160.3-160.3 160.3H318.5c-88.4 0-160.3-71.9-160.3-160.3V315.7c0-77.4 55.2-142.2 128.2-157l38.6 80.2c18.3 38.1 57.5 62.7 99.7 62.7h179.6c42.3 0 81.4-24.6 99.7-62.7l38.6-80.2c73.1 14.9 128.2 79.6 128.2 157V708z" p-id="9498"></path><path d="M486.9 408.2c-4.6-9.9-14.1-15.8-24.3-16.4-0.6 0-1.3-0.1-1.9-0.1-0.7 0-1.4 0.1-2.1 0.1-10.1 0.7-19.6 6.5-24.2 16.4l-142.7 306c-6.7 14.4-0.5 31.4 13.9 38.1 3.9 1.8 8 2.7 12.1 2.7 10.8 0 21.1-6.1 26-16.6l34.4-73.8h165.1l34.4 73.8c4.9 10.4 15.2 16.6 26 16.6 4.1 0 8.2-0.9 12.1-2.7 14.4-6.7 20.6-23.8 13.9-38.1l-142.7-306z m-82 199.1l55.8-119.7 55.8 119.7H404.9zM683.1 391.2c-15.8 0-28.7 12.8-28.7 28.7v306.9c0 15.8 12.8 28.7 28.7 28.7s28.7-12.8 28.7-28.7V419.9c0-15.9-12.8-28.7-28.7-28.7z" p-id="9499"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
frontend/src/assets/icon/ai/assistant.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1775984826390" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11872" width="48" height="48"><path d="M762.112 325.632h-225.536v-65.792c49.408-11.264 86.528-55.296 86.528-108.288 0-61.184-49.92-110.848-111.104-110.848s-111.104 49.664-111.104 110.848c0 52.736 37.12 97.024 86.528 108.288v65.792h-225.536c-87.808 0-158.976 71.424-158.976 158.976V706.56c0 87.808 71.424 158.976 158.976 158.976h500.224c87.808 0 158.976-71.424 158.976-158.976v-221.696c0-87.808-71.424-159.232-158.976-159.232z m-312.064-174.08c0-34.048 27.904-61.952 61.952-61.952s61.952 27.904 61.952 61.952-27.904 61.952-61.952 61.952-61.952-27.904-61.952-61.952zM872.192 706.56c0 60.672-49.408 110.08-110.08 110.08H261.888c-60.672 0-110.08-49.408-110.08-110.08v-221.696c0-60.672 49.408-110.08 110.08-110.08h500.224c60.672 0 110.08 49.408 110.08 110.08V706.56zM724.224 934.4H299.776c-13.568 0-24.576 11.008-24.576 24.576s11.008 24.576 24.576 24.576h424.192c13.568 0 24.576-11.008 24.576-24.576s-10.752-24.576-24.32-24.576zM29.696 478.464c-13.568 0-24.576 11.008-24.576 24.576v185.088c0 13.568 11.008 24.576 24.576 24.576s24.576-11.008 24.576-24.576v-185.088c0-13.568-11.008-24.576-24.576-24.576zM994.304 478.464c-13.568 0-24.576 11.008-24.576 24.576v185.088c0 13.568 11.008 24.576 24.576 24.576s24.576-11.008 24.576-24.576v-185.088c0-13.568-11.008-24.576-24.576-24.576z" p-id="11873"></path><path d="M349.184 467.968c-70.4 0-127.488 57.088-127.488 127.488 0 70.4 57.344 127.488 127.488 127.488s127.744-57.088 127.744-127.488c-0.256-70.144-57.344-127.488-127.744-127.488z m0 206.08c-43.264 0-78.592-35.328-78.592-78.592s35.328-78.592 78.592-78.592 78.592 35.328 78.592 78.592-35.328 78.592-78.592 78.592zM674.816 467.968c-70.4 0-127.744 57.088-127.744 127.488 0 70.4 57.344 127.488 127.744 127.488s127.488-57.088 127.488-127.488c0.256-70.144-57.088-127.488-127.488-127.488z m0 206.08c-43.264 0-78.592-35.328-78.592-78.592s35.328-78.592 78.592-78.592 78.592 35.328 78.592 78.592-35.328 78.592-78.592 78.592z" p-id="11874"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
16
frontend/src/assets/icon/db/clickhouse.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 150 150">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #161616;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M27,25.7c0-.6.5-1.2,1.2-1.2h8.8c.6,0,1.2.5,1.2,1.2v97.7c0,.6-.5,1.2-1.2,1.2h-8.8c-.6,0-1.2-.5-1.2-1.2V25.7Z"/>
|
||||
<path class="cls-1" d="M49.2,25.7c0-.6.5-1.2,1.2-1.2h8.8c.6,0,1.2.5,1.2,1.2v97.7c0,.6-.5,1.2-1.2,1.2h-8.8c-.6,0-1.2-.5-1.2-1.2V25.7Z"/>
|
||||
<path class="cls-1" d="M71.4,25.7c0-.6.5-1.2,1.2-1.2h8.8c.6,0,1.2.5,1.2,1.2v97.7c0,.6-.5,1.2-1.2,1.2h-8.8c-.6,0-1.2-.5-1.2-1.2V25.7Z"/>
|
||||
<path class="cls-1" d="M93.6,25.7c0-.6.5-1.2,1.2-1.2h8.8c.6,0,1.2.5,1.2,1.2v97.7c0,.6-.5,1.2-1.2,1.2h-8.8c-.6,0-1.2-.5-1.2-1.2V25.7Z"/>
|
||||
<path class="cls-1" d="M115.9,64.6c0-.6.5-1.2,1.2-1.2h8.8c.6,0,1.2.5,1.2,1.2v19.9c0,.6-.5,1.2-1.2,1.2h-8.8c-.6,0-1.2-.5-1.2-1.2v-19.9Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 962 B |
1
frontend/src/assets/icon/db/db.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1756305127175" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22356" width="48" height="48"><path d="M959.718832 123.963683C872.444401 50.185297 704.593576 0.299912 511.850044 0.299912S151.255687 50.185297 63.981255 123.963683C23.193205 158.453578 0 198.04198 0 240.22962v543.840672c0 132.461193 229.132871 239.929708 511.850044 239.929708s511.850044-107.468515 511.850044-239.929708v-543.840672c0-42.18764-23.193205-81.776042-63.981256-116.265937zM87.774285 189.64444c19.794201-21.893586 50.685151-43.087377 89.373816-61.182075 42.287611-19.794201 92.073025-35.489603 147.956653-46.586352C384.087474 70.17944 446.869081 64.281168 511.850044 64.281168s127.76257 5.898272 186.745289 17.594845c55.883628 11.096749 105.669042 26.792151 147.956654 46.586352 38.688665 18.094699 69.579615 39.28849 89.373816 61.182075 15.795372 17.494875 23.793029 34.489896 23.793029 50.48521 0 16.095285-7.997657 33.090306-23.793029 50.485209-19.794201 21.893586-50.685151 43.087377-89.373816 61.182075-42.287611 19.894172-92.073025 35.489603-147.956654 46.586352-58.98272 11.696573-121.864298 17.594845-186.745289 17.594845s-127.76257-5.898272-186.74529-17.594845c-55.883628-11.096749-105.669042-26.792151-147.956653-46.586352-38.688665-18.094699-69.579615-39.28849-89.373816-61.182075C71.978912 273.319926 63.981255 256.324905 63.981255 240.22962s7.997657-33.090306 23.79303-50.58518zM63.981255 356.495558c87.274431 73.778385 255.125256 123.66377 447.868789 123.66377s360.594357-49.885385 447.868788-123.66377v155.254515c0 16.095285-7.997657 33.090306-23.793029 50.48521-19.794201 21.893586-50.685151 43.087377-89.373816 61.182075-42.287611 19.794201-92.073025 35.489603-147.956654 46.586352-58.98272 11.696573-121.864298 17.594845-186.745289 17.594845s-127.76257-5.898272-186.74529-17.594845c-55.883628-11.096749-105.669042-26.792151-147.956653-46.586352-38.688665-18.094699-69.579615-39.28849-89.373816-61.182075C71.978912 544.740408 63.981255 527.745387 63.981255 511.750073V356.495558z m895.737577 427.574734c0 16.095285-7.997657 33.090306-23.793029 50.485209-19.794201 21.893586-50.685151 43.087377-89.373816 61.182076-42.287611 19.894172-92.073025 35.489603-147.956654 46.586352-58.98272 11.696573-121.864298 17.594845-186.745289 17.594845s-127.76257-5.898272-186.74529-17.594845c-55.883628-11.096749-105.669042-26.792151-147.956653-46.586352-38.688665-18.094699-69.579615-39.28849-89.373816-61.182076C71.978912 817.160597 63.981255 800.165576 63.981255 784.070292V627.91604c87.274431 73.778385 255.125256 123.66377 447.868789 123.663771s360.594357-49.885385 447.868788-123.663771v156.154252z" p-id="22357"></path><path d="M167.950796 519.847701m-39.988285 0a39.988285 39.988285 0 1 0 79.976569 0 39.988285 39.988285 0 1 0-79.976569 0Z" p-id="22358"></path><path d="M167.950796 791.768037m-39.988285 0a39.988285 39.988285 0 1 0 79.976569 0 39.988285 39.988285 0 1 0-79.976569 0Z" p-id="22359"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
1
frontend/src/assets/icon/db/dm.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-db-dm" viewBox="0 0 1024 1024"><path d="M335.303111 324.096c0 48.071111-46.819556 198.542222 145.635556 218.794667 84.081778 18.773333 105.585778 60.643556 98.304 186.026666a397.653333 397.653333 0 0 1-1.365334 30.264889c-29.468444 15.473778-50.232889 24.803556-62.350222 27.989334 1.934222-17.351111 21.617778-154.339556-18.659555-186.254223-40.277333-31.857778-68.266667-35.783111-109.795556-41.244444-20.081778-2.673778-51.484444-19.512889-75.264-34.133333-23.779556-14.563556-37.660444-42.780444-41.870222-69.176889-2.844444-17.635556-3.640889-43.121778-2.503111-76.458667 15.303111-17.066667 37.944889-35.726222 67.868444-55.751111z m80.554667-46.193778c-1.308444 17.408-28.444444 168.391111 49.436444 204.231111 28.444444 13.141333 40.391111 18.204444 60.472889 22.926223 20.081778 4.721778 70.940444 11.491556 91.989333 32.768 8.533333 8.533333 21.390222 23.04 28.558223 41.415111 10.467556 27.022222 13.255111 65.422222 13.255111 105.187555v30.037334c-22.698667 17.009778-41.870222 28.842667-57.685334 35.669333 0-20.366222 7.281778-115.882667-11.889777-155.192889-19.171556-39.253333-39.708444-57.742222-73.841778-62.293333-34.133333-4.551111-89.144889-18.659556-111.104-37.148445-22.016-18.432-50.801778-46.193778-50.801778-92.899555 0-31.118222 0.739556-61.041778 2.218667-89.827556 22.072889-16.099556 41.870222-27.704889 59.392-34.872889z m81.863111-36.522666c-2.161778 34.474667-7.338667 116.736 9.159111 159.459555 16.497778 42.723556 51.370667 66.56 89.713778 73.159111 38.343111 6.542222 106.496 29.127111 123.448889 69.973334 15.473778 37.148444 17.066667 59.335111 17.294222 77.539555v32.654222c-21.048889 19.171556-39.651556 34.133333-55.864889 44.942223 0-56.149333-0.170667-109.624889-16.497778-141.084445-16.270222-31.402667-37.717333-50.232889-84.423111-61.098667-46.648889-10.922667-95.459556-15.587556-124.928-53.703111-19.683556-25.372444-26.794667-67.299556-21.447111-125.781333l4.835556-49.550222c19.342222-10.467556 38.912-19.342222 58.709333-26.510222z" fill="#E8130D" ></path><path d="M235.804444 406.243556c2.275556 35.896889 5.176889 60.017778 8.760889 72.362666 10.126222 34.816 39.025778 60.928 51.939556 68.721778 20.935111 12.686222 61.895111 29.923556 93.240889 34.133333 20.878222 2.730667 43.406222 9.443556 67.697778 20.081778 22.755556 15.018667 34.474667 29.582222 35.271111 43.804445 4.835556 91.420444-9.216 122.311111-15.303111 154.453333-69.632 42.552889-357.432889 46.933333-363.349334-117.532445-3.982222-109.681778 36.579556-201.671111 121.742222-276.024888z m641.308445-133.632c63.374222 92.16 29.297778 209.749333-102.172445 352.824888a188.245333 188.245333 0 0 0-3.356444-32.085333c-16.327111-72.704-45.511111-104.106667-81.294222-117.304889-35.84-13.255111-139.832889-14.506667-160.028445-87.779555a415.459556 415.459556 0 0 1 6.599111-164.010667c161.848889-45.454222 275.285333-29.297778 340.252445 48.355556z" fill="#1D2683" ></path></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
1
frontend/src/assets/icon/db/guass.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-gauss" viewBox="0 0 1024 1024"><path d="M936.789333 493.2608H562.961067a58.026667 58.026667 0 0 0-58.0608 58.0608v296.8576a58.026667 58.026667 0 0 0 58.0608 58.0608h373.828266a58.026667 58.026667 0 0 0 58.0608-58.0608v-296.8576a58.094933 58.094933 0 0 0-58.0608-58.0608z m-216.029866 305.902933c-16.725333 16.725333-45.021867 29.320533-79.496534 29.320534-67.208533 0-115.985067-47.069867-115.985066-129.297067 0-81.851733 50.4832-131.003733 117.0432-131.003733 35.464533 0 59.357867 15.701333 74.683733 31.744l-21.504 25.258666c-12.253867-12.629333-27.648-22.528-51.848533-22.528-46.728533 0-77.789867 36.1472-77.789867 95.197867 0 59.6992 27.648 96.187733 79.496533 96.187733 15.325867 0 30.685867-4.437333 39.560534-12.288v-59.016533h-49.800534v-32.426667h85.640534v108.8512z m124.6208 24.8832h-67.208534v-251.0848h65.160534c77.073067 0 121.105067 42.666667 121.105066 124.5184 0 81.544533-44.032 126.5664-119.057066 126.5664z" fill="#417CB7" ></path><path d="M840.6016 605.047467h-22.869333v186.606933h22.869333c53.896533 0 82.909867-31.709867 82.909867-94.139733 0-62.805333-28.9792-92.4672-82.909867-92.4672z" fill="#417CB7" ></path><path d="M409.565867 117.794133c-72.157867 0-114.4832 59.869867-114.4832 143.2576 0 131.1744 201.250133 394.069333 201.250133 394.069334V218.794667c0.068267 0 2.048-101.000533-86.766933-101.000534zM180.974933 259.584c-27.648-5.632-87.1424 21.7088-87.1424 139.093333 0 137.3184 368.264533 278.357333 368.264534 278.357334S246.101333 272.7936 180.974933 259.584zM93.525333 686.933333c93.5936 53.384533 346.487467 27.409067 346.487467 27.409067S85.981867 497.425067 47.274667 497.493333c-33.314133 0.068267-29.457067 146.2272 46.250666 189.44z m152.2688 154.385067c71.304533 0 183.944533-98.6112 183.944534-98.6112H125.2352s32.529067 98.6112 120.558933 98.6112z m366.592-723.524267c-88.951467 0-86.903467 101.000533-86.903466 101.000534v266.478933c9.966933-8.2944 21.7088-14.165333 34.474666-14.165333h87.620267c42.222933-73.5232 79.291733-153.838933 79.291733-210.056534-0.1024-83.319467-42.359467-143.2576-114.4832-143.2576z m228.488534 141.789867c-32.3584 6.519467-101.819733 109.499733-163.703467 211.524267h219.921067c18.944-23.7568 30.958933-48.128 30.958933-72.430934 0.034133-117.384533-59.528533-144.6912-87.176533-139.093333z" fill="#C91E1D" ></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
1
frontend/src/assets/icon/db/kingbase.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-kingbase" viewBox="0 0 1024 1024"><path d="M295.808 424.32V128h158.016v214.72L896 128v148.16L453.824 508.352 896 731.968V896l-442.176-215.168V896H295.808V590.976L128 512z" fill="#CF152D" ></path></svg>
|
||||
|
After Width: | Height: | Size: 250 B |
1
frontend/src/assets/icon/db/mariadb.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-mariadb" viewBox="0 0 1088 1024"><path d="M1016.64 171.84c-15.68 0.64-10.88 5.12-45.12 13.44-34.56 8.64-76.8 5.76-113.92 21.44-111.04 46.4-133.44 205.76-234.24 262.72-75.52 42.56-151.68 46.08-219.84 67.52-45.12 14.08-94.08 42.88-135.04 78.08-31.68 27.2-32.32 51.2-65.6 85.44-35.2 36.48-140.48 0.64-188.16 56.64 15.36 15.36 22.08 19.84 52.48 15.68-6.4 11.84-43.2 21.76-35.84 39.36 7.68 18.24 96.96 30.72 177.92-18.24 37.76-22.72 67.84-55.68 126.72-63.68 76.16-10.24 163.84 6.4 251.84 19.2-13.12 39.04-39.36 64.96-60.48 96-6.4 7.04 13.12 7.68 35.52 3.52 40.32-9.92 69.12-17.92 99.52-35.52 37.12-21.76 42.88-77.44 88.64-89.28 25.6 39.04 94.72 48.32 137.6 16.96-37.76-10.56-48-90.88-35.52-126.4 12.16-33.6 24-87.04 36.16-131.2 13.12-47.68 17.92-107.52 33.6-131.84 23.68-36.48 49.92-48.96 72.64-69.44 22.72-20.48 43.52-40.64 42.88-87.68 0.32-14.72-7.36-23.04-21.76-22.72z" fill="#002B64" ></path><path d="M47.68 808.96c57.92 8.32 92.8 0 139.2-20.16 39.36-16.96 77.44-52.48 124.16-67.52 68.48-22.08 143.68 0 216.64 4.48 17.92 0.96 35.52 0.96 53.12-0.96 27.2-16.64 26.56-79.36 53.12-85.12-0.64 88-36.8 140.48-74.56 191.68 79.36-14.08 127.04-59.84 159.04-121.28 9.6-18.56 17.92-38.72 25.28-59.52 11.52 8.64 4.8 35.2 10.56 49.6 54.72-30.4 86.08-100.16 106.88-170.56 24-81.28 33.92-163.84 49.28-187.84 15.04-23.36 38.72-38.08 60.16-53.12 24.32-16.96 46.08-34.88 49.92-67.52-25.6-2.24-31.68-8.32-35.52-21.44-12.8 7.36-24.64 8.96-38.08 9.28-11.52 0.32-24.32-0.32-40 1.28-128.96 13.12-145.28 155.2-227.84 235.84-6.08 5.76-12.48 11.2-19.52 16.32-28.8 21.44-64.32 36.8-96.96 49.28-52.8 20.16-103.04 21.76-152.64 39.04-36.48 12.8-73.28 31.36-103.36 51.84-6.72 5.44-13.76 10.56-20.16 16-17.6 14.4-29.12 30.4-40.32 46.72-11.52 16.96-22.72 34.24-39.36 50.88-27.52 26.88-129.6 8-165.76 32.64-4.16 2.88-7.36 6.08-9.28 10.24 19.52 8.96 32.64 3.52 55.36 6.08 2.88 21.12-46.72 33.92-39.36 43.84zM828.48 654.4c1.6 24.64 15.68 73.6 28.48 85.44-24.64 6.08-67.2-3.84-78.08-21.44 5.44-25.28 34.56-48.32 49.6-64z" fill="#FFFFFF" ></path><path d="M864.64 263.68c18.24 15.68 56.64 3.2 49.6-28.48-28.48-2.24-44.8 7.36-49.6 28.48zM991.68 226.88c-4.8 10.24-14.08 23.36-14.08 49.28 0 4.48-3.52 7.36-3.52 0.64 0.32-25.28 7.04-36.16 14.08-50.56 3.52-6.08 5.44-3.52 3.52 0.64z" fill="#002B64" ></path><path d="M986.88 223.04c-5.76 9.6-19.52 27.52-21.76 53.12-0.32 4.48-4.16 7.04-3.52 0.32 2.56-25.28 13.44-40.96 21.76-54.72 3.84-5.44 5.76-2.88 3.52 1.28zM982.4 217.92c-6.4 9.28-27.84 30.4-32.32 56-0.64 4.48-4.48 6.72-3.52 0 4.48-24.96 22.72-44.48 32.32-57.28 4.48-5.12 6.08-2.56 3.52 1.28z" fill="#002B64" ></path><path d="M978.56 212.48c-7.68 8.32-32.96 35.2-40.96 59.84-1.28 4.16-5.44 6.08-3.52-0.32 8-24 30.08-49.92 41.28-61.44 4.8-4.8 6.08-1.6 3.2 1.92z" fill="#002B64" ></path></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
1
frontend/src/assets/icon/db/mysql.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="icon-op-mysql" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M856.368 169.2H577.76a24 24 0 0 0 0 48h278.608a45.968 45.968 0 0 1 45.92 45.92v557.216a45.968 45.968 0 0 1-45.92 45.92h-253.6a1524.576 1524.576 0 0 1-68.304-57.328 24 24 0 1 0-32.096 35.696c8.304 7.456 16.704 14.752 24.784 21.632H299.136a45.968 45.968 0 0 1-45.92-45.92v-41.76a24 24 0 0 0-48 0v41.76a94.032 94.032 0 0 0 93.92 93.92h557.232a94.032 94.032 0 0 0 93.92-93.92V263.088a94.048 94.048 0 0 0-93.92-93.888z" fill="#666666" ></path><path d="M298.496 796.88a24 24 0 0 0 37.312-15.456 895.808 895.808 0 0 1 26.656-102.72 1102.672 1102.672 0 0 0 41.2 57.088 24 24 0 1 0 38-29.328 1078.192 1078.192 0 0 1-65.6-95.36 24 24 0 0 0-43.008 4.352c-1.024 2.816-20.272 56-34.48 112-27.088-39.632-57.504-121.008-6.544-259.984a24 24 0 0 0-6.016-25.696 341.856 341.856 0 0 1-78.8-120.336 233.216 233.216 0 0 0-42.288-76.064c-41.056-49.312-46.944-78.16-40.896-85.504 10.176-12.368 70.528 15.184 119.568 54.544a24 24 0 0 0 26 2.64c1.008-0.496 102.656-50.608 231.184 62.864a664.256 664.256 0 0 1 144.336 194.672 24.112 24.112 0 0 0 25.6 13.76c0.88-0.144 77.744-10.8 151.664 77.584-110.144 19.2-115.2 36.976-118.4 48a24 24 0 0 0 5.744 23.168c32.288 33.792 119.328 136.144 133.984 203.2a24 24 0 0 0 23.424 18.88 24.576 24.576 0 0 0 5.136-0.544 24 24 0 0 0 18.32-28.576c-15.472-70.752-87.888-160.368-124.72-202.416a921.184 921.184 0 0 1 102.72-20.448 24 24 0 0 0 16.608-36.96c-75.616-114.816-165.488-129.6-203.536-130.24a699.504 699.504 0 0 0-149.056-196.048C408.176 134.08 301.008 155.2 262.656 168.288 144.784 77.92 98.624 115.328 87.072 129.344c-26.8 32.48-12.992 81.856 41.04 146.752a185.744 185.744 0 0 1 33.6 60.496 377.6 377.6 0 0 0 80.144 128.768c-79.152 233.728 50.944 327.552 56.64 331.52z" fill="#1771B9" ></path><path d="M290.048 282.288a24.528 24.528 0 0 0-6.896 16.96 24 24 0 0 0 6.896 16.96 24.128 24.128 0 0 0 34.064 0 24.096 24.096 0 0 0 6.88-16.96 24.512 24.512 0 0 0-6.88-16.96 24.944 24.944 0 0 0-34.064 0z" fill="#1771B9" ></path><path d="M477.408 757.232a15.904 15.904 0 0 0-2.896-3.52 24.608 24.608 0 0 0-33.92 0 16.528 16.528 0 0 0-3.04 3.52 36.896 36.896 0 0 0-2.24 4.16c-0.48 1.44-0.944 3.04-1.264 4.48a24.464 24.464 0 0 0-0.496 4.8 24.992 24.992 0 0 0 1.76 9.12 26.336 26.336 0 0 0 5.28 7.84 22.4 22.4 0 0 0 7.68 5.12 23.008 23.008 0 0 0 9.28 1.92 24.256 24.256 0 0 0 16.96-7.04 22.704 22.704 0 0 0 5.12-7.84 22.208 22.208 0 0 0 1.92-9.12 21.92 21.92 0 0 0-1.92-9.28 20.8 20.8 0 0 0-2.224-4.16z" fill="#666666" ></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
frontend/src/assets/icon/db/oracle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-oracle" viewBox="0 0 1024 1024"><path d="M700.245333 188.245333h-376.32a323.754667 323.754667 0 0 0-0.341333 647.509334h376.661333a323.754667 323.754667 0 0 0 0-647.509334z m-8.234666 533.418667H332.202667a209.706667 209.706667 0 0 1 0-419.328h359.808a209.664 209.664 0 1 1 0 419.328z" fill="#C74634" ></path></svg>
|
||||
|
After Width: | Height: | Size: 364 B |
1
frontend/src/assets/icon/db/postgres.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
frontend/src/assets/icon/db/sql.svg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
1
frontend/src/assets/icon/db/sqlite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-sqlite" viewBox="0 0 1024 1024"><path d="M757.589333 85.248c-93.44 79.872-177.92 267.861333-194.218666 317.738667-16.042667 49.066667-18.56 73.173333-18.730667 82.645333v2.901333a11.946667 11.946667 0 0 0 0.256 2.133334c12.544 0 28.202667 51.925333 24.576 51.925333-2.389333 0-11.776-11.776-28.16-35.328l-7.765333 50.602667 20.010666 30.08c5.632 8.405333-5.632 36.906667-8.661333 21.418666-2.048-10.325333-10.666667-23.893333-25.898667-40.704-4.992 35.626667-7.296 53.504-6.997333 53.632 19.626667 7.637333 36.437333 33.450667 32.853333 131.84-0.981333 28.245333 1.877333 75.776 8.704 142.506667l-0.128-0.682667L170.666667 896a85.333333 85.333333 0 0 1-85.333334-85.333333V170.666667a85.333333 85.333333 0 0 1 85.333334-85.333334z" fill="#0082CE" ></path><path d="M544.853333 754.133333c3.584-98.389333-13.226667-124.202667-32.853333-131.84-0.298667-0.128 2.005333-18.005333 6.997333-53.632 15.232 16.810667 23.893333 30.378667 25.898667 40.704 3.029333 15.488 14.293333-13.013333 8.661333-21.418666l-20.053333-30.08 7.808-50.602667c16.384 23.552 25.770667 35.328 28.16 35.328 3.626667 0-12.032-51.925333-24.576-51.925333-0.085333 0.085333-3.584-20.181333 18.474667-87.68 22.016-67.456 168.789333-387.498667 293.802666-351.146667 125.013333 36.394667 46.08 267.178667 29.866667 312.661333-16.128 45.482667-65.578667 117.248-84.992 133.888-12.928 11.093333-47.786667 30.933333-104.448 59.477334l87.552-27.093334c-40.362667 73.301333-69.546667 114.090667-87.552 122.368-27.050667 12.458667-76.970667 61.824-108.885333 100.992-9.898667-67.968 32.256-179.328 88.704-292.864 53.034667-106.752 118.826667-215.082667 163.242666-299.221333-49.92 70.826667-133.12 196.864-178.218666 288.896-64.512 131.712-83.584 229.888-92.970667 303.189333-10.624 83.029333-7.168 160.768 10.282667 233.301334h-10.24c-18.773333-89.941333-27.008-167.722667-24.618667-233.301334z" fill="#024A64" ></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
1
frontend/src/assets/icon/db/sqlserver.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
frontend/src/assets/icon/db/table.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1756305474315" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24277" width="48" height="48"><path d="M960 0H0v1024h1024V0.146286h-64V0z m-640 960.146286h-256v-192h256v192z m0-256.146286h-256V512.146286h256v191.853714z m320 256.146286h-256v-192h256v192z m0-256.146286h-256V512.146286h256v191.853714z m320 256.146286h-256v-192h256v192z m0-256.146286h-256V512.146286h256v191.853714z m0-256h-256V256.146286H640v192h-256V256.146286h-64v192h-256V256.146286h896v191.853714z" p-id="24278"></path></svg>
|
||||
|
After Width: | Height: | Size: 547 B |
1
frontend/src/assets/icon/db/vastbase.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-vastbase" viewBox="0 0 1024 1024"><path d="M22.528 553.984c-1.024 0-1.024-1.024-1.024-1.024 1.024 0 1.024 1.024 1.024 1.024z" fill="#FEA000" ></path><path d="M391.168 413.696v4.096c-1.024 10.24-6.144 20.48-13.312 27.648-6.144 6.144-14.336 11.264-23.552 12.288-1.024 0-2.048 0-4.096 1.024h-3.072H67.584c-25.6 0-46.08-20.48-46.08-46.08 0-12.288 5.12-23.552 13.312-32.768 8.192-8.192 19.456-13.312 32.768-13.312H266.24L166.912 194.56c-12.288-21.504-5.12-50.176 16.384-62.464 7.168-4.096 15.36-6.144 22.528-6.144 15.36 0 30.72 8.192 39.936 22.528l135.168 233.472 4.096 8.192c4.096 8.192 6.144 15.36 6.144 23.552zM529.408 360.448c-1.024 0-2.048 1.024-2.048 1.024s-1.024 1.024-2.048 1.024c-10.24 4.096-20.48 5.12-30.72 2.048-8.192-2.048-16.384-7.168-22.528-14.336-1.024 0-1.024-1.024-2.048-2.048s-1.024-2.048-2.048-3.072l-1.024-1.024-138.24-240.64C315.392 81.92 323.584 53.248 345.088 40.96c11.264-6.144 23.552-7.168 34.816-4.096 11.264 3.072 21.504 10.24 27.648 21.504l99.328 172.032 99.328-172.032c12.288-21.504 40.96-29.696 62.464-16.384 7.168 4.096 13.312 10.24 16.384 16.384 8.192 13.312 8.192 30.72 0 46.08L549.888 336.896l-4.096 8.192c-4.096 6.144-9.216 12.288-16.384 15.36zM645.12 453.632c-1.024 0-1.024-1.024-2.048-1.024s-1.024-1.024-1.024-1.024c-9.216-6.144-14.336-15.36-17.408-25.6-2.048-8.192-2.048-18.432 1.024-26.624 0-1.024 1.024-2.048 1.024-4.096 0-1.024 1.024-2.048 2.048-3.072 0 0 0-1.024 1.024-1.024l139.264-240.64c12.288-21.504 40.96-29.696 62.464-16.384 11.264 6.144 18.432 16.384 21.504 27.648 3.072 11.264 2.048 23.552-5.12 34.816L747.52 367.616h198.656c25.6 0 46.08 20.48 46.08 46.08 0 8.192-2.048 16.384-6.144 22.528-8.192 13.312-22.528 22.528-39.936 22.528H667.648c-8.192 0-16.384-2.048-22.528-5.12zM391.168 596.992v-2.048-2.048c-1.024-10.24-6.144-20.48-13.312-27.648-6.144-6.144-14.336-11.264-23.552-12.288-1.024 0-2.048 0-4.096-1.024h-3.072H67.584c-25.6 0-46.08 20.48-46.08 46.08 0 12.288 5.12 23.552 13.312 32.768 8.192 8.192 19.456 13.312 32.768 13.312H266.24l-99.328 172.032c-12.288 21.504-5.12 50.176 16.384 62.464 7.168 4.096 15.36 6.144 22.528 6.144 15.36 0 30.72-8.192 39.936-22.528l135.168-233.472 4.096-8.192c4.096-9.216 6.144-16.384 6.144-23.552zM529.408 649.216c-1.024 0-2.048-1.024-2.048-1.024s-1.024-1.024-2.048-1.024c-10.24-4.096-20.48-5.12-30.72-2.048-8.192 2.048-16.384 7.168-22.528 14.336-1.024 1.024-2.048 2.048-2.048 3.072-1.024 1.024-1.024 2.048-2.048 3.072l-1.024 1.024-138.24 239.616c-12.288 21.504-5.12 50.176 16.384 62.464 11.264 6.144 23.552 7.168 34.816 4.096 11.264-3.072 21.504-10.24 27.648-21.504l99.328-172.032L606.208 952.32c12.288 21.504 40.96 29.696 62.464 16.384 7.168-4.096 13.312-10.24 16.384-16.384 8.192-13.312 8.192-30.72 0-46.08L549.888 672.768l-4.096-7.168c-4.096-7.168-9.216-12.288-16.384-16.384zM645.12 557.056c-1.024 0-1.024 1.024-2.048 1.024s-1.024 1.024-1.024 1.024c-9.216 6.144-14.336 15.36-17.408 25.6-2.048 8.192-2.048 18.432 1.024 26.624 0 1.024 1.024 2.048 1.024 4.096 0 1.024 1.024 2.048 2.048 3.072 0 0 0 1.024 1.024 1.024L766.976 860.16c12.288 21.504 40.96 29.696 62.464 16.384 11.264-6.144 18.432-16.384 21.504-27.648 3.072-11.264 2.048-23.552-5.12-34.816L747.52 642.048h198.656c25.6 0 46.08-20.48 46.08-46.08 0-8.192-2.048-16.384-6.144-22.528-8.192-13.312-22.528-22.528-39.936-22.528H667.648c-8.192 0-16.384 2.048-22.528 6.144z" fill="#FE9500" ></path></svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
1
frontend/src/assets/icon/docker/docker.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1756107672203" class="icon" viewBox="0 0 1472 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5144" width="48" height="48"><path d="M1449.66628 358.737a233.848 233.848 0 0 0-166.348-35.445 268.717 268.717 0 0 0-108.127-152.273l-31.158-20.026-22.265 30.455a258.736 258.736 0 0 0-22.01 265.39 177.353 177.353 0 0 1-74.28 21.241h-24.953V309.536H830.08228V0H624.44928v154.768H287.27328v154.704H118.68528V468.08H8.44728L3.26528 504.42a493.032 493.032 0 0 0 95.97 353.3c90.149 110.11 234.232 165.964 428.284 165.964a749.848 749.848 0 0 0 585.42-255.025 804.871 804.871 0 0 0 139.86-226.874c187.718-3.391 213.246-134.359 214.27-139.99l4.863-27.447-22.01-15.61z m-766.291-49.84v-92.068h87.717v92.068h-87.717z m-337.176 154.64v-92.068h87.59v92.068h-87.59z m168.588 0v-92.068h87.589v92.068h-87.589z m168.588 0v-92.068h87.717v92.068h-87.717z m170.38-92.068h87.524v92.068h-87.525v-92.068zM683.37428 62.125h87.717v92.003h-87.717V62.125zM514.78728 216.829h87.589v92.068h-87.525v-92.068z m-168.588 0h87.59v92.068h-87.59v-92.068zM177.61228 371.47h87.525v92.068H177.61228v-92.068zM527.19928 938.4a609.348 609.348 0 0 1-235-40.564 399.493 399.493 0 0 0 151.058-66.092 44.018 44.018 0 0 0 7.87-57.582 39.54 39.54 0 0 0-54.575-11.9 375.18 375.18 0 0 1-215.998 62.508 262.639 262.639 0 0 1-19.194-21.433 392.455 392.455 0 0 1-79.591-249.523h943.9a250.035 250.035 0 0 0 155.216-62.06l4.99-4.671a682.157 682.157 0 0 1-658.42 451.636z m699.432-482.412l-25.144-1.215-15.163-21.178a186.566 186.566 0 0 1-21.626-161.358 145.619 145.619 0 0 1 42.483 100.769l-1.663 60.525 54.83-18.682a205.505 205.505 0 0 1 111.07-1.664 170.123 170.123 0 0 1-144.787 42.803zM544.41028 629.31a69.738 69.738 0 1 1-66.412 69.674 68.139 68.139 0 0 1 66.412-69.674z m0 85.413a15.74 15.74 0 1 0-14.971-15.675 15.291 15.291 0 0 0 14.97 15.675z m0 0" p-id="5145"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
frontend/src/assets/icon/es/es-color.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M96.426667 649.173333H712.96a137.173333 137.173333 0 0 0 0-274.346666H96.426667c-12.8 43.52-19.626667 89.514667-19.626667 137.173333s6.826667 93.696 19.626667 137.173333z" fill="#07A5DE" p-id="6101"></path><path d="M563.2 25.6A486.4 486.4 0 0 0 125.354667 299.946667H837.546667c52.096 0 97.450667-29.013333 120.661333-71.808A485.76 485.76 0 0 0 563.2 25.6z" fill="#EFBF19" p-id="6102"></path><path d="M942.421333 816.64a137.258667 137.258667 0 0 0-129.749333-92.586667H125.312A486.4 486.4 0 0 0 563.2 998.4c153.344 0 290.090667-70.954667 379.221333-181.76z" fill="#3EBEB1" p-id="6103"></path><path d="M506.197333 649.173333c12.8-43.52 19.626667-89.514667 19.626667-137.173333s-6.826667-93.696-19.626667-137.173333H96.469333c-12.8 43.52-19.626667 89.514667-19.626666 137.173333s6.826667 93.696 19.626666 137.173333h409.728z" fill="#231F20" p-id="6104"></path><path d="M477.269333 724.053333H125.354667a488.533333 488.533333 0 0 0 175.957333 197.888 488.533333 488.533333 0 0 0 175.957333-197.930666z" fill="#019B8F" p-id="6105"></path><path d="M301.312 102.058667a488.533333 488.533333 0 0 1 175.957333 197.930666H125.354667a488.533333 488.533333 0 0 1 175.957333-197.930666z" fill="#D8A22A" p-id="6106"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
frontend/src/assets/icon/es/es.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M465.664 679.168c105.301333 0.597333 172.970667 1.066667 202.922667 1.450667 20.48 0.256 36.181333 0.426667 47.274666 0.469333h3.84c45.824 0 84.096 8.533333 114.901334 25.258667 31.189333 16.938667 54.826667 42.368 70.826666 76.245333l1.152 2.517333a24.106667 24.106667 0 0 1-1.621333 3.413334c-46.336 67.84-101.034667 116.565333-164.096 146.346666-63.146667 29.824-134.613333 40.704-214.485333 32.469334-159.232-16.384-283.477333-106.24-372.352-269.994667a5.973333 5.973333 0 0 1 3.584-8.618667c13.653333-3.968 27.733333-6.528 41.941333-7.594666 91.306667-1.365333 170.538667-1.877333 238.165333-1.962667h27.946667z m44.885333 63.829333l-0.64 1.152c-3.754667 6.485333-9.386667 15.36-16.128 25.6l-2.645333 3.925334-1.578667 2.346666c-21.205333 31.445333-51.072 72.234667-70.784 94.464 64.853333 34.304 133.162667 45.44 227.157334 27.52 95.146667-18.090667 145.450667-52.565333 175.829333-114.090666-5.034667-10.581333-14.592-19.285333-31.488-27.733334-12.8-6.4-32.426667-11.050667-58.752-14.250666l-221.013333 1.066666z m-257.578666-5.546666l1.237333 1.536c21.504 26.112 67.712 72.277333 96.896 95.786666 15.146667-14.08 29.098667-29.397333 41.642667-45.824 13.952-18.261333 24.149333-32.64 35.370666-52.821333l-175.146666 1.322667z m471.296-360.874667c38.229333 5.077333 67.626667 18.944 88.448 41.301333 20.736 22.229333 33.024 52.992 36.565333 92.373334 3.626667 39.722667-5.76 71.808-27.733333 96.426666-20.906667 23.381333-53.461333 40.106667-97.877334 49.706667l-2.645333 0.597333-2.816 0.554667H144.725333a8.021333 8.021333 0 0 1-7.893333-6.485333 1545.173333 1545.173333 0 0 1-0.298667-1.578667c-12.373333-62.378667-18.517333-106.666667-18.517333-132.906667 0-38.570667 5.888-81.962667 17.706667-130.261333l1.066666-4.394667a7.082667 7.082667 0 0 1 6.826667-5.333333h580.650667zM197.546667 442.88l-0.853334 2.688c-7.509333 24.064-12.544 44.330667-12.117333 70.954667 0 30.293333 5.418667 54.272 13.653333 81.664h283.050667l0.341333-2.218667 0.469334-3.2c3.541333-24.448 4.010667-47.701333 4.010666-76.544 0-30.805333-1.066667-51.541333-6.4-75.264l-282.154666 1.92z m493.397333-3.029333l-131.797333 1.024 0.512 2.474666c4.48 22.357333 6.741333 43.861333 6.741333 73.216 0 30.421333-2.432 53.76-7.552 79.189334l134.826667-0.170667 1.962666-0.213333c28.16-2.901333 49.194667-7.210667 62.421334-23.04 11.52-13.866667 17.152-32.469333 17.152-55.765334 0-24.746667-6.272-42.624-19.456-54.826666-13.653333-12.714667-34.474667-19.2-61.994667-21.674667l-2.816-0.213333z m49.877333-342.784c63.104 29.824 117.845333 78.592 164.181334 146.346666l1.536 2.304a23.466667 23.466667 0 0 1-1.066667 3.669334c-16 33.92-39.594667 59.306667-70.784 76.245333-30.805333 16.768-69.12 25.258667-114.986667 25.258667-11.178667 0-28.16 0.213333-50.944 0.469333-45.098667 0.597333-112.597333 1.408-202.965333 1.493333h-14.122667c-70.613333 0-154.453333-0.512-251.733333-1.962666a207.061333 207.061333 0 0 1-42.24-7.594667 5.973333 5.973333 0 0 1-3.626667-8.618667C242.986667 170.922667 367.146667 81.066667 526.378667 64.682667c79.829333-8.277333 151.296 2.56 214.4 32.426666z m-102.101333 28.501333c-85.205333-15.36-143.957333-4.010667-213.717333 27.221333 11.648 13.312 26.410667 33.621333 40.874666 55.04l1.578667 2.389334 2.56 3.754666 3.498667 5.376 2.346666 3.584 1.237334 1.877334c18.688 28.757333 35.157333 56.746667 41.728 70.613333h213.674666l2.474667-0.298667 2.56-0.341333c21.290667-2.986667 38.144-10.794667 55.978667-19.754667 17.408-8.576 30.122667-18.304 40.106666-29.866666-49.493333-63.018667-108.586667-104.106667-194.901333-119.594667zM367.744 186.453333c-12.458667 10.069333-34.304 29.44-56.192 50.048l-1.706667 1.621334-3.328 3.157333-3.498666 3.328-1.877334 1.792-2.048 2.005333c-17.322667 16.64-33.578667 33.109333-44.501333 45.909334l179.797333-1.536-1.109333-1.877334a3067.264 3067.264 0 0 1-12.672-21.418666l-11.776-20.053334-2.474667-4.053333-2.56-4.266667-1.152-2.005333-1.237333-2.005333c-12.458667-20.693333-24.917333-40.405333-33.706667-50.645334z" fill="#2c2c2c" p-id="5739"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
1
frontend/src/assets/icon/file/audio.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M916 983.5c0 22.4-16.5 40.5-36.8 40.5H139.3c-20.4 0-36.9-18.1-36.9-40.5v-943c0-22.4 16.5-40.5 36.9-40.5H724l192 214.2v769.3z" fill="#6367F0" ></path><path d="M712.6 0v186.5c0.8 25 21.3 44.6 45.8 43.8H916L712.6 0z" fill="#4F52C0" ></path><path d="M659.2 357.1v266.6c-0.8 27.6-23.3 49.7-50.8 49.7-28.1 0-50.8-22.9-50.8-51.2s22.8-51.2 50.8-51.2c9.8 0 19 2.8 26.8 7.7V386.1l-240.4 26.1v271.6c-0.4 27.9-23 50.4-50.8 50.4-28.1 0-50.8-22.9-50.8-51.2 0-28.3 22.8-51.2 50.8-51.2 9.8 0 19 2.8 26.8 7.7V386.1c0-14 11.1-25.2 24-25.2l237.6-28h1.9c12.8-1 24.9 10.2 24.9 24.2z" fill="#FFFFFF" ></path></svg>
|
||||
|
After Width: | Height: | Size: 665 B |
1
frontend/src/assets/icon/file/css.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M984.32 277.12L708.48 32.64a96.64 96.64 0 0 0-64-24.32h-384a97.92 97.92 0 0 0-95.36 97.28v812.8a97.92 97.92 0 0 0 97.28 97.28h656.64a97.28 97.28 0 0 0 97.28-97.28V349.44a97.28 97.28 0 0 0-32-72.32z m-308.48-188.16l229.76 204.16h-229.76z m243.2 862.72H262.4a33.28 33.28 0 0 1-33.28-33.28V105.6a33.28 33.28 0 0 1 33.28-33.28h349.44v256a32 32 0 0 0 32 32h308.48v561.28a33.28 33.28 0 0 1-33.28 30.08z" fill="#EEAC00" ></path><path d="M6.4 419.84m64 0l609.92 0q64 0 64 64l0 238.72q0 64-64 64l-609.92 0q-64 0-64-64l0-238.72q0-64 64-64Z" fill="#EEAC00" ></path><path d="M216.32 501.12a99.2 99.2 0 0 1 33.92 64h-47.36a50.56 50.56 0 0 0-19.2-34.56 60.16 60.16 0 0 0-39.04-11.52 56.96 56.96 0 0 0-46.08 21.76 99.2 99.2 0 0 0-16.64 64 97.28 97.28 0 0 0 16.64 64 53.76 53.76 0 0 0 46.08 21.76 54.4 54.4 0 0 0 59.52-53.12h46.72A115.84 115.84 0 0 1 215.68 704a110.08 110.08 0 0 1-71.68 22.4A103.68 103.68 0 0 1 64 689.92a128 128 0 0 1-31.36-87.04A128 128 0 0 1 64 517.12a104.32 104.32 0 0 1 83.84-36.48 116.48 116.48 0 0 1 68.48 20.48zM476.8 553.6h-46.08a42.88 42.88 0 0 0-16.64-25.6 64 64 0 0 0-36.48-8.32 64 64 0 0 0-32.64 6.4 21.76 21.76 0 0 0-12.16 20.48c0 7.68 6.4 14.72 18.56 20.48a344.32 344.32 0 0 0 46.72 14.08 208 208 0 0 1 58.88 21.76 54.4 54.4 0 0 1 27.52 48.64c0 48.64-34.56 73.6-103.04 73.6s-97.92-27.52-103.04-81.28h46.72a53.12 53.12 0 0 0 16.64 33.28 67.84 67.84 0 0 0 38.4 8.96c37.12 0 55.68-10.24 55.68-31.36a30.08 30.08 0 0 0-21.12-26.88 325.76 325.76 0 0 0-46.08-13.44 181.12 181.12 0 0 1-57.6-19.84 53.12 53.12 0 0 1-26.88-46.72 56.32 56.32 0 0 1 26.24-49.28 115.2 115.2 0 0 1 67.84-17.92c60.16 0 93.44 24.32 98.56 72.96zM709.76 553.6h-46.08a42.88 42.88 0 0 0-16.64-25.6 64 64 0 0 0-36.48-8.32 64 64 0 0 0-32.64 6.4 21.76 21.76 0 0 0-12.16 20.48c0 7.68 5.76 14.72 18.56 20.48a344.32 344.32 0 0 0 46.72 14.08 215.68 215.68 0 0 1 58.88 21.76 55.68 55.68 0 0 1 27.52 48.64c0 48.64-34.56 73.6-103.04 73.6S516.48 697.6 512 643.84h46.72a53.12 53.12 0 0 0 16.64 33.28 67.84 67.84 0 0 0 38.4 8.96c37.12 0 55.68-10.24 55.68-31.36a30.08 30.08 0 0 0-21.12-26.88 325.76 325.76 0 0 0-46.72-13.44 181.12 181.12 0 0 1-57.6-19.84 53.12 53.12 0 0 1-26.88-46.72 56.32 56.32 0 0 1 26.24-49.28 115.2 115.2 0 0 1 67.84-17.92c60.16 0 92.8 24.32 98.56 72.96z" fill="#FFFFFF" ></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
1
frontend/src/assets/icon/file/excel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M205.799024 64.936585H664.850732l231.773658 231.773659v638.376585c0 13.79353-11.18208 24.97561-24.97561 24.97561H205.799024c-13.79353 0-24.97561-11.18208-24.975609-24.97561V89.912195c0-13.79353 11.18208-24.97561 24.975609-24.97561z m448.70681 24.97561H205.799024v845.174634H871.64878V307.055141L654.505834 89.912195z" fill="#B7B7BD" ></path><path d="M664.850732 64.936585l-10.989269 23.477074v196.807804c0 13.79353 11.18208 24.97561 24.97561 24.97561h194.310244L896.62439 296.710244 664.850732 64.936585z m13.986341 49.306849L849.815102 285.221463H678.837073V114.243434z" fill="#B7B7BD" ></path><path d="M255.250732 571.441951m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z" fill="#B7B7BD" ></path><path d="M255.250732 707.309268m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z" fill="#B7B7BD" ></path><path d="M255.250732 639.37561m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z" fill="#B7B7BD" ></path><path d="M255.250732 774.243902m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z" fill="#B7B7BD" ></path><path d="M255.250732 842.177561m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z" fill="#B7B7BD" ></path><path d="M67.434146 193.810732m15.984391 0l286.72 0q15.98439 0 15.98439 15.98439l0 286.72q0 15.98439-15.98439 15.98439l-286.72 0q-15.98439 0-15.984391-15.98439l0-286.72q0-15.98439 15.984391-15.98439Z" fill="#00C090" ></path><path d="M242.569116 353.23904l89.224866 84.502478c4.337764 4.107988 4.523582 10.954302 0.415595 15.291067-4.107988 4.337764-10.954302 4.523582-15.291068 0.415595l-89.224866-84.502479-84.502478 89.224867c-4.107988 4.337764-10.954302 4.523582-15.291067 0.415594-4.337764-4.107988-4.523582-10.954302-0.415595-15.291067l84.502478-89.224867-89.224866-84.502478c-4.337764-4.107988-4.523582-10.954302-0.415594-15.291067 4.107988-4.337764 10.954302-4.523582 15.291067-0.415594l89.224867 84.502478 84.502478-89.224867c4.107988-4.337764 10.954302-4.523582 15.291067-0.415594 4.337764 4.107988 4.523582 10.954302 0.415594 15.291068l-84.502478 89.224866z" fill="#FFFFFF" ></path></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |