mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-04 00:55:16 +08:00
Compare commits
497 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62a17647e1 | ||
|
|
76b23d4c70 | ||
|
|
c4fbd60bbc | ||
|
|
288724efc8 | ||
|
|
65e2d35821 | ||
|
|
f5db5abb17 | ||
|
|
ddaa0ca5a9 | ||
|
|
4e3de27c51 | ||
|
|
c5ebf86ad9 | ||
|
|
6d2963a9a4 | ||
|
|
6c6c0b771f | ||
|
|
4d05a73949 | ||
|
|
f867c42a72 | ||
|
|
5941a61b80 | ||
|
|
c4bb851fbb | ||
|
|
1c4f7ac5d5 | ||
|
|
afd614e563 | ||
|
|
5fe6e3df74 | ||
|
|
d95c06da28 | ||
|
|
f5d019b260 | ||
|
|
8c766d7e07 | ||
|
|
95698afa18 | ||
|
|
ceed8d3e71 | ||
|
|
20dc1b944c | ||
|
|
f49ca8d9cf | ||
|
|
5bb0e03e98 | ||
|
|
c7cfa40539 | ||
|
|
28337c9694 | ||
|
|
7ecbd03730 | ||
|
|
c16964679d | ||
|
|
f2f2dcad98 | ||
|
|
a2916d7505 | ||
|
|
6dda5890e0 | ||
|
|
fefe2fe95f | ||
|
|
a9810516c1 | ||
|
|
3f8f79b445 | ||
|
|
58d87d9980 | ||
|
|
fce0b1cf0a | ||
|
|
ee900ffb8a | ||
|
|
8e8c56ab53 | ||
|
|
e005c9a3a2 | ||
|
|
b7094eee8a | ||
|
|
e558a97e91 | ||
|
|
a47bbb2c6a | ||
|
|
e1bf94a55e | ||
|
|
fd89a60425 | ||
|
|
6dceb028b3 | ||
|
|
90e584f5a1 | ||
|
|
25fab093fa | ||
|
|
abeac2c3cb | ||
|
|
d719a99d14 | ||
|
|
20d0b5f22b | ||
|
|
ca094c9116 | ||
|
|
67c613788f | ||
|
|
2e3fac93f0 | ||
|
|
07d5823c81 | ||
|
|
65b196e768 | ||
|
|
a17ba88068 | ||
|
|
fb3f807877 | ||
|
|
76fe865274 | ||
|
|
279968c9ef | ||
|
|
646580e6dc | ||
|
|
577ee6b974 | ||
|
|
7312db717c | ||
|
|
4668865b4f | ||
|
|
e263d172cd | ||
|
|
0225e65975 | ||
|
|
218810a7db | ||
|
|
81706debbb | ||
|
|
1c99e74c2e | ||
|
|
a024a215fb | ||
|
|
9a3442d7f5 | ||
|
|
37c8142721 | ||
|
|
2663bf4a5a | ||
|
|
8b45a8818b | ||
|
|
94d5bf5ce6 | ||
|
|
718034a7b4 | ||
|
|
703ec2df8f | ||
|
|
42b31202c6 | ||
|
|
11101e338d | ||
|
|
c7b6865458 | ||
|
|
8ed88c99b5 | ||
|
|
c8f606e809 | ||
|
|
167dfe110e | ||
|
|
748307e826 | ||
|
|
e44efb33e0 | ||
|
|
8b0a59c880 | ||
|
|
386678fc9d | ||
|
|
7d55599db9 | ||
|
|
383c8ddeca | ||
|
|
041eae9129 | ||
|
|
ab01fe6ca1 | ||
|
|
537f178fa5 | ||
|
|
c423e2cb3d | ||
|
|
efc335e78f | ||
|
|
db057aa90e | ||
|
|
58a9002c3a | ||
|
|
77da693d80 | ||
|
|
59c5222497 | ||
|
|
82e5cdb0bb | ||
|
|
0b2b89c3ae | ||
|
|
67bb1f0785 | ||
|
|
b44fcfd86c | ||
|
|
0945d42d0a | ||
|
|
80435c54a4 | ||
|
|
d872e1facc | ||
|
|
394782a989 | ||
|
|
7df05ba56f | ||
|
|
4790f1a787 | ||
|
|
bc36fcc737 | ||
|
|
1f6096ecfb | ||
|
|
a617eb2a63 | ||
|
|
7ef19f6357 | ||
|
|
c0d3acbd4d | ||
|
|
ce27837614 | ||
|
|
6bc2c63e7c | ||
|
|
86705364fe | ||
|
|
8cc08bcb8c | ||
|
|
7ee9e102a5 | ||
|
|
28267ff1c0 | ||
|
|
180d9291a3 | ||
|
|
83e6803a0a | ||
|
|
1a88ff286e | ||
|
|
ae6b3f1b67 | ||
|
|
5bbf69125c | ||
|
|
51c60134c0 | ||
|
|
b6b690edb3 | ||
|
|
6aa176a39a | ||
|
|
90c0c090ca | ||
|
|
5bee1b3733 | ||
|
|
0fad2b7e10 | ||
|
|
353560dc50 | ||
|
|
435b7cdaa9 | ||
|
|
e8f58ef322 | ||
|
|
148f8b7a6f | ||
|
|
96eaa269a1 | ||
|
|
6eefa1cd2e | ||
|
|
3c0d39c4ce | ||
|
|
3a4194d3e9 | ||
|
|
ee40374732 | ||
|
|
2f7ec93f89 | ||
|
|
ea7ae41e71 | ||
|
|
23826cd06e | ||
|
|
ff037400e9 | ||
|
|
ec757f2e9f | ||
|
|
edeae731f0 | ||
|
|
6b4a67aeb4 | ||
|
|
4f689d9881 | ||
|
|
0522e2f9c3 | ||
|
|
80fc381090 | ||
|
|
8de6b99bb3 | ||
|
|
1a39354287 | ||
|
|
7026b06f91 | ||
|
|
4350e5f294 | ||
|
|
d5be4cbbaa | ||
|
|
0e92ab2363 | ||
|
|
3fdea8b85b | ||
|
|
528ff69897 | ||
|
|
17a024de12 | ||
|
|
9b91b2507c | ||
|
|
6976aeef49 | ||
|
|
2da835c44c | ||
|
|
1ace579178 | ||
|
|
3eab01197e | ||
|
|
247bfd1966 | ||
|
|
63b1cb472a | ||
|
|
3a6cfacd0a | ||
|
|
04d152edce | ||
|
|
bb76936032 | ||
|
|
af2da50678 | ||
|
|
d55b79512c | ||
|
|
d7a7ec2c65 | ||
|
|
94f63133c3 | ||
|
|
dccccf94a0 | ||
|
|
ef1beaac38 | ||
|
|
9d403d1e41 | ||
|
|
a09fd8372b | ||
|
|
ae40f99866 | ||
|
|
a96d5c0d6b | ||
|
|
6248c554c6 | ||
|
|
b21dab1695 | ||
|
|
35e7eb9a0e | ||
|
|
cca7dba632 | ||
|
|
6339e2bed1 | ||
|
|
00af0fa628 | ||
|
|
dea794d058 | ||
|
|
5166049818 | ||
|
|
4d2a264df1 | ||
|
|
a9d1429000 | ||
|
|
716e56b8dc | ||
|
|
4b1e28098f | ||
|
|
b1a6b37ee0 | ||
|
|
46722e5a66 | ||
|
|
171c6cfca3 | ||
|
|
892fd2b712 | ||
|
|
75309f3b7a | ||
|
|
714ba48143 | ||
|
|
9493134c56 | ||
|
|
5eb4d787c7 | ||
|
|
f0dc6d118b | ||
|
|
f2b2b057e2 | ||
|
|
49a58206d8 | ||
|
|
6aba138c10 | ||
|
|
d43c3d18ca | ||
|
|
578fa91d36 | ||
|
|
7b5a84bcc3 | ||
|
|
4c79daf501 | ||
|
|
ed8d9dc823 | ||
|
|
a76ebe56f4 | ||
|
|
63a5cdecf9 | ||
|
|
2d78442dd6 | ||
|
|
ed09374932 | ||
|
|
c42b4b6e06 | ||
|
|
3b3adb741a | ||
|
|
ec047ce3b7 | ||
|
|
f403b86583 | ||
|
|
79bce85300 | ||
|
|
45d2a2307a | ||
|
|
633738d2a1 | ||
|
|
4d67358f82 | ||
|
|
7be395f1b4 | ||
|
|
cc5a4e4502 | ||
|
|
29f248ad93 | ||
|
|
7f4e1369cc | ||
|
|
d2e4acef05 | ||
|
|
d4dc430b8a | ||
|
|
1868b75328 | ||
|
|
df213daafe | ||
|
|
e898462c4a | ||
|
|
1fdcd74956 | ||
|
|
691f9c2202 | ||
|
|
09e31d5d75 | ||
|
|
a4cc6c44eb | ||
|
|
78e53f3f31 | ||
|
|
5f1977e6ca | ||
|
|
38201de234 | ||
|
|
a078c14af7 | ||
|
|
c1042ce1c6 | ||
|
|
81860e8277 | ||
|
|
ba60b534bb | ||
|
|
04c3bd8a06 | ||
|
|
0a577cb321 | ||
|
|
ed013e3039 | ||
|
|
18ba49e1c5 | ||
|
|
651efc5814 | ||
|
|
010fd3f240 | ||
|
|
22a37d1bea | ||
|
|
389a5b3f3c | ||
|
|
09f6e35793 | ||
|
|
c1867d5759 | ||
|
|
bb355eb911 | ||
|
|
f2ef5e3a36 | ||
|
|
e51624d7b6 | ||
|
|
f312519d37 | ||
|
|
4935f06200 | ||
|
|
e97d38d9e4 | ||
|
|
6e1659ebe5 | ||
|
|
827d4fe8b6 | ||
|
|
cfe5b58d36 | ||
|
|
4adcc39eda | ||
|
|
103998d8ba | ||
|
|
40697fe389 | ||
|
|
ba04211778 | ||
|
|
4b4393d67f | ||
|
|
a7b9fda6f1 | ||
|
|
c370c99b6a | ||
|
|
65e13e506a | ||
|
|
7baa427038 | ||
|
|
d85f98a869 | ||
|
|
3f98cf0b5b | ||
|
|
6fe88b9663 | ||
|
|
379bf64cb6 | ||
|
|
61860cb193 | ||
|
|
d3e85fa552 | ||
|
|
1887733b32 | ||
|
|
2f0bd6bd26 | ||
|
|
4602952a8a | ||
|
|
f96fbd745a | ||
|
|
304669dcc2 | ||
|
|
3ab1fa500a | ||
|
|
4aaa1c08c4 | ||
|
|
95c152b64a | ||
|
|
dadce88936 | ||
|
|
e33863af46 | ||
|
|
7f92b0e07a | ||
|
|
afd5f6b1dd | ||
|
|
1160774f89 | ||
|
|
0f4c0e5747 | ||
|
|
efe5062a84 | ||
|
|
e08c51725b | ||
|
|
ab30204694 | ||
|
|
118e27c2b1 | ||
|
|
7cabf4a3b5 | ||
|
|
0d2fea7fb5 | ||
|
|
99df8d06f6 | ||
|
|
d91e73d1d3 | ||
|
|
684153c5fc | ||
|
|
dcacb0d4f0 | ||
|
|
cbcb839cb9 | ||
|
|
3baaf4747d | ||
|
|
e2314b2553 | ||
|
|
0579a1498f | ||
|
|
3f48492089 | ||
|
|
4cf1c95789 | ||
|
|
9dd511163c | ||
|
|
b3684b7521 | ||
|
|
ff8f2080d2 | ||
|
|
17aa8dc8bd | ||
|
|
044d28bb93 | ||
|
|
6d58f6b70c | ||
|
|
75526bb7c9 | ||
|
|
52abfecddd | ||
|
|
5cb3ba8205 | ||
|
|
4d663a154b | ||
|
|
5146823835 | ||
|
|
4db4bc6f20 | ||
|
|
0196920236 | ||
|
|
f5a0f82efd | ||
|
|
58ea77bca1 | ||
|
|
a92bf7bee3 | ||
|
|
d0add06ca0 | ||
|
|
c79ad6b986 | ||
|
|
008e781d6b | ||
|
|
b04321e380 | ||
|
|
afd8b81712 | ||
|
|
9128194891 | ||
|
|
63766ea48d | ||
|
|
cf407a70e8 | ||
|
|
04028a4809 | ||
|
|
9e0535ef6a | ||
|
|
0f9607620c | ||
|
|
40a00be9d6 | ||
|
|
090ab096c0 | ||
|
|
1a640b446e | ||
|
|
83536d8309 | ||
|
|
384980b3d9 | ||
|
|
e4fe7b421b | ||
|
|
4fd138900b | ||
|
|
e53445e41c | ||
|
|
ee80d6eee5 | ||
|
|
22397aa221 | ||
|
|
a4e12eddb9 | ||
|
|
7f914ed180 | ||
|
|
575d432982 | ||
|
|
50b478d6c6 | ||
|
|
a292fcdb1d | ||
|
|
aedb332f3d | ||
|
|
6f6bc56d75 | ||
|
|
592bc66704 | ||
|
|
fee373f423 | ||
|
|
a316759a99 | ||
|
|
7212f1f857 | ||
|
|
4cabe9516b | ||
|
|
b51d1ad7a3 | ||
|
|
eb4973d689 | ||
|
|
05998e47c5 | ||
|
|
35c605f048 | ||
|
|
39f42f5910 | ||
|
|
09e43b807e | ||
|
|
f162031f00 | ||
|
|
e3bcfbb09c | ||
|
|
0a34a6c69b | ||
|
|
04ce04c040 | ||
|
|
0778ef7201 | ||
|
|
84e879f8f0 | ||
|
|
1c9560cff8 | ||
|
|
77bcee07c5 | ||
|
|
0cf8c1773c | ||
|
|
522461831f | ||
|
|
4f8b9a4f51 | ||
|
|
54cea42def | ||
|
|
a2da18aa5c | ||
|
|
ff43ad0179 | ||
|
|
fc07f22253 | ||
|
|
c0b5880ae1 | ||
|
|
7fcfb6249d | ||
|
|
53cd57f160 | ||
|
|
b34a515178 | ||
|
|
d37f85876f | ||
|
|
b4abb49935 | ||
|
|
667608d233 | ||
|
|
f4c1fc9210 | ||
|
|
586d53d585 | ||
|
|
9e53641435 | ||
|
|
9a7b9016e6 | ||
|
|
e6779f4921 | ||
|
|
58bb8166c9 | ||
|
|
1c6f2aa96b | ||
|
|
bb2e485713 | ||
|
|
9b7b999364 | ||
|
|
a8edac9ab1 | ||
|
|
c4705fba43 | ||
|
|
b6d352f3b8 | ||
|
|
c60c4b78f0 | ||
|
|
84108e84ac | ||
|
|
64f231f8a8 | ||
|
|
fcfc077195 | ||
|
|
43215291e0 | ||
|
|
8538ada5f0 | ||
|
|
ba7fc61732 | ||
|
|
a337900077 | ||
|
|
69a090f6e0 | ||
|
|
08df3250cf | ||
|
|
6dc8955ade | ||
|
|
5653f0d485 | ||
|
|
9f2d0898ec | ||
|
|
89c0961cbc | ||
|
|
a75ff465d0 | ||
|
|
b67c75a667 | ||
|
|
4a9153533b | ||
|
|
51c8490006 | ||
|
|
ebd774ddf7 | ||
|
|
494ccc144a | ||
|
|
78f87c6f71 | ||
|
|
a430cc9214 | ||
|
|
7829f7679b | ||
|
|
14929c9830 | ||
|
|
b13517010e | ||
|
|
197145ee0f | ||
|
|
a98f61ec33 | ||
|
|
d7618f525b | ||
|
|
2c7a3f83eb | ||
|
|
2bc65b9acf | ||
|
|
1e5502fdca | ||
|
|
726c1a4769 | ||
|
|
29893bf97a | ||
|
|
00cf8a5bfc | ||
|
|
14c52b53a4 | ||
|
|
acc975a471 | ||
|
|
ded73a9143 | ||
|
|
ce990713c0 | ||
|
|
1a91476536 | ||
|
|
dac73754e1 | ||
|
|
b8a3ae995f | ||
|
|
f8657488d8 | ||
|
|
1fcaf23a5e | ||
|
|
698ac62025 | ||
|
|
9059f1cf12 | ||
|
|
c941f12884 | ||
|
|
6195ac6be5 | ||
|
|
a4e56caebb | ||
|
|
bf07f4edd7 | ||
|
|
8f1a749b8d | ||
|
|
b83e5aa656 | ||
|
|
0f77257757 | ||
|
|
8abfdd5016 | ||
|
|
daae12610e | ||
|
|
48d9c50d3a | ||
|
|
f405eed384 | ||
|
|
80e3624b7d | ||
|
|
df71a1a702 | ||
|
|
4356de36b3 | ||
|
|
c77257730c | ||
|
|
7b5560a6a2 | ||
|
|
3f9b108a6e | ||
|
|
0abdcc4c5b | ||
|
|
6b665f50ad | ||
|
|
7f5483c46d | ||
|
|
a7a9407dbc | ||
|
|
5abc98024c | ||
|
|
8352116bea | ||
|
|
5556307969 | ||
|
|
f5266b042b | ||
|
|
a3fb359d87 | ||
|
|
b1926664c5 | ||
|
|
ee28d35a1e | ||
|
|
b31b49a3b9 | ||
|
|
8481ddd0ac | ||
|
|
3b93706c74 | ||
|
|
184dae8185 | ||
|
|
3dcf336bbc | ||
|
|
b6d91ec500 | ||
|
|
5955a03d2c | ||
|
|
9f43e11f81 | ||
|
|
b27f6d9ec7 | ||
|
|
847e66e949 | ||
|
|
dd6f33ffc4 | ||
|
|
afd570b910 | ||
|
|
44edefb37b | ||
|
|
bee15dfea6 | ||
|
|
ea9b8b0e71 | ||
|
|
68f92e9cf2 | ||
|
|
08c340a2bf | ||
|
|
e176ed19e5 | ||
|
|
94ab74892e | ||
|
|
139ff5bda7 | ||
|
|
32a60597ff | ||
|
|
3f200a3a8b | ||
|
|
135d26623e | ||
|
|
f9d635698a | ||
|
|
37af3facd2 | ||
|
|
85f2f29b81 | ||
|
|
0b23e0a940 | ||
|
|
d4dca5d778 | ||
|
|
9d1d885367 | ||
|
|
03ea326e88 | ||
|
|
8ed5a40147 |
34
.agents/PROJECT.md
Normal file
34
.agents/PROJECT.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 项目业务总览(PROJECT)
|
||||
|
||||
> 本文件记录项目的业务上下文,帮助智能体快速理解"这个项目在做什么"。
|
||||
> 框架使用规则见根目录 `AGENTS.md`。
|
||||
|
||||
## 项目定位
|
||||
|
||||
(待填写。用一两句话描述项目是做什么的。)
|
||||
|
||||
## 核心业务模块
|
||||
|
||||
(待填写。列出主要业务模块及其对应目录。)
|
||||
|
||||
## 业务约束
|
||||
|
||||
(待填写。框架不涉及但业务必须遵守的规则。)
|
||||
|
||||
## 技术选型与外部依赖
|
||||
|
||||
(待填写。项目中使用的特殊技术或外部服务。)
|
||||
|
||||
## 开发偏好
|
||||
|
||||
(待填写。团队或开发者的个性化偏好。)
|
||||
|
||||
## 增量规则记录
|
||||
|
||||
(待填写。开发过程中补充的业务规则、团队偏好与临时约束。规则应可执行、可复现、可验证。
|
||||
如需独立规则文件,参考技能:[ulthon-rules-manager](skills/ulthon-rules-manager/SKILL.md))
|
||||
|
||||
## 规则索引
|
||||
|
||||
(待填写。使用者业务规则索引,仅记录 `project-` 前缀的规则。
|
||||
框架内置规则(`ulthon-` 前缀)见 `AGENTS.md` 的「零散规则」章节。)
|
||||
35
.agents/rules/.README
Normal file
35
.agents/rules/.README
Normal file
@@ -0,0 +1,35 @@
|
||||
# 零散规则目录
|
||||
|
||||
本目录存放按模块/场景拆分的独立规则文件,避免全局规则膨胀。
|
||||
|
||||
## 命名约定
|
||||
|
||||
- `ulthon-` 前缀:框架内置规则,框架作者维护
|
||||
- `project-` 前缀:使用者业务规则,开发者维护
|
||||
|
||||
## 文件格式
|
||||
|
||||
每个规则文件包含以下章节:
|
||||
|
||||
```markdown
|
||||
# 规则名称
|
||||
|
||||
> 来源:框架内置(ulthon-)或使用者业务(project-)
|
||||
> 作用域:适用的模块/文件/场景
|
||||
> 触发条件:智能体何时应加载此规则
|
||||
|
||||
## 规则内容
|
||||
(具体的可执行规则)
|
||||
|
||||
## 相关文件 / 相关数据表 / 相关命令
|
||||
(关联的代码路径、配置路径等)
|
||||
```
|
||||
|
||||
## 索引
|
||||
|
||||
- 框架级规则索引:根目录 `AGENTS.md` 的「零散规则」章节
|
||||
- 全量规则索引:`.agents/PROJECT.md` 的「规则索引」章节
|
||||
|
||||
## 管理技能
|
||||
|
||||
新增/维护规则时,参考技能:[ulthon-rules-manager](../skills/ulthon-rules-manager/SKILL.md)
|
||||
46
.agents/rules/ulthon-controller-url.md
Normal file
46
.agents/rules/ulthon-controller-url.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# ThinkPHP 8 控制器 URL 访问规则
|
||||
|
||||
> 来源:框架内置(ulthon-)
|
||||
> 作用域:所有涉及控制器路由、URL 构造的场景
|
||||
> 触发条件:编写/调试控制器路由、URL 报错、配置路由时加载
|
||||
|
||||
## 大驼峰命名转换规则
|
||||
|
||||
ThinkPHP 8 默认开启 URL 自动转换(`url_convert`)。大驼峰命名(CamelCase)的控制器和方法在 URL 中转换为小写+下划线(snake_case)。
|
||||
|
||||
- **控制器类名**:`UserGroup` -> URL:`user_group`
|
||||
- **操作方法名**:`public function editInfo()` -> URL:`edit_info`
|
||||
|
||||
示例:控制器 `app\admin\controller\UserGroup.php` 中的 `editInfo` 方法:
|
||||
访问路径:`/admin/user_group/edit_info`
|
||||
|
||||
## 多级控制器访问规则
|
||||
|
||||
控制器存放在子目录中时,URL 使用 `.`(点号)连接目录名和控制器名。
|
||||
|
||||
- **目录结构**:`app/admin/controller/system/Config.php`
|
||||
- **访问路径**:`/admin/system.config/index`
|
||||
|
||||
注意:
|
||||
- 子目录名建议使用全小写
|
||||
- 如果子目录名也是大驼峰,同样遵循转换规则
|
||||
|
||||
## 混合使用示例
|
||||
|
||||
| 控制器文件路径 | 类名 | 方法名 | 对应 URL 路径 |
|
||||
|---|---|---|---|
|
||||
| `controller/Index.php` | `Index` | `index` | `/admin/index/index` |
|
||||
| `controller/UserGroup.php` | `UserGroup` | `add` | `/admin/user_group/add` |
|
||||
| `controller/system/Admin.php` | `Admin` | `login` | `/admin/system.admin/login` |
|
||||
| `controller/mall/GoodsCate.php` | `GoodsCate` | `getList` | `/admin/mall.goods_cate/get_list` |
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
- **404 错误**:检查是否漏掉了多级控制器的 `.` 分隔符
|
||||
- **大小写敏感**:URL 无法识别下划线时,检查 `config/route.php` 中 `url_convert` 是否为 `false`
|
||||
- **多应用影响**:多应用模式下,URL 第一段是应用名(如 `admin`),后续才是控制器和方法
|
||||
|
||||
## 开发者建议
|
||||
|
||||
- 代码中使用大驼峰命名,前端请求/模板链接中明确指向转换后的路径,或使用系统助手函数自动生成
|
||||
- 复杂 URL 建议在 `route/*.php` 中手动定义路由规则
|
||||
76
.agents/rules/ulthon-database-design.md
Normal file
76
.agents/rules/ulthon-database-design.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 表结构设计规范
|
||||
|
||||
## 特殊字段
|
||||
|
||||
| 字段名 | 用途 | 说明 |
|
||||
|--------|------|------|
|
||||
| `status` | 默认开关字段 | - |
|
||||
| `create_time` | 创建时间 | 尽量 NOT NULL,默认值 0(TP 会自动填充) |
|
||||
| `update_time` | 更新时间 | 尽量 NOT NULL,默认值 0(TP 会自动填充) |
|
||||
| `delete_time` | 删除时间 | 尽量 NOT NULL,默认值 0;CURD 默认开启软删除,删除标志为 0 |
|
||||
|
||||
## 字段后缀约定
|
||||
|
||||
以特殊字符结尾的字段会自动识别为对应类型:
|
||||
|
||||
| 后缀 | 类型 |
|
||||
|------|------|
|
||||
| `image`、`logo`、`photo`、`icon` | 单图片 |
|
||||
| `images`、`photos`、`icons` | 多图片 |
|
||||
| `file` | 单文件 |
|
||||
| `files` | 多文件 |
|
||||
|
||||
## 注释语法
|
||||
|
||||
字段注释支持通过特殊格式定义表单类型和数据集:
|
||||
|
||||
```
|
||||
名称 {类型} (数据集)
|
||||
```
|
||||
|
||||
- **类型**:用 `{}` 包起来,例如 `{radio}`
|
||||
- **数据集**:用 `()` 包起来,例如 `(1:男, 2:女, 0:未知)`
|
||||
- 数据集索引可以用数字或英文单词,不要使用其他字符和空格
|
||||
|
||||
示例:`性别 {radio} (1:男, 2:女, 0:未知)`
|
||||
|
||||
## 类型大全
|
||||
|
||||
| 类型 | 说明 | 是否需要数据集 | 注释案例 |
|
||||
|------|------|----------------|----------|
|
||||
| text | 普通文本框 | 否 | `店铺名称 {text}`(一般不需要写 text) |
|
||||
| image | 单图片 | 否 | `店铺logo {image}` |
|
||||
| images | 多图片 | 否 | `店铺环境 {images}`,分隔符默认为竖线 |
|
||||
| file | 单文件 | 否 | `演示资料 {file}` |
|
||||
| files | 多文件 | 否 | `演示资料 {files}`,默认分隔符为竖线 |
|
||||
| date | 时间组件 | 是 | `生日 {date} (datetime)` |
|
||||
| editor | 富文本 | 否 | `店铺详情 {editor}` |
|
||||
| textarea | 多行文本 | 否 | `店铺简介 {textarea}` |
|
||||
| select | 下拉选择 | 是 | `版本 {select} (trial:免费版,office:正式版)` |
|
||||
| switch | 开关组件 | 是 | `状态 {switch} (0:关闭,1:开启)` |
|
||||
| checkbox | 多选框 | 是 | `功能权限 {checkbox} (mall:商城,blog:博客)` |
|
||||
| radio | 单选框 | 是 | `状态 {radio} (0:未审核,1:审核中)` |
|
||||
| relation | 关联表 | 是(格式见下) | `标签 {relation} (table:tag,relationBindSelect:title)` |
|
||||
| table | 表格选择器 | 是(格式见下) | `商品标签 {table} (table:mall_tag,type:checkbox,valueField:id,fieldName:title)` |
|
||||
| city | 城市选择器 | 是 | `仓库 {city} (level:city)` |
|
||||
|
||||
## 关联表注释参数
|
||||
|
||||
| 参数 | 说明 | 备注 |
|
||||
|------|------|------|
|
||||
| `table` | 关联表名 | 必填 |
|
||||
| `primaryKey` | 关联表主键 | 非必填 |
|
||||
| `modelFilename` | 模型文件 | 非必填,不建议指定,可自动生成 |
|
||||
| `onlyFileds` | 列表页显示字段 | 可指定,用竖线分割 |
|
||||
| `relationBindSelect` | 表单下拉关联字段 | 必填 |
|
||||
|
||||
完整写法示例:
|
||||
|
||||
```
|
||||
标签 {relation} (table:tag,relationBindSelect:title,primaryKey:id,onlyFileds:title|titme_image|username|phone)
|
||||
```
|
||||
|
||||
## 其他细节
|
||||
|
||||
- 设计时尽量设置默认值。例如 `status` 默认值为 1,添加数据时表单会自动将 radio 选中"1:启用"
|
||||
- 分隔符默认为竖线 `|`
|
||||
23
.agents/rules/ulthon-deploy-environment.md
Normal file
23
.agents/rules/ulthon-deploy-environment.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 部署环境与命令执行
|
||||
|
||||
> 来源:框架内置(ulthon-)
|
||||
> 作用域:部署配置、命令执行环境判断
|
||||
> 触发条件:执行 php think 命令、配置部署模式、切换运行环境时加载
|
||||
|
||||
## 部署栈模式
|
||||
|
||||
- `source/stack/` 为模式文件统一目录(含 `default/` 与各模式目录)
|
||||
- `default/` 必须与代码库默认行为一致
|
||||
- 默认行为相关文件变更时需同步更新 `source/stack/default/` 对应文件
|
||||
|
||||
## 运行模式判断
|
||||
|
||||
执行 `php think` 命令前,必须先判断当前运行模式。
|
||||
|
||||
**判断方式**:检查仓库根目录是否存在 `docker-compose.yaml`
|
||||
|
||||
- **存在**:Docker 模式。宿主机可能没有 PHP,不能依赖 `php think` 来检测。所有 `php think` 命令前缀改为 `docker compose exec ulthon_admin`
|
||||
- 示例:`docker compose exec ulthon_admin php think tools:http:call`
|
||||
- **不存在**:宿主机模式。直接执行 `php think`
|
||||
|
||||
也可读取 `source/stack/stack.json` 了解所有可用模式及其说明。
|
||||
13
.agents/rules/ulthon-naming-convention.md
Normal file
13
.agents/rules/ulthon-naming-convention.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 命名规范
|
||||
|
||||
## 目录命名
|
||||
|
||||
- 一个单词的目录用小写,例如 `service/`
|
||||
- 多个单词的目录用大驼峰,例如 `AdminService/`、`UserService/`
|
||||
|
||||
## PHP 文件命名
|
||||
|
||||
- `app/common` 目录下已有单独的规范说明(见 `ulthon-source-directory.md`)
|
||||
- 其他各应用目录下:
|
||||
- **service 模块**需要写后缀,例如 `AdminService`,而不是 `admin`
|
||||
- **除了 service 模块**,其他文件不需要写后缀,不论是 controller、model、traits、config、middleware 等
|
||||
25
.agents/rules/ulthon-source-directory.md
Normal file
25
.agents/rules/ulthon-source-directory.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# source/ 目录约定
|
||||
|
||||
> 来源:框架内置(ulthon-)
|
||||
> 作用域:所有涉及 source/ 目录的操作
|
||||
> 触发条件:新增子项目、存放多端代码、管理部署配套文件时加载
|
||||
|
||||
## 定位
|
||||
|
||||
`source/` 是主工程之外的配套内容统一目录,不影响当前主工程运行与发布。包括:
|
||||
|
||||
- 多端代码(客户端、大屏端等)
|
||||
- 子项目工程(可为 PHP 或其他技术栈)
|
||||
- 项目资料、附件
|
||||
- 部署配套文件
|
||||
|
||||
## 目录约定
|
||||
|
||||
- 各子目录(客户端、大屏端、各类子项目等)独立管理
|
||||
- 若子目录下存在 `AGENTS.md`,则该子工程规则以该文件为准
|
||||
- 目录约定与安全要求见 `source/README.md`
|
||||
|
||||
## 安全要求
|
||||
|
||||
- 禁止提交构建产物
|
||||
- 禁止提交依赖目录(node_modules、vendor 等)
|
||||
33
.agents/rules/ulthon-timer-multi-node.md
Normal file
33
.agents/rules/ulthon-timer-multi-node.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 定时任务多节点协调
|
||||
|
||||
> 来源:框架内置(ulthon-)
|
||||
> 作用域:定时任务相关模块(TimerConfig、TimerLog、Host)
|
||||
> 触发条件:涉及定时任务开发、多节点部署、主节点选举等场景时加载
|
||||
|
||||
## 规则内容
|
||||
|
||||
- 多节点定时任务协调以数据库为主协调中心
|
||||
- run_type 调度模式:auto / main / all / manual
|
||||
- 支持主节点自动选举与手动切换
|
||||
- 执行日志必须记录,支持查看与清理
|
||||
- 定时任务配置管理通过 UI 管理
|
||||
|
||||
## 相关数据表
|
||||
|
||||
- ul_system_timer_config
|
||||
- ul_system_timer_log
|
||||
- ul_system_host(含 is_master 字段)
|
||||
|
||||
## 相关命令
|
||||
|
||||
- `php think admin:timer:log:clean [--days=30]` — 清理过期执行日志
|
||||
|
||||
## 相关管理页面
|
||||
|
||||
- 定时器配置管理:/admin/system.timer_config/index
|
||||
- 定时器执行日志:/admin/system.timer_log/index
|
||||
- 主机列表增强(主节点标识、切换主节点)
|
||||
|
||||
## 相关技能
|
||||
|
||||
- [ulthon-timer](../skills/ulthon-timer/SKILL.md)
|
||||
86
.agents/skills/ulthon-admin-menu-cli/SKILL.md
Normal file
86
.agents/skills/ulthon-admin-menu-cli/SKILL.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
name: "ulthon-admin-menu-cli"
|
||||
description: "解释并指导后台菜单(system_menu)与 admin:menu:* 命令的使用方式。需要导出菜单、通过命令行创建/更新/删除菜单、或排查菜单字段映射(pid/href/auth_node)时调用。"
|
||||
---
|
||||
|
||||
# 菜单管理(admin:menu:* CLI)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 需要导出当前系统菜单数据,用于备份/迁移/对比。
|
||||
- 需要在不进入后台页面的情况下,通过命令行创建/更新/删除菜单。
|
||||
- 需要排查菜单字段映射:`pid`/`href`/`auth_node` 与命令参数 `parent-id`/`path`/`node` 的对应关系。
|
||||
- 需要脚本化输出(JSON)对接自动化运维/测试(仅导出命令支持)。
|
||||
|
||||
## 命令清单
|
||||
|
||||
### 1) 导出菜单
|
||||
|
||||
```bash
|
||||
php think admin:menu:export
|
||||
```
|
||||
|
||||
常用参数:
|
||||
- `--format=json`:JSON 输出(含 count/exported_at)
|
||||
- `--output=<文件路径>`:把导出的菜单数组写入文件(JSON)
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
php think admin:menu:export --format=json
|
||||
php think admin:menu:export --output=runtime/agent/menu_export.json
|
||||
php think admin:menu:export --output=runtime/agent/menu_export.json --format=json
|
||||
```
|
||||
|
||||
### 2) 创建菜单(会写入数据库)
|
||||
|
||||
```bash
|
||||
php think admin:menu:create --title="测试菜单" --path="demo.index/index" --icon="fa fa-list" --parent-id=0 --sort=100
|
||||
```
|
||||
|
||||
### 3) 更新菜单(会写入数据库)
|
||||
|
||||
```bash
|
||||
php think admin:menu:update --id=123 --title="新标题" --path="demo.index/index" --icon="fa fa-list" --parent-id=0 --sort=100
|
||||
```
|
||||
|
||||
说明:
|
||||
- `--id` 必填;其余参数不传则不更新该字段。
|
||||
- `--path` 更新的是数据库字段 `href`。
|
||||
- `--parent-id` 更新的是数据库字段 `pid`。
|
||||
|
||||
### 4) 删除菜单(会写入数据库,软删除)
|
||||
|
||||
```bash
|
||||
php think admin:menu:delete --id=123
|
||||
```
|
||||
|
||||
说明:
|
||||
- 若存在子菜单会拒绝删除(需先删除子菜单)。
|
||||
|
||||
## 字段映射速查
|
||||
|
||||
- `--parent-id` → `system_menu.pid`
|
||||
- `--path` → `system_menu.href`
|
||||
- `--node` → `system_menu.auth_node`
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### 工作流 A:备份菜单到文件
|
||||
|
||||
```bash
|
||||
php think admin:menu:export --output=runtime/agent/menu_export.json
|
||||
```
|
||||
|
||||
### 工作流 B:创建 → 更新 → 删除(用于验证命令链路)
|
||||
|
||||
```bash
|
||||
php think admin:menu:create --title="CLI测试菜单" --path="test.cli/index" --parent-id=0
|
||||
php think admin:menu:update --id=<上一步返回的id> --title="CLI测试菜单_改"
|
||||
php think admin:menu:delete --id=<上一步返回的id>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `create/update/delete` 会实际修改数据库,建议在测试环境或确认无风险后执行。
|
||||
- 导出默认过滤 `delete_time=0`(只导出未删除菜单)。
|
||||
28
.agents/skills/ulthon-auth-session-token/SKILL.md
Normal file
28
.agents/skills/ulthon-auth-session-token/SKILL.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: "ulthon-auth-session-token"
|
||||
description: "解释并指导 Session+Token 登录认证的使用方式。需要实现接口鉴权或联调移动端请求时调用。"
|
||||
---
|
||||
|
||||
# 登录认证(Session + Token)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 编写/联调需要登录态的接口。
|
||||
- 需要在非浏览器环境(移动端、小程序、跨域脚本)调用后台接口。
|
||||
|
||||
## 机制概览
|
||||
|
||||
- Session:主要用于浏览器环境,依赖 Cookie。
|
||||
- Token:用于接口与无 Cookie 场景,通过 Header 传递完成认证。
|
||||
|
||||
## 使用方式
|
||||
|
||||
1. 登录成功后,接口会返回 `token` 数据。
|
||||
2. 后续请求在 Header 携带:
|
||||
|
||||
```text
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
3. 系统会自动识别并完成身份认证。
|
||||
|
||||
121
.agents/skills/ulthon-base-app-architecture/SKILL.md
Normal file
121
.agents/skills/ulthon-base-app-architecture/SKILL.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: "ulthon-base-app-architecture"
|
||||
description: "详细说明了 Base/App 双层架构的设计理念、三层结构、身份职责、目录映射、扩展模式及调用红线,帮助开发者理解框架结构并避免误操作。"
|
||||
---
|
||||
|
||||
# Base/App 双层架构
|
||||
|
||||
本架构旨在解决**框架内核升级**与**业务代码定制**之间的矛盾。请根据您的身份阅读对应部分。
|
||||
|
||||
## 角色定位
|
||||
|
||||
| 你的身份 | 你的主要工作 | 请关注章节 |
|
||||
| :--- | :--- | :--- |
|
||||
| **框架使用者** | 开发具体业务功能、使用框架内置能力 | 一、我是框架使用者(做业务) |
|
||||
| **框架作者** | 维护框架内核、修复 Bug、新增通用组件 | 二、我是框架作者(修内核) |
|
||||
|
||||
---
|
||||
|
||||
## 一、我是框架使用者(做业务)
|
||||
|
||||
### 1. 你的地盘与禁区
|
||||
|
||||
- **自由开发区**:除了 `extend/base/` 之外的所有目录。虽然业务代码通常推荐放在 `app/` 下,但你完全可以在项目中自由发挥。
|
||||
- **绝对禁区**:`extend/base/` 目录。**严禁修改**这里的文件,因为 `php think admin:update` 会覆盖它。
|
||||
|
||||
### 2. 开发场景指南
|
||||
|
||||
#### 场景 A:新增全新的业务功能
|
||||
|
||||
直接在 `app/` 下创建控制器、模型或服务即可。不需要关心 Base 层,也不需要继承任何 Base 类(除非你需要利用框架基类的功能)。
|
||||
|
||||
- 公共代码放在 `app/common/`(工具类、基础类等)
|
||||
- 命令类放在 `app/common/command/`(不放在 `app/admin/command/`)
|
||||
- 新写业务能力:不需要、也不应该在 `extend/base/` 新建任何 `*Base.php`,这不是业务开发的默认模式
|
||||
|
||||
#### 场景 B:修改/扩展框架内置功能
|
||||
|
||||
如果你对框架默认的某个功能(如登录逻辑、CURD流程)不满意,请按以下步骤操作:
|
||||
|
||||
1. **定位**:找到该功能对应的 `app/` 入口类(例如 `app/admin/model/SystemAdmin.php`)。
|
||||
2. **重写**:在该类中重写你需要修改的方法。
|
||||
- 保持方法签名(参数、返回值)一致。
|
||||
- 可以使用 `parent::method()` 复用父类逻辑,或复制父类代码后自行实现。
|
||||
3. **生效**:系统会自动调用你的类,而不是底层的 Base 类。
|
||||
|
||||
#### 常见误区
|
||||
|
||||
- 误区:新增业务功能时,也要先在 `extend/base/` 建一个 `*Base` 再在 `app/` 继承
|
||||
结论:不需要;这是框架内核维护模式,不是业务开发默认模式
|
||||
- 误区:看到框架存在 Base/App 双层机制,就认为所有类都必须走"入口类"
|
||||
结论:入口类主要用于"覆盖框架默认实现",纯业务类可以直接使用,不需要额外包装
|
||||
|
||||
### 3. 更新机制与保障
|
||||
|
||||
执行 `php think admin:update` 时,系统会检查框架所有文件的更新:
|
||||
|
||||
- **Base 层(extend/base/)**:默认为**覆盖更新**(Always Yes),因为这里是内核。
|
||||
- **App 层(app/)及其他**:默认为**保护模式**(Default No)。命令会提示即将变动的文件列表。
|
||||
- **推荐操作**:一般选择**跳过**,随后根据实际业务代码与上游更新进行对比,手动合并差异。
|
||||
- **替代操作**:也可选择**覆盖**,更新后通过 Git 查看差异,并恢复不应变动的业务代码。
|
||||
|
||||
---
|
||||
|
||||
## 二、我是框架作者(修内核)
|
||||
|
||||
### 1. 你的地盘与职责
|
||||
|
||||
- **你的地盘**:`extend/base/` 目录。通用逻辑、基类代码写在这里。
|
||||
- **你的义务**:每在 Base 层新增一个类(如 `UserBase`),**必须**在 `app/` 层提供对应的入口类(如 `User`),并让入口类继承 Base 类。
|
||||
|
||||
### 2. 开发关键原则(依赖倒置)
|
||||
|
||||
为了保证使用者的重写能生效,你必须遵守以下**铁律**:
|
||||
|
||||
> **严禁直接调用 Base 类**
|
||||
> 无论在何处,禁止写 `new UserBase()` 或 `UserBase::find()`。
|
||||
|
||||
> **必须调用 App 入口类**
|
||||
> 必须写 `new \app\...\User()`。
|
||||
|
||||
**为什么?**
|
||||
如果代码直接调用了 `UserBase`,那么使用者在 `app/` 下重写的 `User` 类就变成了"摆设",无法拦截逻辑。只有调用 `app/` 下的子类,多态机制才能生效,使用者才有机会改变行为。
|
||||
|
||||
### 3. 文件组织规范
|
||||
|
||||
- **类文件**:以 `*Base.php` 结尾,放在 `extend/base/`。路径和名称与 `app/` 层一一对应。
|
||||
- **辅助函数**:放在 `extend/base/helper.php`,通过 `app/common.php` 引入。
|
||||
- **初始化数据**:放在 `extend/base/adminInitData/`(带 @internal-framework 注解标记)。
|
||||
- **版本更新代码**:放在 `extend/base/adminUpdateCodeData/`(带 @internal-framework 注解标记)。
|
||||
- 静态文件/模板/配置支持分层加载:优先加载 `app/`,不存在时再回落到框架默认实现(例如 `app_file_path`)。
|
||||
|
||||
### 4. 核心层维护原则
|
||||
|
||||
- 稳定性优先:保证向下兼容
|
||||
- 通用性优先:不引入具体业务逻辑
|
||||
|
||||
---
|
||||
|
||||
## 三、架构参考资料
|
||||
|
||||
### 1. 三层结构图解
|
||||
|
||||
```
|
||||
ThinkPHP 框架层(底层基础设施)
|
||||
|
|
||||
| 继承/扩展
|
||||
ulthon_admin 内核层(extend/base/,框架作者维护)
|
||||
|
|
||||
| 继承/覆盖(依赖倒置:内核只调用 App 层入口)
|
||||
App 应用层(app/,框架使用者维护)
|
||||
```
|
||||
|
||||
### 2. 常见目录映射
|
||||
|
||||
| Base 层(内核实现) | App 层(调用入口) |
|
||||
| :--- | :--- |
|
||||
| `extend/base/admin/controller/system/AdminBase.php` | `app/admin/controller/system/Admin.php` |
|
||||
| `extend/base/admin/model/SystemAdminBase.php` | `app/admin/model/SystemAdmin.php` |
|
||||
| `extend/base/common/service/SmsBase.php` | `app/common/service/Sms.php` |
|
||||
| `extend/base/common/command/CurdBase.php` | `app/common/command/Curd.php` |
|
||||
| `extend/base/common/command/admin/role/AdminRoleCreateBase.php` | `app/common/command/admin/role/AdminRoleCreate.php` |
|
||||
35
.agents/skills/ulthon-db-tools-debug/SKILL.md
Normal file
35
.agents/skills/ulthon-db-tools-debug/SKILL.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: "ulthon-db-tools-debug"
|
||||
description: "封装 tools:db 命令的使用方法。需要快速查询/执行 SQL 或查看表信息进行调试时调用。"
|
||||
---
|
||||
|
||||
# 内置数据库调试命令(tools:db)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 需要快速查看数据、验证 SQL、检查表结构(字段/索引/行数)用于排错。
|
||||
- 需要在不写临时代码的情况下做一次性查询或数据检查。
|
||||
|
||||
## 必须注意
|
||||
|
||||
- 这些命令用于“调试数据”,不要用来“设计表结构”。
|
||||
- 如果为了排错临时改了表结构或数据,需要在任务结束前确保影响可控并记录变更点。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
php think tools:db:query
|
||||
php think tools:db:execute
|
||||
php think tools:db:table
|
||||
php think tools:db:count
|
||||
php think tools:db:info
|
||||
php think tools:db:desc
|
||||
```
|
||||
|
||||
通过 `--help` 查看每个命令参数说明。
|
||||
|
||||
## 使用建议
|
||||
|
||||
- 优先用 `query/desc/info/table/count` 做只读检查。
|
||||
- 需要变更数据时再用 `execute`,并尽量将变更限制在最小范围。
|
||||
|
||||
48
.agents/skills/ulthon-page-api-dual-mode/SKILL.md
Normal file
48
.agents/skills/ulthon-page-api-dual-mode/SKILL.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: "ulthon-page-api-dual-mode"
|
||||
description: "指导控制器实现“页面/接口同体”。需要同一路由同时返回 HTML 与 JSON 时调用。"
|
||||
---
|
||||
|
||||
# 页面 / 接口同体(Controller Dual Mode)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 希望同一个控制器方法既能渲染页面(HTML),也能作为接口返回 JSON。
|
||||
- 需要让现有页面接口在移动端/脚本调用时返回结构化数据。
|
||||
|
||||
## 触发与认证
|
||||
|
||||
- 触发:请求头 `Accept` 包含 `application/json`(框架使用 `request()->isJson()` 判断)。
|
||||
- 认证:Header 传 `Authorization: Bearer <tokenContent>`。
|
||||
|
||||
## 实现要点
|
||||
|
||||
- 控制器方法必须调用 `$this->fetch()`,不要使用 `View::fetch()`。
|
||||
- 无论使用 `$this->assign()` 还是 `View::assign()`,其数据都会被转换为 JSON 返回。
|
||||
- 若不希望某个 assign 字段出现在 JSON 中,可使用:
|
||||
|
||||
```php
|
||||
$this->assign('name', 'value', -1);
|
||||
```
|
||||
|
||||
## 特殊行为
|
||||
|
||||
- 这里存在两种“JSON 语义”,不要混淆:
|
||||
- **接口模式(API)**:只需要 `Accept: application/json`。典型用法是 `index` 的表格分页数据、以及所有 `POST` 提交的 success/error JSON 返回。
|
||||
- **页面数据模式(Page Data)**:用于“拿页面 assign 的数据”(例如表单的下拉选项、默认值等),需要在 `Accept: application/json` 的基础上追加 `get_page_data=1`。
|
||||
- `get_page_data=1` 会强制让 `request()->isAjax()` 返回 false,从而避免 `index` 这类方法走“Ajax 分页数据分支”,转而执行 `$this->fetch()`;随后 `$this->fetch()` 会把 `View::fetchData()`(即 assign 的变量)打包成 `json_message()` 返回。
|
||||
- 当 URL 带 `get_page_data=1` 但请求头不含 `Accept: application/json` 时,框架会直接返回 JSON 错误提示,避免“看起来参数写了但返回了 HTML”的误解。
|
||||
- 仅在控制器模式(Controller Mode)生效;路由模式(Route Mode)不生效。
|
||||
|
||||
## 常见例子
|
||||
|
||||
- 获取列表分页数据(API):只加 `Accept: application/json`,不要带 `get_page_data`
|
||||
- `GET /admin/system.admin/index`
|
||||
- 获取页面 assign 数据(Page Data):`Accept: application/json` + `get_page_data=1`
|
||||
- `GET /admin/system.menu/add?get_page_data=1`
|
||||
- `GET /admin/system.admin/add?get_page_data=1`
|
||||
|
||||
## 命令行联调建议
|
||||
|
||||
- `php think tools:http:call --app=admin --controller=system.admin --action=index`:默认按 API 语义调用(不再自动追加 `get_page_data=1`)
|
||||
- `php think tools:http:call --app=admin --controller=system.admin --action=add --page-data`:获取该页面的 assign 数据(等价于追加 `get_page_data=1`)
|
||||
106
.agents/skills/ulthon-permission-cli/SKILL.md
Normal file
106
.agents/skills/ulthon-permission-cli/SKILL.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: "ulthon-permission-cli"
|
||||
description: "解释并指导 RBAC 权限体系与相关 CLI 命令(admin:role:* / admin:user:role:* / admin:permission:*)。用于查看节点、管理角色权限、给用户分配角色、排查权限问题。"
|
||||
---
|
||||
|
||||
# 权限与角色管理(RBAC CLI)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 需要查看系统当前有哪些权限节点。
|
||||
- 需要创建/查看/删除角色,或查看角色详情。
|
||||
- 需要为角色分配/撤回权限节点。
|
||||
- 需要给指定用户分配/撤回角色。
|
||||
- 需要排查“有菜单但无权限 / 有权限但仍被拦截 / 节点名称不一致”等权限问题。
|
||||
|
||||
## 概念速览
|
||||
|
||||
- 权限节点:基于控制器/方法注解生成的权限标识(用于授权与鉴权)。
|
||||
- 角色:权限节点的集合(`SystemAuth`)。
|
||||
- 用户:后台用户(`SystemAdmin`),通过 `auth_ids`(逗号分隔)绑定角色 ID 列表。
|
||||
- 角色-节点:`SystemAuthNode` 记录 `auth_id + node` 的多对多关系。
|
||||
- 鉴权入口:后台请求进入后会根据“当前节点”进行权限检查(未授权则拦截)。
|
||||
- 输出说明:权限相关命令默认以文本输出为主;如命令涉及交互确认,可使用 `--force-force` 或 `-ff` 跳过确认。
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 1) 查看权限节点
|
||||
|
||||
```bash
|
||||
php think admin:permission:nodes
|
||||
```
|
||||
|
||||
常用参数(如需):
|
||||
- `--module=<模块>`:按模块过滤
|
||||
- `--level=1|2`:按级别过滤(1=控制器,2=方法)
|
||||
|
||||
### 2) 角色管理
|
||||
|
||||
```bash
|
||||
php think admin:role:list
|
||||
php think admin:role:info --role-id=12
|
||||
php think admin:role:create --title="测试角色" --remark="测试用"
|
||||
php think admin:role:delete --role-id=12
|
||||
```
|
||||
|
||||
要点:
|
||||
- 删除角色会检查该角色是否仍被用户分配;若仍有用户绑定,则删除会失败。
|
||||
|
||||
### 3) 角色权限管理
|
||||
|
||||
```bash
|
||||
php think admin:role:permission:list --role-id=12
|
||||
php think admin:role:permission:assign --role-id=12 --nodes="system.admin/index,system.admin/add"
|
||||
php think admin:role:permission:revoke --role-id=12 --nodes="system.admin/add"
|
||||
```
|
||||
|
||||
要点:
|
||||
- `--nodes` 支持逗号分隔批量操作。
|
||||
- 建议先用 `admin:permission:nodes` 确认节点名称完全一致后再分配到角色。
|
||||
|
||||
### 4) 用户角色管理
|
||||
|
||||
```bash
|
||||
php think admin:user:role:list --user-id=1
|
||||
php think admin:user:role:assign --user-id=1 --role-ids="12,13"
|
||||
php think admin:user:role:revoke --user-id=1 --role-ids="13"
|
||||
```
|
||||
|
||||
### 5) 透视查询用户当前权限
|
||||
|
||||
```bash
|
||||
php think admin:permission:user --user-id=1
|
||||
```
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### 工作流 A:新增/调整注解后,核对节点并授权
|
||||
|
||||
1. 在控制器/方法上新增或调整权限注解(节点名称、标题、模块等)。
|
||||
2. 执行 `admin:permission:nodes`,确认节点列表中已出现新节点且名称符合预期。
|
||||
3. 确定要使用的角色(可通过 `admin:role:list` / `admin:role:info` 查看;没有合适角色就用 `admin:role:create` 新建)。
|
||||
4. 执行 `admin:role:permission:assign` 将新节点分配给角色。
|
||||
5. 执行 `admin:user:role:assign` 将角色分配给用户。
|
||||
6. 执行 `admin:permission:user` 透视确认该用户已拥有节点。
|
||||
|
||||
### 工作流 B:排查“明明有权限但仍被拦截”
|
||||
|
||||
1. 执行 `admin:permission:user --user-id=<id>`,确认用户是否真的拥有当前节点。
|
||||
2. 执行 `admin:permission:nodes`,确认“当前被拦截节点名”是否存在、是否有大小写/分隔符差异。
|
||||
3. 如果用户通过角色获得权限,执行 `admin:role:permission:list --role-id=<roleId>` 核对角色是否包含该节点。
|
||||
4. 检查后台配置中是否存在免鉴权规则(例如 no_login/no_auth 的控制器/节点白名单)。
|
||||
|
||||
## 变更说明(重要)
|
||||
|
||||
- 旧命令 `admin:permission:assign` / `admin:permission:revoke` 已移除。
|
||||
- “按用户直接增删节点”的需求,应通过“角色”承接:
|
||||
- 只影响单个用户:创建专用角色 → 给该角色分配节点 → 将角色分配给该用户。
|
||||
- 影响一批用户:维护共享角色 → 给该角色增删节点 → 所有拥有该角色的用户一起生效。
|
||||
|
||||
## 常见坑位
|
||||
|
||||
- 节点名不一致:注解里写的节点名与实际鉴权使用的当前节点不一致,导致分配了权限但无法命中。
|
||||
- 只改了菜单没改权限:菜单能看到不代表有权限访问,权限检查以节点为准。
|
||||
- 缓存/环境差异:在不同环境中节点生成来源不一致时,先以 `admin:permission:nodes` 的输出为准核对。
|
||||
- 修改共享角色影响面过大:撤回/新增角色节点会影响所有拥有该角色的用户。
|
||||
- 删除角色失败:仍有用户绑定该角色,先用 `admin:user:role:revoke` 撤回后再删除。
|
||||
142
.agents/skills/ulthon-rules-manager/SKILL.md
Normal file
142
.agents/skills/ulthon-rules-manager/SKILL.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
name: "ulthon-rules-manager"
|
||||
description: "零散规则管理技能。指导智能体在 `.agents/rules/` 目录下新增、维护模块级/场景级规则文件,并同步更新 AGENTS.md 与 PROJECT.md 的规则索引。触发词包括"记录规则"、"新增规则"、"写条规则"、"这个模块有约束"、"记录约束"等。"
|
||||
---
|
||||
|
||||
# 零散规则管理(Rules Manager)
|
||||
|
||||
## 核心概念
|
||||
|
||||
**零散规则(Rules)** 是模块级/场景级的专属知识,不适合放在全局 `AGENTS.md` 中展开。存放在 `.agents/rules/` 目录下,每条规则一个独立文件,通过索引被智能体发现和引用。
|
||||
|
||||
Rules 可包含以下类型的内容:
|
||||
- **约束**:不能怎么做、必须怎么做(如命名约定、分层铁律)
|
||||
- **约定**:惯例、默认行为(如目录结构、文件配对)
|
||||
- **设计决策**:某个功能的设计方案与背景(如多节点协调方案、认证架构选型)
|
||||
|
||||
## Rules 与 Skills 的边界
|
||||
|
||||
| 维度 | Rule(规则) | Skill(技能) |
|
||||
|------|-------------|---------------|
|
||||
| 核心问题 | "是什么""不能做什么""为什么这样设计" | "怎么做""一步步如何完成" |
|
||||
| 知识形态 | 静态声明(A 对应 B、禁止 C) | 动态流程(第 1 步...第 2 步...) |
|
||||
| 触发时机 | 涉及某模块时需先了解其规则 | 需要执行某操作时按步骤调用 |
|
||||
| 典型例子 | URL 映射规则、目录约定、多节点设计 | CURD 生成流程、定时任务配置、菜单创建 |
|
||||
|
||||
**判断方法**:如果内容主要是"告知性"的(让智能体知道某个事实/约束/设计),放 Rule。如果内容主要是"操作性"的(让智能体按步骤完成某件事),放 Skill。
|
||||
|
||||
**灰色地带处理**:部分内容混合了规则和操作(如 Base/App 架构文档既有铁律又有场景指南),此时保留为 Skill,因为其核心价值在"指导如何操作"。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.agents/rules/
|
||||
├── .README # 目录说明与格式规范
|
||||
├── ulthon-timer-multi-node.md # 框架内置规则示例
|
||||
├── project-xxx.md # 使用者业务规则示例
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 命名约定
|
||||
|
||||
| 前缀 | 含义 | 维护者 | 约束 |
|
||||
|------|------|--------|------|
|
||||
| `ulthon-` | 框架内置规则 | 框架作者 | 随框架更新分发 |
|
||||
| `project-` | 使用者业务规则 | 开发者 | 不被框架更新影响 |
|
||||
|
||||
文件名使用小写英文 + 短横线,语义清晰,例如:
|
||||
- `ulthon-timer-multi-node.md`
|
||||
- `ulthon-upload-storage.md`
|
||||
- `project-order-stock-lock.md`
|
||||
- `project-wechat-auth.md`
|
||||
|
||||
## 规则文件格式模板
|
||||
|
||||
```markdown
|
||||
# 规则名称
|
||||
|
||||
> 来源:框架内置(ulthon-)或 使用者业务(project-)
|
||||
> 作用域:适用的模块/文件/场景
|
||||
> 触发条件:智能体何时应加载此规则
|
||||
|
||||
## 规则内容
|
||||
|
||||
(具体的可执行规则,每条应可验证)
|
||||
|
||||
## 相关文件
|
||||
|
||||
(关联的代码路径、配置路径等)
|
||||
|
||||
## 相关技能
|
||||
|
||||
(如有对应的 Skill,用相对链接引用)
|
||||
```
|
||||
|
||||
必填章节:`规则内容`
|
||||
按需章节:`相关文件`、`相关数据表`、`相关命令`、`相关技能`
|
||||
|
||||
## 新增规则流程
|
||||
|
||||
### 1. 判断是否需要新增规则
|
||||
|
||||
满足以下条件之一时,应建议新增规则:
|
||||
- 某个模块有独特的开发约束,不适合写在全局 `AGENTS.md`
|
||||
- 开发过程中发现了可复用的约定,值得记录
|
||||
- 用户明确要求"记录这条规则"/"这个模块有约束"
|
||||
|
||||
不满足条件时:
|
||||
- 如果是全局性规则 → 记录到 `AGENTS.md`(需框架作者身份确认)
|
||||
- 如果是项目业务概述 → 记录到 `.agents/PROJECT.md`
|
||||
|
||||
### 2. 确定命名与来源
|
||||
|
||||
- 框架内置规则:`ulthon-{模块}-{场景}.md`
|
||||
- 使用者业务规则:`project-{模块}-{场景}.md`
|
||||
|
||||
### 3. 编写规则文件
|
||||
|
||||
在 `.agents/rules/` 下创建文件,按格式模板填写。
|
||||
|
||||
规则内容要求:
|
||||
- **可执行**:智能体能直接据此行动
|
||||
- **可验证**:有明确的对/错判断标准
|
||||
- **有边界**:明确写出作用域,避免被误用到其他模块
|
||||
|
||||
### 4. 更新索引(必须)
|
||||
|
||||
新增规则后,必须同步更新两处索引:
|
||||
|
||||
**AGENTS.md(框架级索引)**:
|
||||
在「零散规则」章节的索引表中新增一行(仅 `ulthon-` 前缀的规则)。
|
||||
|
||||
**PROJECT.md(全量索引)**:
|
||||
在「规则索引」章节的索引表中新增一行(所有前缀的规则)。
|
||||
|
||||
### 5. 迁移现有内容
|
||||
|
||||
如果规则内容原本记录在 `PROJECT.md` 的「增量规则记录」章节,迁移后应将该章节的对应内容替换为指向规则文件的引用(而非直接删除,保留历史痕迹)。
|
||||
|
||||
## 读取规则
|
||||
|
||||
智能体在以下场景应主动查阅 `.agents/rules/`:
|
||||
|
||||
1. 首次接触项目时,通过 `AGENTS.md` 或 `PROJECT.md` 的索引了解有哪些规则
|
||||
2. 涉及特定模块开发时,查找该模块是否有对应的规则文件
|
||||
3. 用户提到某个模块有特殊约束时,查找对应规则
|
||||
|
||||
## 维护规则
|
||||
|
||||
- 规则内容变更时,同步更新文件内容和索引中的说明列
|
||||
- 规则过期时,标记为"已废弃"或直接删除,并从索引中移除
|
||||
- 框架更新时,只操作 `ulthon-` 前缀的规则文件,不动 `project-` 前缀的文件
|
||||
|
||||
## 索引格式
|
||||
|
||||
索引表统一使用以下格式:
|
||||
|
||||
```markdown
|
||||
| 规则文件 | 来源 | 作用域 | 说明 |
|
||||
|---------|------|--------|------|
|
||||
| ulthon-timer-multi-node.md | 框架 | 定时任务相关 | 多节点协调规则 |
|
||||
| project-order-stock-lock.md | 业务 | 订单模块 | 库存锁定规则 |
|
||||
```
|
||||
183
.agents/skills/ulthon-scheme-curd-workflow/SKILL.md
Normal file
183
.agents/skills/ulthon-scheme-curd-workflow/SKILL.md
Normal file
@@ -0,0 +1,183 @@
|
||||
---
|
||||
name: "ulthon-scheme-curd-workflow"
|
||||
description: "指导使用 Scheme 与 CURD 的标准开发流程。需要新增/调整表结构并生成模块基础代码时调用。"
|
||||
---
|
||||
|
||||
# Scheme + CURD 标准工作流
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 新增业务表、调整字段、补充索引/注释,并希望保持“代码 <-> 数据库”一致。
|
||||
- 准备生成控制器/模型/视图等基础 CRUD 代码。
|
||||
|
||||
## 关键原则
|
||||
|
||||
- CURD 生成前,Scheme 与数据库表结构必须完全一致,否则会被拒绝。
|
||||
- 业务 Scheme 代码统一放在 `app/admin/scheme/`。
|
||||
- 表结构设计遵循项目数据库规范:表名小写下划线、字段注释完整、避免 ENUM。
|
||||
- CURD 命令中的 `{table}` 参数应为**不含前缀的下划线**格式(例如:数据库表 `ul_user_profile` 对应的参数为 `user_profile`)。
|
||||
- CURD 生成的页面脚本(`index.js` / `add.js` / `edit.js` / `read.js` / `_common.js`)默认与视图文件放在同一目录:`app/admin/view/<模块路径>/`,不提供“输出到其他 JS 目录”的配置项。
|
||||
- 一旦你开始在生成代码上做业务改造,就应默认“正式目录不可被覆盖”,后续结构变更需要走“临时生成 + 按需合并”。
|
||||
|
||||
## CURD 命令参数说明
|
||||
|
||||
| 参数 | 简写 | 说明 |
|
||||
|------|------|------|
|
||||
| `--table` | `-t` | 主表名(支持带前缀或不带前缀) |
|
||||
| `--force` | `-f` | 强制覆盖模式(**谨慎使用**) |
|
||||
| `--delete` | `-d` | 删除模式(**删除生成的文件,不是数据库操作**) |
|
||||
| `--runtime` | `-r` | 临时生成模式(**推荐用于预览**) |
|
||||
|
||||
### 参数使用建议
|
||||
|
||||
1. **首次生成**:直接使用 `-t` 生成到项目目录
|
||||
2. **已有业务代码**:使用 `-r` 生成到临时目录,对比并手动合并新增字段/逻辑
|
||||
3. **删除文件**:`-d` 用于清理已生成的文件,不影响数据库
|
||||
|
||||
## 推荐流程
|
||||
|
||||
### A. 首次生成(代码未做业务改造)
|
||||
|
||||
1. 在 `app/admin/scheme/` 新建或修改对应表的 Scheme 类。
|
||||
2. 仅查看差异(不改库),确认当前 Scheme 与 DB 的不一致点:
|
||||
|
||||
```bash
|
||||
php think scheme:sync --dry-run
|
||||
```
|
||||
|
||||
3. 执行同步,将代码结构同步到数据库:
|
||||
|
||||
```bash
|
||||
php think scheme:sync
|
||||
```
|
||||
|
||||
4. 生成 CURD(先生成到临时目录确认更稳妥,`{table}` 需使用下划线格式):
|
||||
|
||||
```bash
|
||||
php think curd -t {table} -r
|
||||
```
|
||||
|
||||
5. 确认无误后正式生成:
|
||||
|
||||
```bash
|
||||
php think curd -t {table}
|
||||
```
|
||||
|
||||
也可以直接使用 `-f` 强制覆盖(首次生成或确认无需保留时)。
|
||||
|
||||
### B. 以数据库为准
|
||||
|
||||
1. 在数据库中创建/修改表结构。
|
||||
2. 反向生成 Scheme 代码(`{table}` 需使用下划线格式):
|
||||
|
||||
```bash
|
||||
php think scheme:make -t {table}
|
||||
```
|
||||
|
||||
3. 再执行 CURD:
|
||||
|
||||
```bash
|
||||
php think curd -t {table} -r
|
||||
```
|
||||
|
||||
## 已有业务代码时如何安全重新生成(核心能力)
|
||||
|
||||
> 框架的 CURD 机制支持在任何阶段安全地重新生成代码。这是与其他很多系统的关键区别:其他系统要么不推荐重新生成,要么重新生成会直接覆盖已有改动。
|
||||
|
||||
### 场景
|
||||
|
||||
你已经基于 CURD 生成的代码做了业务定制(改过控制器逻辑、视图布局、JS 交互等),现在需要新增或调整字段。
|
||||
|
||||
### 错误做法
|
||||
|
||||
直接 `curd -t {table} -f` 强制覆盖 -- 会丢失所有业务改动。
|
||||
|
||||
### 正确做法(临时生成 + 按需合并)
|
||||
|
||||
1. 先修改 Scheme 并同步结构(确保 Scheme 与 DB 一致):
|
||||
|
||||
```bash
|
||||
php think scheme:sync
|
||||
```
|
||||
|
||||
2. 生成到临时目录(**不影响正式代码**):
|
||||
|
||||
```bash
|
||||
php think curd -t {table} -r
|
||||
```
|
||||
|
||||
3. 对比临时目录与正式目录的差异,仅将结构性变更按需合并回正式代码:
|
||||
- 新增字段对应的列表列、表单项、校验规则
|
||||
- 新增字段在模型中的属性声明
|
||||
- 其他自动生成的配置(如关联关系、组件类型)
|
||||
|
||||
4. 合并后做一次功能自测,重点验证新增字段在新增/编辑/列表/详情中的完整链路。
|
||||
|
||||
### 临时目录位置
|
||||
|
||||
使用 `-r` 参数时,生成的文件会输出到 `runtime/curd/` 目录下,按表名组织,可直接与 `app/` 下的正式代码对比。
|
||||
|
||||
## 业务定制(生成后必做)
|
||||
|
||||
CURD 生成的代码是标准化骨架,实际业务几乎都需要在此基础上调整。所有修改仅在 `app/` 目录进行。
|
||||
|
||||
### 生成物说明
|
||||
|
||||
一次 CURD 会生成以下文件(路径以 `app/admin/` 为基准):
|
||||
|
||||
| 类型 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 控制器 | `controller/<模块路径>.php` | 列表/新增/编辑/详情/删除等动作 |
|
||||
| 模型 | `model/<表名>.php` | 字段定义、关联、获取器等 |
|
||||
| 视图 | `view/<模块路径>/index.html` 等 | 列表页、新增/编辑/详情页 |
|
||||
| JS 脚本 | `view/<模块路径>/index.js` 等 | 与视图同名配对的页面脚本 |
|
||||
| 公共脚本 | `view/<模块路径>/_common.js` | 模块级公共逻辑 |
|
||||
|
||||
### 逐页检查清单
|
||||
|
||||
1. **列表页(index)**:列展示字段、搜索项、行操作按钮(编辑/删除/详情等)、顶部工具栏按钮(新增等)
|
||||
2. **新增/编辑页(add/edit)**:表单字段、校验规则、默认值、关联数据加载
|
||||
3. **详情页(read)**:展示字段与格式
|
||||
4. **公共逻辑(_common.js)**:模块级事件绑定与共享函数
|
||||
|
||||
### 接口需求
|
||||
|
||||
如需让页面同时支持 JSON 输出(供移动端/前端调用),使用"页面/接口同体机制",详见 [ulthon-page-api-dual-mode](./ulthon-page-api-dual-mode/SKILL.md)。
|
||||
|
||||
## 验证与交付
|
||||
|
||||
### 功能验证
|
||||
|
||||
用 `tools:http:call` 在命令行模拟已登录管理员请求,验证增删改查链路:
|
||||
|
||||
```bash
|
||||
# 列表
|
||||
php think tools:http:call --url="/admin/<模块路径>/index" --method=GET
|
||||
# 新增
|
||||
php think tools:http:call --url="/admin/<模块路径>/add" --method=POST --data='{"title":"测试"}'
|
||||
# 编辑
|
||||
php think tools:http:call --url="/admin/<模块路径>/edit" --method=POST --data='{"id":1,"title":"修改后"}'
|
||||
# 详情
|
||||
php think tools:http:call --url="/admin/<模块路径>/read?id=1" --method=GET
|
||||
# 删除
|
||||
php think tools:http:call --url="/admin/<模块路径>/delete" --method=POST --data='{"id":1}'
|
||||
```
|
||||
|
||||
异常时用 `php think tools:log:*` 追踪,详见 [ulthon-tools-http-call](./ulthon-tools-http-call/SKILL.md)。
|
||||
|
||||
### 功能配套
|
||||
|
||||
- **菜单**:用 `admin:menu:*` 新增/调整菜单,与页面路径/节点保持一致。详见 [ulthon-admin-menu-cli](./ulthon-admin-menu-cli/SKILL.md)。
|
||||
- **权限**:用 `admin:permission:nodes` 生成/核对节点,为角色分配节点并为用户分配角色。详见 [ulthon-permission-cli](./ulthon-permission-cli/SKILL.md)。
|
||||
- **数据库排错**:必要时用 `tools:db:*` 做只读检查,详见 [ulthon-db-tools-debug](./ulthon-db-tools-debug/SKILL.md)。
|
||||
|
||||
### 交付检查
|
||||
|
||||
- [ ] 后台页面:列表/表单/详情/删除等核心路径可用
|
||||
- [ ] 命令行验证:增删改查已通过 tools 命令跑通
|
||||
- [ ] 代码规范:按仓库根目录 `.php-cs-fixer.php` 约束自查
|
||||
- [ ] 菜单与权限:已配置且符合预期
|
||||
|
||||
## 交互提示
|
||||
|
||||
- 命令行工具通常支持 `--force-force`(`-ff`)跳过交互确认。
|
||||
118
.agents/skills/ulthon-scheme-definition/SKILL.md
Normal file
118
.agents/skills/ulthon-scheme-definition/SKILL.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
name: "ulthon-scheme-definition"
|
||||
description: "指导编写 Ulthon Admin 的 Scheme 架构定义文件。当需要新增表结构、修改字段定义或配置组件显示时调用。"
|
||||
---
|
||||
|
||||
# Ulthon Scheme 定义指南
|
||||
|
||||
本技能指导如何在 `app/admin/scheme/` 目录下编写 Scheme 文件,这些文件定义了数据库表结构以及后台管理界面的组件呈现方式。
|
||||
|
||||
参考文档:[表结构-ulthon_admin](https://doc.ulthon.com/read/augushong/ulthon_admin/619efc9d7af62/zh-cn/2.x.html)
|
||||
|
||||
## 基本结构
|
||||
|
||||
Scheme 文件是一个 PHP 类,继承自 `BaseScheme`,并使用 PHP 8 注解(Attribute)来描述元数据。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_your_table_name', comment: '表注释')]
|
||||
#[Index(columns: ['field1'], name: 'idx_field1', type: 'NORMAL')]
|
||||
class YourClassName extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 100, default: '', comment: '标题')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'radio', options: ['禁用', '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
```
|
||||
|
||||
## 注解详解
|
||||
|
||||
### 1. #[Table] (类注解)
|
||||
- `name`: 数据库表名(建议以 `ul_` 开头)。
|
||||
- `comment`: 表注释。
|
||||
- `engine`: 存储引擎,默认 `InnoDB`。
|
||||
- `charset`: 字符集,默认 `utf8mb4`。
|
||||
|
||||
### 2. #[Index] (类注解,可重复)
|
||||
- `columns`: 索引列,字符串或数组(如 `['cate_id', 'status']`)。
|
||||
- `name`: 索引名称。
|
||||
- `type`: 索引类型:`NORMAL`, `UNIQUE`, `FULLTEXT`。
|
||||
|
||||
### 3. #[Field] (属性注解)
|
||||
- `type`: 字段类型 (e.g., `int`, `bigint`, `char`, `varchar`, `text`, `decimal`, `tinyint`)。
|
||||
- `length`: 长度 (对于 char/varchar/int)。
|
||||
- `precision`: 精度 (对于 decimal)。
|
||||
- `scale`: 小数位数 (对于 decimal)。
|
||||
- `nullable`: 是否允许为空 (bool)。
|
||||
- `default`: 默认值 (mixed)。
|
||||
- `comment`: 字段注释。
|
||||
- `unsigned`: 是否无符号 (bool)。
|
||||
- `autoIncrement`: 是否自增 (bool)。
|
||||
- `primary`: 是否为主键 (bool)。
|
||||
|
||||
### 4. #[Component] (属性注解,可选)
|
||||
定义在后台管理页面中该字段使用的 UI 组件。
|
||||
- `type`: 组件类型。常用值:
|
||||
- `text`: 普通文本框(默认)。
|
||||
- `image`: 单图片上传。
|
||||
- `images`: 多图片上传(默认分隔符为 `|`)。
|
||||
- `file`: 单文件上传。
|
||||
- `files`: 多文件上传(默认分隔符为 `|`)。
|
||||
- `date`: 时间/日期组件。需配合 `options` 指定格式,如 `datetime` 或 `date`。
|
||||
- `editor`: 富文本编辑器。
|
||||
- `textarea`: 文本域。
|
||||
- `select`: 下拉选择框(需配合 `options`)。
|
||||
- `switch`: 开关组件(需配合 `options`,如 `['0' => '关闭', '1' => '开启']`)。
|
||||
- `checkbox`: 复选框(需配合 `options`)。
|
||||
- `radio`: 单选框(需配合 `options`)。
|
||||
- `relation`: 关联表下拉选择。`options` 需包含:
|
||||
- `table`: 关联表名。
|
||||
- `relationBindSelect`: 显示的字段名。
|
||||
- `primaryKey`: 关联表主键(可选)。
|
||||
- `onlyFields`: 列表页显示的字段(可选,用 `|` 分隔)。
|
||||
- `table`: 表格选择器。`options` 需包含:
|
||||
- `table`: 关联表名。
|
||||
- `type`: 选择模式 (`checkbox`/`radio`)。
|
||||
- `valueField`: 值字段名。
|
||||
- `fieldName`: 显示字段名。
|
||||
- `city`: 城市选择器。`options` 需包含:
|
||||
- `level`: 层级 (`province`/`city`/`area`)。
|
||||
- `options`: 选项数据。支持索引数组 `['禁用', '启用']` 或关联数组 `['1' => '男', '2' => '女']`,对于 `relation`/`table`/`city` 类型,则是配置参数数组。
|
||||
|
||||
## 编写规范
|
||||
|
||||
1. **命名规范**: 文件名采用大驼峰(PascalCase),且必须与类名一致。
|
||||
2. **时间字段**: 统一使用 `int` 存储 Unix 时间戳,默认 `0`,非空。不要使用 `datetime`/`timestamp`。
|
||||
3. **软删除**: `delete_time` 字段(`int`,默认 `0`)存在时,模型自动启用软删除机制。查询时不要手写 `delete_time` 条件。
|
||||
4. **状态表达**: 避免使用 MySQL ENUM 类型。优先使用 `int` 或 `tinyint` 配合 `#[Component(type: 'radio')]` 或 `#[Component(type: 'switch')]`。
|
||||
5. **注释约定**: `Field` 的 `comment` 会直接同步到数据库字段注释,同时也作为后台表单的 Label。
|
||||
6. **字段后缀约定**: 框架会根据字段名后缀自动推断部分组件类型(若未显式指定 `#[Component]`):
|
||||
- `image`, `logo`, `photo`, `icon`: 默认为单图片。
|
||||
- `images`, `photos`, `icons`: 默认为多图片。
|
||||
- `file`: 默认为单文件。
|
||||
- `files`: 默认为多文件。
|
||||
|
||||
270
.agents/skills/ulthon-timer/SKILL.md
Normal file
270
.agents/skills/ulthon-timer/SKILL.md
Normal file
@@ -0,0 +1,270 @@
|
||||
---
|
||||
name: "ulthon-timer"
|
||||
description: "内置秒级定时器(php think timer)的使用与扩展规范;用于新增/调整定时任务(site/call、并发分片、TimerController 防刷)。"
|
||||
---
|
||||
|
||||
# timer(内置秒级定时器)
|
||||
|
||||
## 核心机制(你要记住的 3 件事)
|
||||
|
||||
1) 任务配置统一从 `app_file_path('common/command/timer/config.php')` 读取
|
||||
2) 每个配置会按 `concurrency` 自动展开成多份任务实例,并自动注入 `concurrency_id` / `concurrency_count`
|
||||
3) “是否该执行”主要由定时器侧的 Cache 节流控制(可选叠加控制器侧的防刷保护)
|
||||
|
||||
相关实现入口:
|
||||
|
||||
- 系统入口(优先从 app 层理解):
|
||||
- 定时器命令入口:`app/common/command/Timer.php`(实现继承自 `extend/base/common/command/TimerBase.php`)
|
||||
- 任务实例服务入口:`app/common/service/TimerService.php`(实现继承自 `extend/base/common/service/TimerServiceBase.php`)
|
||||
- site 任务控制器基类入口:`app/common/controller/TimerController.php`(实现继承自 `extend/base/common/controller/TimerControllerBase.php`)
|
||||
- 实现文件(需要深入机制时再看 Base):
|
||||
- [Timer.php](../../../app/common/command/Timer.php) / [TimerBase.php](../../../extend/base/common/command/TimerBase.php)
|
||||
- [TimerService.php](../../../app/common/service/TimerService.php) / [TimerServiceBase.php](../../../extend/base/common/service/TimerServiceBase.php)
|
||||
- [TimerController.php](../../../app/common/controller/TimerController.php) / [TimerControllerBase.php](../../../extend/base/common/controller/TimerControllerBase.php)
|
||||
- 运行配置:[timer.php](../../../config/timer.php)
|
||||
|
||||
## 新增定时任务(默认规则)
|
||||
|
||||
无特殊情况下,新增定时任务应当通过本机制实现:在 `timer/config.php` 注册任务 + 以 `site` 或 `call` 的方式实现目标逻辑。
|
||||
|
||||
### 1) 选择任务类型
|
||||
|
||||
- `site`:通过 HTTP 访问一个控制器地址(默认优先使用)。即使任务需要“长时间运行”,也尽量设计成 `site` 模式(分片/分批/可重入),以复用框架的页面/接口同体、鉴权、日志、事务、限流等能力。
|
||||
- `call`:直接执行一个 PHP callable(不推荐;除非万不得已)。仅在确实不适合走 HTTP 上下文、且不希望暴露为控制器入口时使用。
|
||||
|
||||
长时间运行任务的推荐写法(仍用 `site`):
|
||||
|
||||
- 设计为"可重入"的短任务:每次 `do()` 只处理一小批数据,处理进度写入缓存/表,下次继续
|
||||
- 结合 `concurrency` 做分片:按 `$this->concurrencyId` 划分数据范围,多个实例并行推进
|
||||
- 结合 `frequency` 控制节奏:用调度频率限制整体吞吐,避免单次占用过久
|
||||
|
||||
#### 按时间窗口循环处理(队列消费推荐)
|
||||
|
||||
适用于:需要持续消费队列/轮询数据的场景(如消息处理、订单状态同步、通知推送等)。
|
||||
|
||||
核心思路:在 `do()` 方法内设置一个**最大执行时间窗口**,循环处理单条数据,超时后自动退出,由定时器下次调度继续。
|
||||
|
||||
```php
|
||||
class MyQueueTask extends TimerController
|
||||
{
|
||||
// 每次执行的最大运行时间(秒),根据业务和定时器 frequency 合理设置
|
||||
// 建议不超过 frequency 的一半,留出调度间隔
|
||||
protected $maxRunTime = 5;
|
||||
|
||||
// 防刷间隔(秒),与定时器侧 frequency 配合
|
||||
protected $frequency = 10;
|
||||
|
||||
public function do()
|
||||
{
|
||||
$maxTime = time() + $this->maxRunTime;
|
||||
|
||||
do {
|
||||
$this->doItem();
|
||||
|
||||
// 超时检查:时间窗口用完则退出,下次调度继续
|
||||
if (time() >= $maxTime) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
protected function doItem()
|
||||
{
|
||||
// 取一条待处理的数据
|
||||
$item = SomeModel::where('status', 0)->order('id', 'asc')->find();
|
||||
|
||||
if (empty($item)) {
|
||||
// 队列为空时短暂休眠,避免空转消耗 CPU
|
||||
sleep(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// ... 处理单条数据的业务逻辑 ...
|
||||
|
||||
$item->status = 1;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
| 配置项 | 建议 |
|
||||
|--------|------|
|
||||
| `maxRunTime` | 不超过 `frequency` 的一半;例如 `frequency=10` 时设 3~5 秒 |
|
||||
| `doItem()` 中的 `sleep()` | 队列空时必须休眠,防止 CPU 空转;有数据时不要 sleep |
|
||||
| `doItem()` 的粒度 | 每次只处理一条/一批数据,保证可重入、可中断 |
|
||||
| `frequency` | 与 `maxRunTime` 配合,`frequency` >= `maxRunTime * 2` 为宜 |
|
||||
|
||||
**与普通定时任务的区别:**
|
||||
|
||||
- 普通任务:`do()` 执行一次就返回,靠定时器周期性调度
|
||||
- 时间窗口模式:`do()` 在时间窗口内持续循环消费,处理完积压数据后自动退出
|
||||
- 优势:面对突发积压数据时,单次调度可处理多条,提高吞吐;超时安全退出,不会阻塞定时器
|
||||
|
||||
### 2) 编写任务目标(target)
|
||||
|
||||
#### A. site 类型(HTTP 任务)
|
||||
|
||||
实现方式建议:
|
||||
|
||||
- 框架使用者(做业务):仅在 `app/tools/controller/timer/` 新增控制器 `*.php` 即可;不需要、也不应该在 `extend/base/` 增加 `*Base.php`
|
||||
- 框架作者(维护内核):在 `extend/base/tools/controller/timer/` 新增 `*Base.php` 作为默认实现,同时在 `app/tools/controller/timer/` 增加同名入口类 `*.php` 继承 Base(系统唯一调用入口)
|
||||
- 控制器建议继承 `app\common\controller\TimerController`(以获得并发参数校验与可选的 `$frequency` 防刷);执行入口一般提供 `do()`
|
||||
|
||||
示例(已有实现可参考):[ClearLog.php](../../../app/tools/controller/timer/ClearLog.php)、[ClearLogBase.php](../../../extend/base/tools/controller/timer/ClearLogBase.php)
|
||||
|
||||
并发与防刷建议:
|
||||
|
||||
- 如需并发分片处理,在控制器中设置:
|
||||
- `protected $concurrency = N;`
|
||||
- 使用 `$this->concurrencyId` 做分片编号(0 ~ N-1)
|
||||
- 如希望防止外部重复请求(不仅是定时器自身节流),在控制器中设置:
|
||||
- `protected $frequency = 秒数;`
|
||||
- 这会启用 `TimerControllerBase::protectVisit()` 基于 URL 的防刷限制
|
||||
|
||||
#### B. call 类型(函数任务)
|
||||
|
||||
目标形态:
|
||||
|
||||
- `target` 为 `call_user_func` 可执行的 callable,例如:`[SomeService::class, 'method']`、闭包等
|
||||
- 由于 Base/App 双层机制的入口要求,类引用应指向 `app/` 下的入口类(由入口类继承 Base 实现)
|
||||
- 长时间/重任务不建议用 `call`:它缺少 HTTP 上下文与控制器层通用能力,排错与复用成本更高
|
||||
|
||||
### 3) 注册到任务配置(timer/config.php)
|
||||
|
||||
默认配置文件位置(支持分层覆盖):
|
||||
|
||||
- 优先覆盖:`app/common/command/timer/config.php`(一旦存在,`app_file_path(...)` 将优先读取此文件)
|
||||
- 框架默认:`extend/base/common/command/timer/config.php`(当 app 未提供时回落到该文件)
|
||||
|
||||
字段说明(兜底默认值由 Base 层的 `initConfigItem()` 提供):
|
||||
|
||||
- `name`:任务唯一名称(用于 Cache key),不可重复
|
||||
- `type`:`site` 或 `call`
|
||||
- `target`:
|
||||
- `site`:以 `/` 开头的相对路径(建议指向 `tools/timer.*` 控制器的 `do` 方法)
|
||||
- `call`:callable
|
||||
- `frequency`:执行频率(秒),小于 0 会被修正为 0
|
||||
- `concurrency`:并发数量(默认 1)。`site` 类型会自动把并发参数写入 query
|
||||
- `run_type`:调度策略(仅对 `site` 类型生效,`call` 类型不受影响)。可选值见「多节点协调 > run_type 调度」
|
||||
|
||||
示例(site):
|
||||
|
||||
```php
|
||||
return [
|
||||
[
|
||||
'name' => 'clear_log',
|
||||
'type' => 'site',
|
||||
'target' => '/tools/timer.ClearLog/do',
|
||||
'frequency' => 600,
|
||||
'concurrency' => 1,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
示例(call):
|
||||
|
||||
```php
|
||||
return [
|
||||
[
|
||||
'name' => 'system_host_register',
|
||||
'type' => 'call',
|
||||
'target' => [\app\common\service\HostService::class, 'heartbeat'],
|
||||
'frequency' => 30,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## 运行与验证
|
||||
|
||||
### 运行命令
|
||||
|
||||
- 常规运行:`php think timer`
|
||||
- 只跑一轮(便于验证):`php think timer --temp`
|
||||
- 无任务时不输出“no request”:`php think timer --quiet`
|
||||
|
||||
### 本地调试(指定请求 Host)
|
||||
|
||||
site 任务会按站点域名发起请求,默认从 `sysconfig('site','site_domain')` 读取。
|
||||
|
||||
- 本地调试:`php think timer --local --local-host=http://localhost --local-port=8000`
|
||||
|
||||
### 运行模式
|
||||
|
||||
配置在 [timer.php](../../../config/timer.php)。定时器使用 Guzzle CurlMultiHandler 实现非阻塞异步事件循环:
|
||||
|
||||
- `site` 类型任务通过 curl multi 并行发送 HTTP 请求,真正非阻塞
|
||||
- `call` 类型任务在主循环中同步执行
|
||||
- `pending` 数组追踪进行中的请求,`handler->tick()` 非阻塞推进
|
||||
- 自适应 sleep 策略(50ms/200ms)避免 CPU 空转
|
||||
|
||||
### 配置项说明
|
||||
|
||||
| 配置键 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `connect_timeout` | `30` | 连接超时时间(秒) |
|
||||
| `timeout` | `86400` | 请求响应超时时间(秒) |
|
||||
| `max_handles` | `100` | curl multi 最大并发句柄数 |
|
||||
| `select_timeout` | `0.001` | curl_multi_select 超时(秒) |
|
||||
| `clear_log_days` | `3` | ClearLog 任务清理 debug_log 表的保留天数,支持从 `.env` 的 `TIMER_CLEAR_LOG_DAYS` 覆盖 |
|
||||
|
||||
## 常见坑位(快速自检)
|
||||
|
||||
- `name` 重复:会导致 Cache key 冲突,表现为任务"莫名其妙不跑/跑得不对"
|
||||
- `concurrency` 与控制器侧 `$concurrency` 不一致:会触发 `concurrency id/count error`
|
||||
- 只依赖控制器侧 `$frequency`:它只是防刷,不是调度;调度频率以定时器侧 Cache 节流为准
|
||||
- 本地开发忘记 `--local`:`site` 任务默认请求 `sysconfig('site','site_domain')` 指向的生产域名,不带 `--local` 会直接打到线上
|
||||
|
||||
## 多节点协调
|
||||
|
||||
定时器支持多节点部署,以数据库(MySQL)作为协调中心。多个节点连接同一个数据库即可自动组成集群。
|
||||
|
||||
### 节点注册
|
||||
|
||||
每个节点启动后通过 `system_host_register` call 任务自动注册心跳(每 30 秒一次)。节点 ID 生成规则为 `{hostname}-{8位md5}`,持久化在 `runtime/node_id.lock` 文件中,重启后保持不变。
|
||||
|
||||
### 主节点选举
|
||||
|
||||
- 第一个注册的节点自动成为主节点
|
||||
- 管理员可在主机列表页面手动切换主节点
|
||||
- 相关 API:`HostService::setMasterNode()` / `HostService::getMasterNode()`
|
||||
|
||||
### run_type 调度
|
||||
|
||||
`run_type` 仅对 `site` 类型任务生效,`call` 类型任务始终在所有节点执行。可选值:
|
||||
|
||||
| run_type | 行为 | 适用场景 |
|
||||
|----------|------|----------|
|
||||
| `auto`(默认) | 两阶段 DB 行锁竞争:BEGIN / SELECT FOR UPDATE / UPDATE / COMMIT 抢占,抢占成功后释放锁再执行。每个频率窗口内只有一个节点执行 | 通用场景 |
|
||||
| `main` | 仅主节点执行 | 需要集中处理的任务 |
|
||||
| `all` | 所有节点各自独立执行 | 节点本地清理等 |
|
||||
| `manual` | 仅当 DB 中 `manual_trigger=1` 时执行,执行后自动重置为 0。通过管理后台触发 | 运维手动触发 |
|
||||
|
||||
### 配置同步
|
||||
|
||||
`TimerService::syncConfigToDatabase()` 在定时器启动时运行,将 PHP 配置文件中的任务同步到 `system_timer_config` 表。同步时不会覆盖数据库中已管理的字段(`run_type`、`status`),以管理后台的设置为准。
|
||||
|
||||
### 执行日志
|
||||
|
||||
`TimerControllerBase::execute()` 自动包裹 `do()` 并调用 `logStart()` / `logEnd()` 记录执行日志,开发者无需手动调用。配置中写 `/do`,运行时 `TimerServiceBase` 自动重写为 `/execute` 以触发日志包裹。`host_id` 会自动注入到 site 任务的 URL 参数中,用于标识执行节点。
|
||||
|
||||
### 管理后台
|
||||
|
||||
| 页面 | 路径 | 功能 |
|
||||
|------|------|------|
|
||||
| 定时器配置 | `/admin/system.timer_config/index` | 管理 run_type、status、手动触发 |
|
||||
| 定时器日志 | `/admin/system.timer_log/index` | 只读执行日志查看 |
|
||||
| 主机列表 | `/admin/system.host/index` | 节点状态、主节点管理 |
|
||||
|
||||
### 日志清理
|
||||
|
||||
```bash
|
||||
php think admin:timer:log:clean --days=30
|
||||
```
|
||||
|
||||
### 相关数据表
|
||||
|
||||
- `ul_system_timer_config`:任务协调配置(run_type、status、manual_trigger 等)
|
||||
- `ul_system_timer_log`:执行日志记录
|
||||
- `ul_system_host`:新增 `is_master` 字段,标识主节点
|
||||
60
.agents/skills/ulthon-tools-http-call/SKILL.md
Normal file
60
.agents/skills/ulthon-tools-http-call/SKILL.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: "ulthon-tools-http-call"
|
||||
description: "命令行调用后台控制器/URL 进行联调与功能验证(含默认 super token 模拟登录)。"
|
||||
---
|
||||
|
||||
# tools:http:call(命令行 HTTP 调用工具)
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 需要在命令行快速验证某个后台页面/接口是否正常返回。
|
||||
- 需要绕过浏览器环境,在 CLI 中模拟已登录管理员请求。
|
||||
- 需要用 `--app/--controller/--action` 直接拼控制器路径调用。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 1) 直接按 URL 调用
|
||||
|
||||
```bash
|
||||
php think tools:http:call --url="/admin/system.admin/index" --method=GET
|
||||
```
|
||||
|
||||
### 2) 按控制器参数调用
|
||||
|
||||
```bash
|
||||
php think tools:http:call --app=admin --controller=system.admin --action=index --method=GET
|
||||
```
|
||||
|
||||
## 请求参数
|
||||
|
||||
- `--url`:请求路径(支持以 `/` 开头的相对路径)。
|
||||
- `--method`:GET/POST/PUT/PATCH/DELETE。
|
||||
- `--data`:JSON 字符串,请求会自动带 `Content-Type: application/json`。
|
||||
- `--body`:原始请求体字符串。
|
||||
- `--headers`:JSON 字符串的请求头对象(例如 `{ "Accept":"application/json" }`)。
|
||||
- `--app --controller --action`:框架特性参数,用于拼控制器路径。
|
||||
|
||||
## super token(默认模拟登录)
|
||||
|
||||
### 机制说明
|
||||
|
||||
- `tools:http:call` 默认会为每次请求生成一个新的 token,并写入 `Cache::store('login')`。
|
||||
- 请求会自动携带 `Authorization: Bearer <token>`。
|
||||
- 服务端会按既有 Bearer token 机制从 `Cache::store('login')` 读取登录态,因此只要命令行与服务端使用同一套缓存即可生效。
|
||||
|
||||
### 指定模拟账号
|
||||
|
||||
```bash
|
||||
php think tools:http:call --url="/admin/system.admin/index" --user-id=2
|
||||
```
|
||||
|
||||
### 关闭 super token(回到未登录行为)
|
||||
|
||||
```bash
|
||||
php think tools:http:call --url="/admin/system.admin/index" --super-token=false
|
||||
```
|
||||
|
||||
## 常见现象与排查
|
||||
|
||||
- 仍提示“请先登录后台”:通常是命令行与服务端的缓存不共享,或服务端未运行/未走到同一环境配置。
|
||||
- 返回“无权限访问”:说明已经是登录态,但该账号对当前节点没有权限;可以换 `--user-id` 或调整权限节点。
|
||||
256
.agents/skills/ulthon-update-workflow/SKILL.md
Normal file
256
.agents/skills/ulthon-update-workflow/SKILL.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
name: "ulthon-update-workflow"
|
||||
description: "指导 AI agent 协助开发者使用 php think admin:update 同步上游框架代码,含预览评估、冲突策略选择与更新后操作。"
|
||||
---
|
||||
|
||||
# admin:update(框架更新工作流)
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本技能指导 AI agent 协助开发者执行 `php think admin:update`,将上游框架代码同步到本地项目。
|
||||
|
||||
核心原则:**开发者当前代码状态为主,上游更新为参考**。更新命令不会盲目覆盖,而是根据文件类型和定制状态分类处理。agent 的职责是帮助开发者评估风险、选择合适的冲突策略,并在更新后执行必要的收尾操作。
|
||||
|
||||
## 2. 触发场景
|
||||
|
||||
- 开发者主动要求"更新框架"、"同步上游"、"php think admin:update"
|
||||
- 开发者询问"怎么更新"、"新版本有什么变化"
|
||||
- 开发者提到版本号与上游不一致,需要拉取最新框架代码
|
||||
|
||||
**不主动触发**:除非开发者明确表达版本相关的诉求,否则 agent 不应主动建议执行更新。
|
||||
|
||||
## 3. 更新前检查
|
||||
|
||||
执行更新前,必须依次确认以下事项:
|
||||
|
||||
### 3.1 确认执行模式
|
||||
|
||||
```bash
|
||||
php think admin:stack:mode current
|
||||
```
|
||||
|
||||
- 如果返回 Docker 模式(具体哪些模式属于 Docker 模式见 `php think admin:stack:mode list` 或 `source/stack/stack.json`),后续所有 `php think` 命令前缀改为 `docker compose exec ulthon_admin`
|
||||
- 非 Docker 模式直接执行 `php think`,不做转换
|
||||
|
||||
### 3.2 确认代码已提交
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
- 输出为空:可以继续
|
||||
- 有未提交改动:**必须提醒开发者先提交或暂存**。更新会修改文件,未提交的改动可能丢失或产生冲突
|
||||
|
||||
### 3.3 确认当前版本
|
||||
|
||||
```bash
|
||||
php think admin:version
|
||||
```
|
||||
|
||||
- 记录当前版本号,用于更新后对比
|
||||
|
||||
## 4. 评估阶段(推荐先 dry-run)
|
||||
|
||||
**每次更新前都应先执行 dry-run**,不要跳过这一步。
|
||||
|
||||
```bash
|
||||
php think admin:update --dry-run
|
||||
```
|
||||
|
||||
命令会拉取上游仓库,对比文件差异,输出变更预览。重点关注以下信息:
|
||||
|
||||
### 4.1 风险评估行
|
||||
|
||||
输出末尾会有一行风险评估:
|
||||
|
||||
```
|
||||
风险评估: 强制冲突 X个(高风险) | 可选冲突 X个(中风险) | 无冲突变更 X个(低风险)
|
||||
```
|
||||
|
||||
| 风险等级 | 含义 | 建议操作 |
|
||||
|----------|------|----------|
|
||||
| 强制冲突(高风险) | 开发者修改了 `extend/base/` 等强制跟踪目录的文件 | 建议覆盖,通过 App 层扩展机制重新实现定制 |
|
||||
| 可选冲突(中风险) | 开发者修改了 `app/`/`config/` 等可选目录的文件 | 默认跳过,手动按需合并 |
|
||||
| 无冲突变更(低风险) | 文件未被定制,可以直接更新 | 直接处理 |
|
||||
|
||||
### 4.2 目录分组
|
||||
|
||||
当变更文件超过 5 个时,输出会按目录分组显示:
|
||||
|
||||
```
|
||||
[extend/base/]
|
||||
[update] extend/base/admin/service/AdminUpdateServiceBase.php
|
||||
[app/]
|
||||
[add][conflict:optional:overwrite] app/admin/controller/NewFeature.php
|
||||
```
|
||||
|
||||
### 4.3 冲突标注
|
||||
|
||||
冲突文件会带有策略标签:
|
||||
|
||||
- `[conflict:optional:overwrite]` 或 `[conflict:optional:skip]`:可选文件冲突
|
||||
- `[conflict:force:overwrite]` 或 `[conflict:force:skip]`:强制文件冲突
|
||||
- `[skipped]`:因冲突策略被跳过的文件
|
||||
|
||||
### 4.4 Composer 依赖变更
|
||||
|
||||
输出中包含 `Composer 依赖变更` 段落,列出新增、删除和版本变更的依赖包。
|
||||
|
||||
向开发者报告以上信息,由开发者决定是否继续。
|
||||
|
||||
### 4.5 跳过文件处理
|
||||
|
||||
当 dry-run 输出中存在 `[skipped]` 标记的文件时,说明这些冲突文件被跳过了。此时建议重新执行 dry-run 并加 `--keep-repo`,保留上游克隆目录用于对比:
|
||||
|
||||
```bash
|
||||
php think admin:update --dry-run --optional-conflict=skip --force-conflict=overwrite --keep-repo
|
||||
```
|
||||
|
||||
命令结束后,以下目录会被保留:
|
||||
|
||||
| 目录 | 内容 |
|
||||
|------|------|
|
||||
| `runtime/update/current/` | 当前安装版本的原始代码 |
|
||||
| `runtime/update/repo/` | 上游最新版本的代码 |
|
||||
| 项目根目录 | 开发者实际修改的代码 |
|
||||
|
||||
**对比方式**:逐个读取三个版本的跳过文件,辅助开发者判断是否需要合入上游改动。关注点:
|
||||
- 上游改了什么(对比 `current/` vs `repo/`)
|
||||
- 开发者改了什么(对比 `current/` vs 项目根目录)
|
||||
- 两者是否冲突
|
||||
|
||||
**清理方式**:下次运行 `admin:update` 时会自动清理 `runtime/update/` 目录,或手动删除。
|
||||
|
||||
## 5. 参数参考
|
||||
|
||||
| 参数 | 可选值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `--dry-run` | (无值) | 预览模式,只看变更不执行实际文件操作 |
|
||||
| `--optional-conflict` | `skip` / `overwrite` / `ask` | 可选文件(app/config/route/extend/think/根目录文件)的冲突处理策略 |
|
||||
| `--force-conflict` | `overwrite` / `skip` / `ask` | 强制文件(extend/base/public/database/think)的冲突处理策略 |
|
||||
| `--show` | `all` / `conflict` | 变更输出范围:all 显示全部文件,conflict 只显示冲突文件 |
|
||||
| `--keep-repo` | (无值) | 预览模式下保留上游克隆目录(runtime/update/current/ 和 runtime/update/repo/),便于手动对比跳过的冲突文件 |
|
||||
| `--reinstall` | (无值) | 即使当前版本已是最新,也强制重新安装代码 |
|
||||
| `--update-master` | (无值) | 更新到 master 分支而非最新 tag |
|
||||
|
||||
## 6. 执行更新
|
||||
|
||||
根据 dry-run 的评估结果和开发者意愿,选择对应的执行命令。
|
||||
|
||||
### 6.1 推荐默认方案
|
||||
|
||||
适用于大多数场景,低风险直接处理,可选冲突跳过,强制冲突覆盖:
|
||||
|
||||
```bash
|
||||
php think admin:update --optional-conflict=skip --force-conflict=overwrite --show=all
|
||||
```
|
||||
|
||||
### 6.2 开发者想要合并上游可选改动
|
||||
|
||||
当开发者明确希望将上游对 app/config 等目录的改动也同步过来时:
|
||||
|
||||
```bash
|
||||
php think admin:update --optional-conflict=overwrite --force-conflict=overwrite --show=all
|
||||
```
|
||||
|
||||
### 6.3 开发者不想覆盖任何冲突
|
||||
|
||||
当开发者希望所有冲突文件都跳过,只更新无冲突部分:
|
||||
|
||||
```bash
|
||||
php think admin:update --optional-conflict=skip --force-conflict=skip --show=all
|
||||
```
|
||||
|
||||
**注意**:强制冲突跳过可能导致框架功能异常,agent 应明确警告。
|
||||
|
||||
### 6.4 禁止事项
|
||||
|
||||
- **永远不要使用 `ask` 模式**。agent 无法处理交互式确认提示(`$output->confirm()`),会导致命令挂起
|
||||
- **不要省略冲突策略参数**。不传参数时,如果存在冲突文件,命令会回退到交互模式,同样会挂起
|
||||
- **`--keep-repo` 仅在预览模式下有效**:正式执行时自动忽略,不会保留目录
|
||||
|
||||
## 7. 冲突处理指引
|
||||
|
||||
### 7.1 可选文件冲突
|
||||
|
||||
涉及目录:`app/`、`config/`、`route/`、`extend/think/`、`source/docker/`、根目录文件(如 `.php-cs-fixer.php`)
|
||||
|
||||
这些是开发者可以自由修改的区域。出现冲突说明开发者定制了这些文件,且上游也修改了同一文件。
|
||||
|
||||
处理建议:
|
||||
|
||||
- **默认跳过**(`--optional-conflict=skip`):保留开发者的定制,不引入上游改动
|
||||
- 如果开发者需要上游的某个改动,在 dry-run 输出中找到具体文件路径,建议开发者手动查看 diff 并选择性合并
|
||||
- **不建议直接 overwrite**:会覆盖开发者的所有定制,且无法自动合并
|
||||
|
||||
### 7.2 强制文件冲突
|
||||
|
||||
涉及目录:`extend/base/`、`public/`、`database/`
|
||||
|
||||
这些是框架自动管理的区域。出现冲突说明开发者违反了 Base/App 架构规则,直接修改了 `extend/base/` 下的文件。
|
||||
|
||||
处理建议:
|
||||
|
||||
- **默认覆盖**(`--force-conflict=overwrite`):让框架恢复到标准状态
|
||||
- 覆盖后,提醒开发者通过 App 层扩展机制重新实现之前的定制逻辑(参考 `ulthon-base-app-architecture` 技能)
|
||||
- 如果开发者坚持不覆盖(`--force-conflict=skip`),**必须警告**:跳过强制文件更新可能导致框架功能异常、兼容性问题
|
||||
|
||||
## 8. 更新后操作
|
||||
|
||||
更新命令执行完成后,输出中会包含"建议执行以下后续操作"段落。根据提示,按需执行:
|
||||
|
||||
### 8.1 Composer 依赖变更
|
||||
|
||||
如果输出中出现 `composer.json 已变更,请更新依赖`:
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
**需开发者确认后才执行**,不要自动运行。
|
||||
|
||||
### 8.2 数据库迁移
|
||||
|
||||
如果输出中出现 `数据库迁移文件已变更,请执行迁移`:
|
||||
|
||||
```bash
|
||||
php think migrate:run
|
||||
```
|
||||
|
||||
**需开发者确认后才执行**。迁移可能影响生产数据,务必谨慎。
|
||||
|
||||
### 8.3 缓存清理
|
||||
|
||||
如果输出中出现 `框架核心或配置已变更,请清理缓存`:
|
||||
|
||||
```bash
|
||||
php think clear
|
||||
```
|
||||
|
||||
这一步通常可以直接执行,风险较低。
|
||||
|
||||
### 8.4 确认版本号
|
||||
|
||||
```bash
|
||||
php think admin:version
|
||||
```
|
||||
|
||||
确认版本号已更新为预期版本。
|
||||
|
||||
### 8.5 建议开发者测试
|
||||
|
||||
提醒开发者验证关键业务功能是否正常,特别是:
|
||||
- 后台登录
|
||||
- 菜单与权限
|
||||
- 核心 CURD 页面
|
||||
- 定时任务(如有)
|
||||
|
||||
## 9. 注意事项
|
||||
|
||||
- **禁止使用 `ask` 模式**:agent 无法处理交互式提示,必须通过参数明确指定冲突策略
|
||||
- **更新前务必确保代码已提交**:未提交的改动可能在更新过程中丢失
|
||||
- **不自动执行 composer 或数据库操作**:这些操作影响面大,需开发者确认
|
||||
- **大版本跳跃风险更高**:跨越多个 tag 时变更量大,建议格外谨慎,仔细审查 dry-run 输出
|
||||
- **Docker 模式注意**:Docker 模式下所有 `php think` 命令前缀改为 `docker compose exec ulthon_admin`(具体哪些模式属于 Docker 模式见 `php think admin:stack:mode list` 或 `source/stack/stack.json`)
|
||||
- **更新会临时占用磁盘**:命令会在 `runtime/update/` 下克隆上游仓库进行对比,完成后自动清理
|
||||
- **版本更新说明**:更新完成后,命令会输出跨版本的更新说明(来自 `adminUpdateData/tips.php`),提醒开发者关注
|
||||
18
.example.env
18
.example.env
@@ -3,13 +3,15 @@ APP_DEBUG=true
|
||||
[APP]
|
||||
DEFAULT_TIMEZONE=Asia/Shanghai
|
||||
AUTO_CACHE_LOG=false
|
||||
AUTO_PARSE_API=true
|
||||
|
||||
[DATABASE]
|
||||
MAIN=main
|
||||
TYPE=mysql
|
||||
HOSTNAME=host.docker.internal
|
||||
DATABASE=ulthon
|
||||
USERNAME=root
|
||||
PASSWORD=root
|
||||
DATABASE=ulthon_admin
|
||||
USERNAME=ulthon_admin
|
||||
PASSWORD=MYSQL_PASSWORD
|
||||
HOSTPORT=3306
|
||||
CHARSET=utf8mb4
|
||||
DEBUG=true
|
||||
@@ -19,6 +21,10 @@ FIELDS_CACHE=false
|
||||
[LOG]
|
||||
CHANNEL=file
|
||||
|
||||
[TIMER]
|
||||
# 清理日志保留天数(debug_log 表)
|
||||
CLEAR_LOG_DAYS=3
|
||||
|
||||
[LANG]
|
||||
default_lang=zh-cn
|
||||
|
||||
@@ -53,4 +59,8 @@ DEFAULT_AUTH_CHECK=false
|
||||
STRICT_EVENT=true
|
||||
|
||||
# 严格要求每个页面都建立js文件
|
||||
STRICT_VIEW_JS=true
|
||||
STRICT_VIEW_JS=true
|
||||
|
||||
MAKE_VIEW_WHILE_MISSING=false
|
||||
|
||||
UPDATE_LEVEL=production
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,3 +1,6 @@
|
||||
*.js linguist-language=PHP
|
||||
*.css linguist-language=PHP
|
||||
*.html linguist-language=PHP
|
||||
|
||||
# Shell脚本始终使用LF行尾,避免Windows挂载到Docker时CRLF报错
|
||||
*.sh text eol=lf
|
||||
|
||||
157
.gitea/workflows/build-and-deploy.yml
Normal file
157
.gitea/workflows/build-and-deploy.yml
Normal file
@@ -0,0 +1,157 @@
|
||||
name: build-and-deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REMOTE_APP_DIR: /data/projects/ulthon_admin/docker
|
||||
PACKAGE_NAME: ulthon_admin_release.tar.gz
|
||||
COMPOSE_PROJECT_NAME: ulthon_admin
|
||||
DB_HOSTNAME: host.docker.internal
|
||||
|
||||
jobs:
|
||||
deploy_host15:
|
||||
name: 直传代码并部署到 Host15
|
||||
runs-on: main
|
||||
container:
|
||||
image: ulthon/debian-php82-composer-node20-act:20260503082548
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: 生成 .env
|
||||
shell: bash
|
||||
env:
|
||||
MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
|
||||
DB_HOSTNAME: ${{ env.DB_HOSTNAME }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cp .example.env .env
|
||||
|
||||
awk -v host="$DB_HOSTNAME" -v newpwd="$MYSQL_PASSWORD" '
|
||||
BEGIN { has_host = 0; has_pwd = 0 }
|
||||
$0 ~ /^HOSTNAME=/ {
|
||||
print "HOSTNAME=" host
|
||||
has_host = 1
|
||||
next
|
||||
}
|
||||
$0 ~ /^PASSWORD=/ {
|
||||
print "PASSWORD=" newpwd
|
||||
has_pwd = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!has_host) print "HOSTNAME=" host
|
||||
if (!has_pwd) print "PASSWORD=" newpwd
|
||||
}' .env > .env.tmp
|
||||
|
||||
mv .env.tmp .env
|
||||
|
||||
- name: 安装依赖
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
composer config -g repos.packagist composer https://nexus.hl7.top:1243/repository/composer-proxy/
|
||||
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: 切换到 docker-serve 模式
|
||||
shell: bash
|
||||
run: php think admin:stack:mode use docker-serve -f
|
||||
|
||||
- name: 打包发布文件
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TMP_PACKAGE="/tmp/${PACKAGE_NAME}"
|
||||
rm -f "$TMP_PACKAGE" "$PACKAGE_NAME"
|
||||
|
||||
tar -czf "$TMP_PACKAGE" \
|
||||
--exclude="$PACKAGE_NAME" \
|
||||
--exclude-vcs \
|
||||
--exclude="./runtime/*" \
|
||||
--exclude="./.trae/*" \
|
||||
--exclude="./source/clients/uniapp/node_modules/*" \
|
||||
.
|
||||
cp -f "$TMP_PACKAGE" "$PACKAGE_NAME"
|
||||
|
||||
- name: 创建远端目录
|
||||
uses: appleboy/ssh-action@v0.1.10
|
||||
env:
|
||||
REMOTE_APP_DIR: ${{ env.REMOTE_APP_DIR }}
|
||||
with:
|
||||
host: ${{ secrets.UL_HOST15_IP }}
|
||||
username: ${{ secrets.UL_HOST15_USER }}
|
||||
password: ${{ secrets.UL_HOST15_PASSWORD }}
|
||||
port: ${{ secrets.UL_HOST15_PORT }}
|
||||
envs: REMOTE_APP_DIR
|
||||
script: |
|
||||
set -euo pipefail
|
||||
mkdir -p "${REMOTE_APP_DIR}/incoming"
|
||||
mkdir -p "${REMOTE_APP_DIR}/releases"
|
||||
|
||||
- name: 上传发布包与 compose
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
env:
|
||||
REMOTE_APP_DIR: ${{ env.REMOTE_APP_DIR }}
|
||||
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
|
||||
with:
|
||||
host: ${{ secrets.UL_HOST15_IP }}
|
||||
username: ${{ secrets.UL_HOST15_USER }}
|
||||
password: ${{ secrets.UL_HOST15_PASSWORD }}
|
||||
port: ${{ secrets.UL_HOST15_PORT }}
|
||||
source: "${{ env.PACKAGE_NAME }},docker-compose.yaml"
|
||||
target: "${{ env.REMOTE_APP_DIR }}/incoming"
|
||||
overwrite: true
|
||||
|
||||
- name: 远端解压并启动
|
||||
uses: appleboy/ssh-action@v0.1.10
|
||||
env:
|
||||
REMOTE_APP_DIR: ${{ env.REMOTE_APP_DIR }}
|
||||
PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
|
||||
COMPOSE_PROJECT_NAME: ${{ env.COMPOSE_PROJECT_NAME }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
host: ${{ secrets.UL_HOST15_IP }}
|
||||
username: ${{ secrets.UL_HOST15_USER }}
|
||||
password: ${{ secrets.UL_HOST15_PASSWORD }}
|
||||
port: ${{ secrets.UL_HOST15_PORT }}
|
||||
timeout: 120s
|
||||
command_timeout: 60m
|
||||
envs: REMOTE_APP_DIR,PACKAGE_NAME,COMPOSE_PROJECT_NAME,GITHUB_RUN_ID
|
||||
script: |
|
||||
set -euo pipefail
|
||||
|
||||
RELEASE_DIR="${REMOTE_APP_DIR}/releases/${GITHUB_RUN_ID}"
|
||||
PACKAGE_PATH="${REMOTE_APP_DIR}/incoming/${PACKAGE_NAME}"
|
||||
COMPOSE_PATH="${REMOTE_APP_DIR}/incoming/docker-compose.yaml"
|
||||
|
||||
rm -rf "${RELEASE_DIR}"
|
||||
mkdir -p "${RELEASE_DIR}"
|
||||
|
||||
tar -xzf "${PACKAGE_PATH}" -C "${RELEASE_DIR}"
|
||||
|
||||
if [ -f "${COMPOSE_PATH}" ]; then
|
||||
cp -f "${COMPOSE_PATH}" "${RELEASE_DIR}/docker-compose.yaml"
|
||||
fi
|
||||
|
||||
cd "${RELEASE_DIR}"
|
||||
export COMPOSE_PROJECT_NAME
|
||||
|
||||
docker compose down || true
|
||||
docker compose up -d --build --remove-orphans
|
||||
|
||||
docker compose ps
|
||||
docker compose exec -T ulthon_admin test -f /var/www/html/vendor/autoload.php
|
||||
docker compose exec -T ulthon_admin php -r "require '/var/www/html/vendor/autoload.php'; echo 'autoload-ok'.PHP_EOL;"
|
||||
docker compose logs --tail=80 ulthon_admin
|
||||
|
||||
ln -sfn "${RELEASE_DIR}" "${REMOTE_APP_DIR}/current"
|
||||
|
||||
ls -1dt "${REMOTE_APP_DIR}/releases"/* 2>/dev/null | tail -n +6 | xargs -r rm -rf
|
||||
40
.gitignore
vendored
40
.gitignore
vendored
@@ -3,10 +3,26 @@
|
||||
*.log
|
||||
.env
|
||||
config/install/lock/install.lock
|
||||
public/upload
|
||||
# 忽略 storage 目录下的所有内容
|
||||
public/storage/*
|
||||
# 但保留 .gitkeep 文件和必要的子目录
|
||||
!public/storage/.gitkeep
|
||||
!public/storage/upload/
|
||||
!public/storage/build/
|
||||
|
||||
# 忽略子目录下的所有内容
|
||||
public/storage/upload/*
|
||||
public/storage/build/*
|
||||
|
||||
# 但保留子目录中的 .gitkeep 文件
|
||||
!public/storage/upload/.gitkeep
|
||||
!public/storage/build/.gitkeep
|
||||
|
||||
public/docs
|
||||
public/conf
|
||||
public/WowOss.exe
|
||||
build/*
|
||||
!build/.gitkeep
|
||||
app/index/controller/Test.php
|
||||
composer.phar
|
||||
app/test/
|
||||
@@ -16,6 +32,22 @@ ul.db
|
||||
/app/tools/controller/Install.php
|
||||
/app/common/command/curd/migrate_output.php
|
||||
/dist
|
||||
/composer.lock
|
||||
/public/storage
|
||||
/build
|
||||
|
||||
/build
|
||||
/.VSCodeCounter
|
||||
result.txt
|
||||
test.php
|
||||
/.env.prod
|
||||
extend/base/common/command/curd/migrate_output.php
|
||||
|
||||
# source 目录:多端工程依赖与构建产物
|
||||
/source/**/node_modules
|
||||
/source/**/dist
|
||||
/source/**/.hbuilderx
|
||||
/source/**/.idea
|
||||
/source/**/.vscode
|
||||
.trae/documents/
|
||||
/docker-dev/
|
||||
/.sisyphus
|
||||
/.omo
|
||||
/.playwright-mcp
|
||||
@@ -127,6 +127,7 @@ return (new PhpCsFixer\Config())
|
||||
],
|
||||
'whitespace_after_comma_in_array' => true,
|
||||
'no_unused_imports' => true,
|
||||
'method_chaining_indentation' => true,
|
||||
|
||||
])
|
||||
// ->setIndent("\t")
|
||||
|
||||
153
AGENTS.md
Normal file
153
AGENTS.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# 智能体协作规范(Ulthon Admin)
|
||||
|
||||
- `AGENTS.md`(本文件):协作规则入口与导航
|
||||
- `.agents/`:工作流(skills)、零散规则(rules)与补充文档
|
||||
- `.agents/PROJECT.md`:项目业务总览(定位、核心模块、业务约束),由开发者按项目实际情况编写,智能体首次接触项目时应主动读取
|
||||
- `.agents/rules/`:模块级/场景级零散规则,按文件独立存放(详见「零散规则」章节)
|
||||
- `.agents/skills/`:按场景调用的工作流技能,统一以 `*/SKILL.md` 为准(详见「工作流」章节)
|
||||
- 标准开发流程:见「标准开发流程」表格,操作细节见对应技能文件
|
||||
|
||||
|
||||
## 项目级规则(最高优先级 / 唯一权威)
|
||||
|
||||
与其他文档、聊天内容、或智能体建议冲突时,一律以本节为准。
|
||||
除非明确要求,按照框架使用者的规则进行开发。
|
||||
|
||||
### 通用基础规范(所有开发者必须遵守)
|
||||
|
||||
- 技术栈:ThinkPHP 8.x;PHP 8+;MySQL 8+;Layui 2.x;模板引擎:ThinkPHP 内置模板引擎
|
||||
- 命名规范(必读):[ulthon-naming-convention.md](./.agents/rules/ulthon-naming-convention.md)
|
||||
- 代码风格(必读):遵循项目根目录 `.php-cs-fixer.php`
|
||||
- 表结构设计规范(必读):[ulthon-database-design.md](./.agents/rules/ulthon-database-design.md)
|
||||
- 项目文档主页(在线):<https://doc.ulthon.com/read/augushong/ulthon_admin/home/zh-cn/2.x.html>
|
||||
|
||||
### 代码分层铁律(不可协商)
|
||||
|
||||
框架采用 Base/App 双层架构。根据当前任务的开发者身份,遵守对应约束:
|
||||
|
||||
**所有身份通用**:
|
||||
- `extend/base/` 是框架内核(`*Base`),`app/` 是业务代码入口,目录路径一一对应
|
||||
|
||||
**框架使用者(默认身份,除非明确说"修内核/改框架")**:
|
||||
- 严禁修改 `extend/base/` 下任何文件
|
||||
- 所有开发只在 `app/` 目录进行;新写业务能力直接在 `app/` 下实现即可
|
||||
- 扩展内置能力时,只改 `app/` 下对应入口类(必要时 `extends` 对应 `*Base`),保持方法签名一致
|
||||
|
||||
**框架作者(仅当任务涉及 `extend/base/` 时)**:
|
||||
- 每新增一个 Base 类,必须同时在 `app/` 提供入口类(可为空类继承 Base)
|
||||
- Base 内部禁止直接引用/调用 `extend/base/` 下的类(含内部互调),必须引用 `app/` 下的入口类
|
||||
- 例外:`app/common.php` 引用 `extend/base/helper.php` 是全局辅助函数的唯一特许
|
||||
- 核心层维护原则:稳定性优先(保证向下兼容);通用性优先(不引入具体业务逻辑)
|
||||
|
||||
详细架构说明与操作流程:[ulthon-base-app-architecture](./.agents/skills/ulthon-base-app-architecture/SKILL.md)
|
||||
|
||||
### 其他规则
|
||||
|
||||
- 数据库:表结构优先 Scheme(`app/admin/scheme/`);避免 ENUM;tools:db 用于调试,不用于"设计表结构"
|
||||
- 前端:视图与脚本同名配对(`*.html` + 同名 `*.js`),并按模块维护 `_common.js`
|
||||
- 权限:基于 `auth` 注解生成节点与鉴权;以角色为中心管理(角色、角色权限、用户角色);命令行使用见技能:[ulthon-permission-cli](./.agents/skills/ulthon-permission-cli/SKILL.md)
|
||||
- 临时文件:智能体在任务中产生的临时文件(脚本、日志、缓存、产物等)统一输出到 `runtime/agents/`(可按智能体/任务再分子目录),不要放在仓库根目录;除非任务明确要求或框架约定位置属于根目录
|
||||
- 调试与验证:优先使用框架内置命令行工具(tools:http:call、tools:db:*、tools:log:*、admin:menu:*、admin:permission:*),不需要借助外部数据库 MCP 或临时脚本
|
||||
|
||||
### 标准开发流程(Scheme + CURD,默认必须执行)
|
||||
|
||||
> 完整操作细节见对应技能文件,本节仅列出流程骨架与核心约束。
|
||||
|
||||
| 步骤 | 说明 | 对应技能 |
|
||||
|------|------|----------|
|
||||
| 1. 初始化环境 | 依赖安装、`.env` 配置、数据库初始化、确保 `php think` 可用 | - |
|
||||
| 2. 设计表结构 | 在 `app/admin/scheme/` 中编写 Scheme 类;遵循表结构设计规范 | [ulthon-scheme-definition](./.agents/skills/ulthon-scheme-definition/SKILL.md) |
|
||||
| 3. 同步 Scheme 与 DB | `scheme:sync`(以代码为准)或 `scheme:make`(以 DB 为准);**CURD 生成前两者必须一致** | [ulthon-scheme-curd-workflow](./.agents/skills/ulthon-scheme-curd-workflow/SKILL.md) |
|
||||
| 4. 生成 CURD 代码 | `curd -t {table}`;后续字段变更可安全重新生成(`-r` 临时目录对比合并,不覆盖已改造的业务代码) | [ulthon-scheme-curd-workflow](./.agents/skills/ulthon-scheme-curd-workflow/SKILL.md) |
|
||||
| 5. 业务定制 | 仅改 `app/`;逐页调整按钮/字段/表单/交互;接口需求用页面接口同体机制 | [ulthon-page-api-dual-mode](./.agents/skills/ulthon-page-api-dual-mode/SKILL.md) |
|
||||
| 6. 功能验证 | `tools:http:call` 模拟请求跑通增删改查;`tools:log:*` 排查异常 | [ulthon-tools-http-call](./.agents/skills/ulthon-tools-http-call/SKILL.md) |
|
||||
| 7. 功能配套 | 菜单(`admin:menu:*`)、权限节点(`admin:permission:nodes`)、角色分配 | [ulthon-admin-menu-cli](./.agents/skills/ulthon-admin-menu-cli/SKILL.md)、[ulthon-permission-cli](./.agents/skills/ulthon-permission-cli/SKILL.md) |
|
||||
| 8. 最终检查 | 页面路径可用、命令行验证通过、代码规范自查 | - |
|
||||
|
||||
## 规则维护机制
|
||||
|
||||
- **框架基础规则**:`AGENTS.md`(本文件)+ `.agents/rules/ulthon-*` 为唯一权威;默认不随任务动态增长
|
||||
- **使用者补充规则**:`.agents/PROJECT.md` + `.agents/rules/project-*`,由开发者维护
|
||||
- **维护约束**:框架作者新增/调整规则前须与开发者确认;使用者身份发现需记录的约束时,优先记录到对应位置
|
||||
- **管理技能**:[ulthon-rules-manager](./.agents/skills/ulthon-rules-manager/SKILL.md)
|
||||
|
||||
## 零散规则
|
||||
|
||||
模块级/场景级的专属知识(约束、约定、设计决策),不适合在全局展开,存放在 `.agents/rules/` 目录下,每条规则一个独立文件。
|
||||
|
||||
- 命名约定:`ulthon-` 前缀为框架内置规则,`project-` 前缀为使用者业务规则
|
||||
|
||||
### 框架内置规则索引
|
||||
|
||||
| 规则文件 | 作用域 | 说明 |
|
||||
|---------|--------|------|
|
||||
| [ulthon-naming-convention.md](./.agents/rules/ulthon-naming-convention.md) | 命名规范 | 目录命名与 PHP 文件命名约定 |
|
||||
| [ulthon-database-design.md](./.agents/rules/ulthon-database-design.md) | 数据库设计 | 表结构设计:特殊字段、字段后缀、注释语法、类型大全 |
|
||||
| [ulthon-controller-url.md](./.agents/rules/ulthon-controller-url.md) | 控制器路由 | URL 与控制器/方法的映射规则 |
|
||||
| [ulthon-deploy-environment.md](./.agents/rules/ulthon-deploy-environment.md) | 部署与命令执行 | 部署栈模式与 Docker/宿主机命令判断 |
|
||||
| [ulthon-source-directory.md](./.agents/rules/ulthon-source-directory.md) | source/ 目录 | 子项目/多端代码的目录约定与安全要求 |
|
||||
| [ulthon-timer-multi-node.md](./.agents/rules/ulthon-timer-multi-node.md) | 定时任务相关 | 多节点协调规则与设计 |
|
||||
|
||||
> 使用者业务规则索引见 `.agents/PROJECT.md` 的「规则索引」章节。
|
||||
|
||||
## 工作流(Skills)
|
||||
|
||||
Skills 是"按场景调用的工作流说明",统一以 `.agents/skills/*/SKILL.md` 为准。
|
||||
|
||||
|
||||
- 技能命名约定:`ulthon-` 前缀为框架内置技能,`project-` 前缀为项目业务技能。新增业务技能时使用 `project-` 前缀。
|
||||
- Base/App 架构与扩展指南(含身份分章节):[ulthon-base-app-architecture](./.agents/skills/ulthon-base-app-architecture/SKILL.md)
|
||||
- Scheme + CURD 工作流:[ulthon-scheme-curd-workflow](./.agents/skills/ulthon-scheme-curd-workflow/SKILL.md)
|
||||
- Scheme 定义指南:[ulthon-scheme-definition](./.agents/skills/ulthon-scheme-definition/SKILL.md)
|
||||
- 数据库调试命令(tools:db):[ulthon-db-tools-debug](./.agents/skills/ulthon-db-tools-debug/SKILL.md)
|
||||
- HTTP 调用工具(tools:http:call):[ulthon-tools-http-call](./.agents/skills/ulthon-tools-http-call/SKILL.md)
|
||||
- 日志命令(tools:log):[ulthon-tools-log](./.agents/skills/ulthon-tools-log/SKILL.md)
|
||||
- 内置定时器与定时任务扩展:[ulthon-timer](./.agents/skills/ulthon-timer/SKILL.md)
|
||||
- 页面 / 接口同体:[ulthon-page-api-dual-mode](./.agents/skills/ulthon-page-api-dual-mode/SKILL.md)
|
||||
- 登录认证(Session + Token):[ulthon-auth-session-token](./.agents/skills/ulthon-auth-session-token/SKILL.md)
|
||||
- 权限与角色管理(RBAC CLI):[ulthon-permission-cli](./.agents/skills/ulthon-permission-cli/SKILL.md)
|
||||
- 菜单管理(admin:menu:\* CLI):[ulthon-admin-menu-cli](./.agents/skills/ulthon-admin-menu-cli/SKILL.md)
|
||||
|
||||
## 智能体指导
|
||||
|
||||
使用命令可以将内置智能体规则应用到各类智能体中。设计规则时以 AGENTS.md 和 `.agents/` 目录为主。
|
||||
|
||||
php think tools:agent:publish
|
||||
|
||||
## 项目结构速览
|
||||
|
||||
```
|
||||
ulthon_admin/
|
||||
├── app/ # 应用层(业务代码唯一入口)
|
||||
│ ├── admin/ # 后台管理模块(控制器、模型、视图、Scheme)
|
||||
│ ├── common/ # 公共代码(命令、服务、工具类)
|
||||
│ └── tools/ # 工具控制器(定时任务等)
|
||||
├── extend/
|
||||
│ ├── base/ # 框架内核(*Base.php,禁止业务修改)
|
||||
│ └── think/ # ThinkPHP 扩展(存储驱动、日志、迁移)
|
||||
├── config/ # ThinkPHP 配置
|
||||
├── public/ # Web 入口 + 静态资源
|
||||
├── view/ # 视图覆盖层
|
||||
├── route/ # 路由定义
|
||||
├── database/ # 数据库迁移与种子
|
||||
├── source/ # 主工程外内容统一目录(多端代码/资料/附件/各类子项目)
|
||||
│ └── clients/uniapp/ # uni-app 前端工程
|
||||
└── .agents/ # 智能体协作资源
|
||||
├── skills/ # 工作流技能(按场景调用)
|
||||
├── rules/ # 零散规则(按模块/场景拆分)
|
||||
└── PROJECT.md # 项目业务总览与规则索引
|
||||
```
|
||||
|
||||
## 快速命令参考
|
||||
|
||||
| 场景 | 命令 |
|
||||
| ---------------- | --------------------------------------- |
|
||||
| 同步表结构(代码→DB) | `php think scheme:sync` |
|
||||
| 生成 Scheme(DB→代码) | `php think scheme:make -t {table}` |
|
||||
| CURD 代码生成 | `php think curd -t {table}` |
|
||||
| 模拟 HTTP 请求 | `php think tools:http:call` |
|
||||
| 查看日志 | `php think tools:log:show` |
|
||||
| 数据库查询 | `php think tools:db:query "SELECT ..."` |
|
||||
| 权限节点生成 | `php think admin:permission:nodes` |
|
||||
| 菜单管理 | `php think admin:menu:*` |
|
||||
| 框架更新 | `php think admin:update` |
|
||||
@@ -4,7 +4,7 @@ For a better development experience.
|
||||
|
||||
Only for `developers' services, only for `demand customization' services.
|
||||
|
||||
A rapidly developed backend management system based on ThinkPHP6.1 and layui2.8.
|
||||
A rapidly developed backend management system based on ThinkPHP and Layui 2.10.
|
||||
|
||||
Technical exchange QQ group: [207160418](https://jq.qq.com/?_wv=1027&k=TULvsosz)
|
||||
|
||||
|
||||
131
README.md
131
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
只为`开发人员`服务,只为`需求定制`服务。
|
||||
|
||||
基于ThinkPHP6.1和layui2.8的快速开发的后台管理系统。
|
||||
基于 ThinkPHP 和 Layui 2.10 的快速开发的后台管理系统。*自定义扩展架构,支持自动更新上游框架代码。*
|
||||
|
||||
技术交流QQ群:[207160418](https://jq.qq.com/?_wv=1027&k=TULvsosz)
|
||||
|
||||
@@ -58,6 +58,8 @@ php think run
|
||||
|
||||
> 这个安装方式对开发体验非常友好
|
||||
|
||||
> 安装后,建议将`composer.lock`从`.gitignore`去掉,避免分发带来的问题。
|
||||
|
||||
### ~~下载完整包~~
|
||||
|
||||
完整包下载方式更新中。
|
||||
@@ -71,8 +73,20 @@ php think run
|
||||
|
||||
[如何在线上安装数据库](https://doc.ulthon.com/read/augushong/ulthon_admin/online_install.html)
|
||||
|
||||
### 更新
|
||||
|
||||
直接运行命令,即可。
|
||||
```
|
||||
php think admin:update
|
||||
```
|
||||
|
||||
> 注意:必须使用git管理您的代码,在更新时尽量您自己的所有改动提交,然后您可以对比更新的文件。
|
||||
|
||||
上游框架经常会增加新特性和修复细节问题,使用该命令会很方便的同步代码。
|
||||
|
||||
## 为什么要选择ulthon_admin
|
||||
|
||||
- 扩展架构,自动更新
|
||||
- 不搞`插件生态`和`应用市场`,没有历史包袱和开发包袱
|
||||
- **保持最新技术栈和开发思想**
|
||||
- **不断完善开发体验**
|
||||
@@ -153,7 +167,7 @@ php think run
|
||||
|
||||
以后每当实现一个新特性则发布一个tag。
|
||||
|
||||
> tag的主要意义是方便查询文档,比较差异。(ulthon_admin本身是为了定制,不会强制更新)
|
||||
> tag的主要意义是更新底层代码。如果您只改动了app和config下的文件,那么一般情况下,可以通过命令随便更新底层框架。
|
||||
|
||||
## 开源协议
|
||||
|
||||
@@ -161,7 +175,7 @@ php think run
|
||||
|
||||
## 是什么
|
||||
|
||||
`tp6后台`,`thinkphp6后台`,`layui后台`,`curd后台`
|
||||
`tp8后台`,`thinkphp8后台`,`layui后台`,`curd后台`
|
||||
|
||||
## 皮肤预览
|
||||
|
||||
@@ -180,9 +194,9 @@ php think run
|
||||
感受到来自gnome的恐惧了吗?一个“兼容Linux”的后台框架。
|
||||

|
||||
|
||||
## 开发依赖
|
||||
# 开发依赖
|
||||
|
||||
### 基本环境
|
||||
## 基本环境
|
||||
|
||||
只需要最基础的PHP开发环境即可。
|
||||
|
||||
@@ -194,7 +208,7 @@ php think run
|
||||
|
||||
> 实际上,如果你使用sqlite开发,连mysql都不想要安装,但是sqlite并不能很好地调整数据表和列,所以一般使用mysql等常规数据库。
|
||||
|
||||
### SASS
|
||||
## SASS
|
||||
|
||||
框架中部分底层组件使用了SASS特性,但一般不需要关心,如果使用vscode,可参考以下内容:
|
||||
|
||||
@@ -207,7 +221,7 @@ ID: glenn2223.live-sass
|
||||
VS Marketplace 链接: https://marketplace.visualstudio.com/items?itemName=glenn2223.live-sass
|
||||
```
|
||||
|
||||
### 配置
|
||||
## 配置
|
||||
|
||||
vscode中liveSassCompiler的配置:
|
||||
|
||||
@@ -215,9 +229,112 @@ vscode中liveSassCompiler的配置:
|
||||
{
|
||||
"liveSassCompile.settings.includeItems": [
|
||||
"/public/static/common/css/theme/*.scss",
|
||||
"/public/static/plugs/lay-module/mapLocation/mapLocation.scss",
|
||||
"/public/static/plugs/lay-module/tableData/tableData.scss",
|
||||
"/public/static/plugs/lay-module/tagInput/tagInput.scss",
|
||||
"/public/static/plugs/lay-module/propertyInput/propertyInput.scss"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# docker
|
||||
|
||||
## 打包镜像
|
||||
|
||||
下面给出的命令指定了标签名,可自行替换。
|
||||
使用以下命令打包时,相同的名称不会覆盖,名称会被新的镜像占用,因此用第二行以时间为版本名的方式,调试起来更方面。
|
||||
|
||||
```
|
||||
docker build -t ulthon/ulthon_admin:v1 .
|
||||
docker build -t ulthon/ulthon_admin:202404071454 .
|
||||
```
|
||||
|
||||
## 运行镜像
|
||||
|
||||
### docker命令正式运行
|
||||
下面的命令中为容器指定了名字,可自行替换。
|
||||
相同名称不能重复运行,所以指定名字是个好习惯,否则docker会自动起名。
|
||||
|
||||
|
||||
```
|
||||
docker run -d \
|
||||
--restart=always \
|
||||
-p 8000:8000 \
|
||||
-v /data/ulthon_admin/runtime:/var/www/html/runtime \
|
||||
-v /data/ulthon_admin/storage:/var/www/html/public/storage \
|
||||
-v /data/ulthon_admin/build:/var/www/html/public/build \
|
||||
-v /data/ulthon_admin/safe_storage:/var/www/html/storage \
|
||||
--name ulthon_admin ulthon/ulthon_admin:202404071454 \
|
||||
server
|
||||
```
|
||||
|
||||
- 端口:容器内和外部默认均为8000端口,冒号左侧可自定义,修改为本机可用的一个端口。
|
||||
- runtime:需要映射runtime目录,冒号左侧可自定义,修改为指定的目录,推荐设置,因为缓存、日志等文件都保存在这个目录下,不指定的话,重启丢失。
|
||||
- public/storage:框架默认的上传文件存储在public/storage下,因此必须映射该目录,如果系统不需要上传,则可以不设置。
|
||||
- public/build:框架默认的生成文件(比如海报、二维码)存储在public/build下,推荐映射,否则重启后丢失。
|
||||
- storage:框架设计了一个安全存储的位置,相对于storage在public下,storage在项目根目录下,因此无法直接请求到,相对安全,此时只能通过相关支持认证的控制器访问,因此更安全,可以用于存储身份证等敏感信息,但此机制大多数项目用不到,如果用到了必须要设置。
|
||||
- 后端运行,`-d`参数可以让docker在后端运行。
|
||||
|
||||
如果有其他指定的目录,可在Dockerfile中自行添加,在命令中映射。
|
||||
|
||||
> 如果不映射目录,镜像不会出错,但重启后丢失。
|
||||
|
||||
### docker compose 正式运行
|
||||
|
||||
如果和代码一起分发,那么直接运行即可。会自动构建docker镜像并映射当前目录。
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
建议提前构建好镜像,使用镜像名启动,此时需要映射指定的目录,但不需要分发代码了。
|
||||
|
||||
### docker开发运行
|
||||
|
||||
需要提前构建镜像,比如镜像名为:`ulthon/ulthon_admin:v1`。
|
||||
然后运行:
|
||||
```bash
|
||||
# 默认行为
|
||||
docker run --rm -it --name ulthon_admin_instance -p 8000:8000 -v ./:/var/www/html/ ulthon/ulthon_admin:v1
|
||||
# 运行指定命令
|
||||
docker run --rm -it --name ulthon_admin_instance -p 8000:8000 -v ./:/var/www/html/ ulthon/ulthon_admin:v1 think
|
||||
```
|
||||
要注意该命令不能通过crtl+c终止。
|
||||
|
||||
|
||||
## 基本用法
|
||||
|
||||
内置了docker打包命令,打包出的镜像支持两种调用方法:
|
||||
|
||||
- server或空
|
||||
- think
|
||||
|
||||
有两个基本的用法,运行镜像后,如果在最后增加任何参数或者增加server,会启用镜像中的php-fpm和nginx,使用`-d`后自动运行到后台,相当于框架一键运行。
|
||||
|
||||
如果以think开始传参,则是调用think命令,比如`docker run xxxxx think admin:version`,是调用内置命令。
|
||||
|
||||
> 实际上可以不以think开始,也可以是其他路径的php文件,但这里不过多讨论
|
||||
|
||||
## 其他常用命令
|
||||
|
||||
```bash
|
||||
# 一键删除停止的容器
|
||||
docker rm $(docker ps -a -q)
|
||||
# 导出镜像
|
||||
docker save e950efa97445 -o ulthon_admin.tar ulthon/ulthon_admin:202404071454
|
||||
# 导入镜像
|
||||
docker load -i ulthon_admin.tar
|
||||
# 删除指定名称的镜像
|
||||
docker images | grep ulthon_admin | awk '{print $3}' | xargs docker rmi
|
||||
# 停止指定名称的容器
|
||||
docker stop ulthon_admin
|
||||
# 删除指定名称的容器
|
||||
docker remove ulthon_admin
|
||||
# 开启交互终端
|
||||
docker exec -it ulthon_admin /bin/bash
|
||||
# 删除没有运行的镜像
|
||||
docker image prune -a
|
||||
# 移除卷
|
||||
docker volume prune
|
||||
# 移除所有卷
|
||||
docker system prune
|
||||
```
|
||||
@@ -4,97 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace app;
|
||||
|
||||
use think\App;
|
||||
use think\exception\ValidateException;
|
||||
use think\facade\Log;
|
||||
use think\Validate;
|
||||
use base\BaseControllerBase;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
* 控制器基础类.
|
||||
*/
|
||||
abstract class BaseController
|
||||
abstract class BaseController extends BaseControllerBase
|
||||
{
|
||||
/**
|
||||
* Request实例
|
||||
* @var \think\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* 应用实例
|
||||
* @var \think\App
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* 是否批量验证
|
||||
* @var bool
|
||||
*/
|
||||
protected $batchValidate = false;
|
||||
|
||||
/**
|
||||
* 控制器中间件
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [];
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @access public
|
||||
* @param App $app 应用对象
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->request = $this->app->request;
|
||||
|
||||
// 控制器初始化
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
protected function initialize()
|
||||
{
|
||||
|
||||
Log::debug('request url :' . $this->request->url());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
* @access protected
|
||||
* @param array $data 数据
|
||||
* @param string|array $validate 验证器名或者验证规则数组
|
||||
* @param array $message 提示信息
|
||||
* @param bool $batch 是否批量验证
|
||||
* @return array|string|true
|
||||
* @throws ValidateException
|
||||
*/
|
||||
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
|
||||
{
|
||||
if (is_array($validate)) {
|
||||
$v = new Validate();
|
||||
$v->rule($validate);
|
||||
} else if ($validate instanceof Validate) {
|
||||
$v = $validate;
|
||||
} else {
|
||||
if (strpos($validate, '.')) {
|
||||
// 支持场景
|
||||
list($validate, $scene) = explode('.', $validate);
|
||||
}
|
||||
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
|
||||
$v = new $class();
|
||||
if (!empty($scene)) {
|
||||
$v->scene($scene);
|
||||
}
|
||||
}
|
||||
|
||||
$v->message($message);
|
||||
|
||||
// 是否批量验证
|
||||
if ($batch || $this->batchValidate) {
|
||||
$v->batch(true);
|
||||
}
|
||||
|
||||
return $v->failException(true)->check($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
// !当前文件的内容应当与 /extend/base/admin/config/admin.php的内容保持一直,然后再根据实际情况设置
|
||||
|
||||
use think\facade\Env;
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 应用设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Env;
|
||||
|
||||
return [
|
||||
|
||||
];
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 控制台配置
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// 指令定义
|
||||
'commands' => [
|
||||
|
||||
],
|
||||
];
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 路由设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
|
||||
// 路由中间件
|
||||
'middleware' => [
|
||||
|
||||
],
|
||||
];
|
||||
@@ -1,146 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\SystemUploadfile;
|
||||
use app\common\controller\AdminController;
|
||||
use app\common\service\MenuService;
|
||||
use app\common\service\UploadService;
|
||||
use think\db\Query;
|
||||
use think\facade\Cache;
|
||||
use base\admin\controller\AjaxBase;
|
||||
|
||||
|
||||
class Ajax extends AdminController
|
||||
class Ajax extends AjaxBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 初始化后台接口地址
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function initAdmin()
|
||||
{
|
||||
$cacheData = Cache::get('initAdmin_' . session('admin.id'));
|
||||
if (!empty($cacheData)) {
|
||||
return json($cacheData);
|
||||
}
|
||||
$menuService = new MenuService(session('admin.id'));
|
||||
$data = [
|
||||
'logoInfo' => [
|
||||
'title' => sysconfig('site', 'logo_title'),
|
||||
'image' => sysconfig('site', 'logo_image'),
|
||||
'href' => __url('index/index'),
|
||||
],
|
||||
'homeInfo' => $menuService->getHomeInfo(),
|
||||
'menuInfo' => $menuService->getMenuTree(),
|
||||
];
|
||||
Cache::tag('initAdmin')->set('initAdmin_' . session('admin.id'), $data);
|
||||
return json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存接口
|
||||
*/
|
||||
public function clearCache()
|
||||
{
|
||||
Cache::clear();
|
||||
$this->success('清理缓存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
public function upload()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$data = [
|
||||
'upload_type' => $this->request->post('upload_type'),
|
||||
'file' => $this->request->file('file'),
|
||||
];
|
||||
|
||||
try {
|
||||
|
||||
$upload_service = new UploadService($data['upload_type']);
|
||||
|
||||
$upload_service->validateException($data['file']);
|
||||
|
||||
$result = $upload_service->save($data['file']);
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$this->success('上传成功', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片至编辑器
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function uploadEditor()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$data = [
|
||||
'upload_type' => $this->request->post('upload_type'),
|
||||
'file' => $this->request->file('upload'),
|
||||
];
|
||||
|
||||
try {
|
||||
$upload_service = new UploadService($data['upload_type']);
|
||||
|
||||
$upload_service->validateException($data['file']);
|
||||
|
||||
$result = $upload_service->save($data['file']);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
return json([
|
||||
'error' => [
|
||||
'message' => '上传成功',
|
||||
'number' => 201,
|
||||
],
|
||||
'fileName' => '',
|
||||
'uploaded' => 1,
|
||||
'url' => $result['url'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传文件列表
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getUploadFiles()
|
||||
{
|
||||
$get = $this->request->get();
|
||||
$page = isset($get['page']) && !empty($get['page']) ? $get['page'] : 1;
|
||||
$limit = isset($get['limit']) && !empty($get['limit']) ? $get['limit'] : 10;
|
||||
$title = isset($get['title']) && !empty($get['title']) ? $get['title'] : null;
|
||||
$this->model = new SystemUploadfile();
|
||||
$count = $this->model
|
||||
->where(function (Query $query) use ($title) {
|
||||
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
|
||||
})
|
||||
->count();
|
||||
$list = $this->model
|
||||
->where(function (Query $query) use ($title) {
|
||||
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
|
||||
})
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->select();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,119 +2,8 @@
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use base\admin\controller\IndexBase;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\model\SystemQuick;
|
||||
use app\common\controller\AdminController;
|
||||
use think\App;
|
||||
use think\facade\Env;
|
||||
|
||||
class Index extends AdminController
|
||||
class Index extends IndexBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 后台主页
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return $this->fetch('', [
|
||||
'admin' => session('admin'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台欢迎页
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function welcome()
|
||||
{
|
||||
$quicks = SystemQuick::field('id,title,icon,href')
|
||||
->where(['status' => 1])
|
||||
->order('sort', 'desc')
|
||||
->autoCache('welcome_list')
|
||||
->limit(8)
|
||||
->select();
|
||||
$this->assign('quicks', $quicks);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改管理员信息
|
||||
* @return string
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function editAdmin()
|
||||
{
|
||||
$id = session('admin.id');
|
||||
$row = (new SystemAdmin())
|
||||
->withoutField('password')
|
||||
->find($id);
|
||||
empty($row) && $this->error('用户信息不存在');
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$this->isDemo && $this->error('演示环境下不允许修改');
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$save = $row
|
||||
->allowField(['head_img', 'phone', 'remark', 'update_time'])
|
||||
->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @return string
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function editPassword()
|
||||
{
|
||||
$id = session('admin.id');
|
||||
$row = (new SystemAdmin())
|
||||
->withoutField('password')
|
||||
->find($id);
|
||||
if (!$row) {
|
||||
$this->error('用户信息不存在');
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$this->isDemo && $this->error('演示环境下不允许修改');
|
||||
$rule = [
|
||||
'password|登录密码' => 'require',
|
||||
'password_again|确认密码' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if ($post['password'] != $post['password_again']) {
|
||||
$this->error('两次密码输入不一致');
|
||||
}
|
||||
|
||||
try {
|
||||
$save = $row->save([
|
||||
'password' => password($post['password']),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
if ($save) {
|
||||
$this->success('保存成功');
|
||||
} else {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,92 +2,11 @@
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\common\controller\AdminController;
|
||||
use think\captcha\facade\Captcha;
|
||||
use think\facade\Env;
|
||||
use think\facade\Event;
|
||||
use base\admin\controller\LoginBase;
|
||||
|
||||
/**
|
||||
* Class Login.
|
||||
*/
|
||||
class Login extends AdminController
|
||||
class Login extends LoginBase
|
||||
{
|
||||
/**
|
||||
* 初始化方法.
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
$action = $this->request->action();
|
||||
if (!empty(session('admin')) && !in_array($action, ['out'])) {
|
||||
$adminModuleName = config('app.admin_alias_name');
|
||||
$this->success('已登录,无需再次登录', [], __url("@{$adminModuleName}"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录.
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
$captcha = Env::get('adminsystem.captcha', 1);
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$rule = [
|
||||
'username|用户名' => 'require',
|
||||
'password|密码' => 'require',
|
||||
'keep_login|是否保持登录' => 'require',
|
||||
];
|
||||
$captcha == 1 && $rule['captcha|验证码'] = 'require|captcha';
|
||||
$this->validate($post, $rule);
|
||||
$admin = SystemAdmin::where(['username' => $post['username']])->find();
|
||||
if (empty($admin)) {
|
||||
$this->error('用户不存在');
|
||||
}
|
||||
if (password($post['password']) != $admin->password) {
|
||||
$this->error('密码输入有误');
|
||||
}
|
||||
if ($admin->status == 0) {
|
||||
$this->error('账号已被禁用');
|
||||
}
|
||||
$admin->login_num += 1;
|
||||
$admin->save();
|
||||
|
||||
Event::trigger('AdminLoginSuccess', $admin);
|
||||
|
||||
$admin = $admin->toArray();
|
||||
unset($admin['password']);
|
||||
$admin['expire_time'] = $post['keep_login'] == 1 ? true : time() + 7200;
|
||||
session('admin', $admin);
|
||||
|
||||
$this->success('登录成功');
|
||||
}
|
||||
$this->assign('captcha', $captcha);
|
||||
$this->assign('demo', $this->isDemo);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户退出.
|
||||
* @return mixed
|
||||
*/
|
||||
public function out()
|
||||
{
|
||||
session('admin', null);
|
||||
$this->success('退出登录成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function captcha()
|
||||
{
|
||||
return Captcha::create();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,12 @@
|
||||
|
||||
namespace app\admin\controller\debug;
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\debug\LogBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="debug_log")
|
||||
*/
|
||||
class Log extends AdminController
|
||||
class Log extends LogBase
|
||||
{
|
||||
|
||||
protected $sort = [
|
||||
'uid' => 'desc',
|
||||
'id' => 'asc',
|
||||
];
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->model = new \app\admin\model\DebugLog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
|
||||
use app\admin\model\MallCate;
|
||||
use app\admin\traits\Curd;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\mall\CateBase;
|
||||
|
||||
/**
|
||||
* Class Admin
|
||||
* @package app\admin\controller\system
|
||||
* @ControllerAnnotation(title="商品分类管理")
|
||||
* Class Admin.
|
||||
* @ControllerAnnotation(title="商品分类管理",module="商城")
|
||||
*/
|
||||
class Cate extends AdminController
|
||||
class Cate extends CateBase
|
||||
{
|
||||
|
||||
use Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new MallCate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,121 +2,13 @@
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\admin\model\MallCate;
|
||||
use app\admin\model\MallGoods;
|
||||
use app\admin\model\MallTag;
|
||||
use app\admin\traits\Curd;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\mall\GoodsBase;
|
||||
|
||||
/**
|
||||
* Class Goods
|
||||
* @package app\admin\controller\mall
|
||||
* @ControllerAnnotation(title="商城商品管理")
|
||||
* Class Goods.
|
||||
* @ControllerAnnotation(title="商城商品管理",module="商城")
|
||||
*/
|
||||
class Goods extends AdminController
|
||||
class Goods extends GoodsBase
|
||||
{
|
||||
use Curd;
|
||||
|
||||
protected $relationSearch = true;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new MallGoods();
|
||||
|
||||
$this->assign('select_list_cate', MallCate::select(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where) = $this->buildTableParames();
|
||||
$count = $this->model
|
||||
->withJoin('cate', 'LEFT')
|
||||
->where($where)
|
||||
->count();
|
||||
$list = $this->model
|
||||
->withJoin('cate', 'LEFT')
|
||||
->where($where)
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->select();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="入库")
|
||||
*/
|
||||
public function stock($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$post['total_stock'] = $row->total_stock + $post['stock'];
|
||||
$post['stock'] = $row->stock + $post['stock'];
|
||||
$save = $row->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
public function read($id)
|
||||
{
|
||||
|
||||
$row = $this->model->find($id);
|
||||
|
||||
$this->assign('row', $row);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="导出")
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
list($page, $limit, $where) = $this->buildTableParames();
|
||||
|
||||
$this->model = $this->model
|
||||
->withJoin('cate', 'LEFT');
|
||||
|
||||
$fields = $this->request->param('fields', '{}', null);
|
||||
$image_fields = $this->request->param('image_fields', '{}', null);
|
||||
$select_fields = $this->request->param('select_fields', '{}', null);
|
||||
$date_fields = $this->request->param('date_fields', '{}', null);
|
||||
|
||||
$fields = json_decode($fields, true);
|
||||
$image_fields = json_decode($image_fields, true);
|
||||
$select_fields = json_decode($select_fields, true);
|
||||
$date_fields = json_decode($date_fields, true);
|
||||
|
||||
$content = \app\common\tools\ExportTools::excel($this->model, $where, $fields, $image_fields, $select_fields, $date_fields);
|
||||
|
||||
return download($content, $this->model->getName() . date('YmdHis') . '.xlsx', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,26 +2,12 @@
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\mall\TagBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="mall_tag")
|
||||
* @ControllerAnnotation(title="mall_tag",module="商城")
|
||||
*/
|
||||
class Tag extends AdminController
|
||||
class Tag extends TagBase
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->model = new \app\admin\model\MallTag();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,218 +2,16 @@
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\constants\AdminConstant;
|
||||
use app\common\controller\AdminController;
|
||||
use think\App;
|
||||
use think\facade\Validate;
|
||||
use think\validate\ValidateRule;
|
||||
use base\admin\controller\system\AdminBase;
|
||||
|
||||
/**
|
||||
* Class Admin.
|
||||
* @ControllerAnnotation(title="管理员管理")
|
||||
* @ControllerAnnotation(title="管理员管理",module="系统")
|
||||
*
|
||||
* @NodeAnotation(title="自定义权限标识符",name="customFlag")
|
||||
*/
|
||||
class Admin extends AdminController
|
||||
class Admin extends AdminBase
|
||||
{
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
protected $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemAdmin();
|
||||
$this->assign('auth_list', $this->model->getAuthList(), true);
|
||||
|
||||
$this->setDataBrage('count', 10);
|
||||
$this->setDataBrage('tips', '请谨慎操作');
|
||||
|
||||
$this->setDataBrage('adminCustomFlag', $this->checkAuth('system.admin/customFlag', false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where) = $this->buildTableParames();
|
||||
$count = $this->model
|
||||
->where($where)
|
||||
->count();
|
||||
$list = $this->model
|
||||
->withoutField('password')
|
||||
->where($where)
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->select();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
|
||||
return json($data);
|
||||
}
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加")
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$authIds = $this->request->post('auth_ids', []);
|
||||
$post['auth_ids'] = implode(',', array_keys($authIds));
|
||||
$rule = Validate::rule('username|用户登录名', ValidateRule::isRequire());
|
||||
$post['password'] = password(sysconfig('site', 'site_default_password', '123456'));
|
||||
$this->validate($post, $rule);
|
||||
|
||||
try {
|
||||
$model_admin = SystemAdmin::where('username', $post['username'])->find();
|
||||
|
||||
if (!empty($model_admin)) {
|
||||
throw new \Exception('同名用户已存在');
|
||||
}
|
||||
|
||||
$save = $this->model->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败:' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$authIds = $this->request->post('auth_ids', []);
|
||||
$post['auth_ids'] = implode(',', array_keys($authIds));
|
||||
$rule = [];
|
||||
$this->validate($post, $rule);
|
||||
if (isset($row['password'])) {
|
||||
unset($row['password']);
|
||||
}
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
TriggerService::updateMenu($id);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$row->auth_ids = explode(',', $row->auth_ids);
|
||||
$this->assign('row', $row);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function password($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isAjax()) {
|
||||
$this->checkPostRequest();
|
||||
$post = $this->request->post();
|
||||
$rule = [
|
||||
'password|登录密码' => 'require',
|
||||
'password_again|确认密码' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if ($post['password'] != $post['password_again']) {
|
||||
$this->error('两次密码输入不一致');
|
||||
}
|
||||
try {
|
||||
$save = $row->save([
|
||||
'password' => password($post['password']),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$row->auth_ids = explode(',', $row->auth_ids);
|
||||
$this->assign('row', $row);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="删除")
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$row = $this->model->whereIn('id', $id)->select();
|
||||
$row->isEmpty() && $this->error('数据不存在');
|
||||
$id == AdminConstant::SUPER_ADMIN_ID && $this->error('超级管理员不允许修改');
|
||||
if (is_array($id)) {
|
||||
if (in_array(AdminConstant::SUPER_ADMIN_ID, $id)) {
|
||||
$this->error('超级管理员不允许修改');
|
||||
}
|
||||
}
|
||||
try {
|
||||
$save = $row->delete();
|
||||
} catch (\Exception $e) {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
$save ? $this->success('删除成功') : $this->error('删除失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="属性修改")
|
||||
*/
|
||||
public function modify()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$post = $this->request->post();
|
||||
$rule = [
|
||||
'id|ID' => 'require',
|
||||
'field|字段' => 'require',
|
||||
'value|值' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
if (!in_array($post['field'], $this->allowModifyFields)) {
|
||||
$this->error('该字段不允许修改:' . $post['field']);
|
||||
}
|
||||
if ($post['id'] == AdminConstant::SUPER_ADMIN_ID && $post['field'] == 'status') {
|
||||
$this->error('超级管理员状态不允许修改');
|
||||
}
|
||||
$row = $this->model->find($post['id']);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
try {
|
||||
$row->save([
|
||||
$post['field'] => $post['value'],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\system\AuthBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="角色权限管理")
|
||||
* @ControllerAnnotation(title="角色权限管理",module="系统")
|
||||
* Class Auth
|
||||
* @package app\admin\controller\system
|
||||
*/
|
||||
class Auth extends AdminController
|
||||
class Auth extends AuthBase
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
protected $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemAuth();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="授权")
|
||||
*/
|
||||
public function authorize($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isAjax()) {
|
||||
$list = $this->model->getAuthorizeNodeListByAdminId($id);
|
||||
$this->success('获取成功', $list);
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="授权保存")
|
||||
*/
|
||||
public function saveAuthorize()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$id = $this->request->post('id');
|
||||
$node = $this->request->post('node', "[]");
|
||||
$node = json_decode($node, true);
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
try {
|
||||
$authNode = new SystemAuthNode();
|
||||
$authNode->where('auth_id', $id)->delete();
|
||||
if (!empty($node)) {
|
||||
$saveAll = [];
|
||||
foreach ($node as $vo) {
|
||||
$saveAll[] = [
|
||||
'auth_id' => $id,
|
||||
'node_id' => $vo,
|
||||
];
|
||||
}
|
||||
$authNode->saveAll($saveAll);
|
||||
}
|
||||
TriggerService::updateMenu();
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
|
||||
use app\admin\model\SystemConfig;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\system\ConfigBase;
|
||||
|
||||
/**
|
||||
* Class Config
|
||||
* @package app\admin\controller\system
|
||||
* @ControllerAnnotation(title="系统配置管理")
|
||||
* Class Config.
|
||||
*/
|
||||
class Config extends AdminController
|
||||
class Config extends ConfigBase
|
||||
{
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="保存")
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
|
||||
$post = $this->request->except(['group_name'], 'post');
|
||||
|
||||
$group_name = $this->request->post('group_name');
|
||||
|
||||
try {
|
||||
foreach ($post as $key => $val) {
|
||||
|
||||
if (empty($group_name)) {
|
||||
|
||||
$this->model
|
||||
->where('name', $key)
|
||||
->update([
|
||||
'value' => $val,
|
||||
]);
|
||||
} else {
|
||||
$model_config = SystemConfig::where('group', $group_name)
|
||||
->where('name', $key)
|
||||
->find();
|
||||
|
||||
if (empty($model_config)) {
|
||||
$model_config = SystemConfig::create([
|
||||
'group' => $group_name,
|
||||
'name' => $key,
|
||||
'value' => $val
|
||||
]);
|
||||
}
|
||||
|
||||
$model_config->save([
|
||||
'value' => $val
|
||||
]);
|
||||
}
|
||||
}
|
||||
TriggerService::updateMenu();
|
||||
TriggerService::updateSysconfig();
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
$this->success('保存成功');
|
||||
}
|
||||
}
|
||||
|
||||
22
app/admin/controller/system/Host.php
Normal file
22
app/admin/controller/system/Host.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use base\admin\controller\system\HostBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="system_host",module="系统")
|
||||
*/
|
||||
class Host extends HostBase
|
||||
{
|
||||
/**
|
||||
* 设置主节点.
|
||||
*
|
||||
* @\app\admin\service\annotation\NodeAnotation(title="设置主节点")
|
||||
*/
|
||||
public function setMaster()
|
||||
{
|
||||
return parent::setMaster();
|
||||
}
|
||||
}
|
||||
@@ -1,217 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\admin\model\SystemNode;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\constants\MenuConstant;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use app\common\controller\AdminController;
|
||||
use think\App;
|
||||
use base\admin\controller\system\MenuBase;
|
||||
|
||||
/**
|
||||
* Class Menu
|
||||
* @package app\admin\controller\system
|
||||
* @ControllerAnnotation(title="菜单管理",auth=true)
|
||||
* Class Menu.
|
||||
* @ControllerAnnotation(title="菜单管理",module="系统")
|
||||
*/
|
||||
class Menu extends AdminController
|
||||
class Menu extends MenuBase
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
protected $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'asc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemMenu();
|
||||
|
||||
$this->assign('menu_home_pid', MenuConstant::HOME_PID, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
$count = $this->model->count();
|
||||
$list = $this->model->order($this->sort)->select();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加")
|
||||
*/
|
||||
public function add($id = null)
|
||||
{
|
||||
$homeId = $this->model
|
||||
->where([
|
||||
'pid' => MenuConstant::HOME_PID,
|
||||
])
|
||||
->value('id');
|
||||
if ($id == $homeId) {
|
||||
$this->error('首页不能添加子菜单');
|
||||
}
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$rule = [
|
||||
'pid|上级菜单' => 'require',
|
||||
'title|菜单名称' => 'require',
|
||||
'icon|菜单图标' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
try {
|
||||
$save = $this->model->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
if ($save) {
|
||||
TriggerService::updateMenu();
|
||||
$this->success('保存成功');
|
||||
} else {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
}
|
||||
$pidMenuList = $this->model->getPidMenuList();
|
||||
$this->assign('id', $id);
|
||||
$this->assign('pidMenuList', $pidMenuList);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
$rule = [
|
||||
'pid|上级菜单' => 'require',
|
||||
'title|菜单名称' => 'require',
|
||||
'icon|菜单图标' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
|
||||
//防止首页pid被修改而导致渲染时报错
|
||||
if ($row->pid == MenuConstant::HOME_PID) {
|
||||
unset($post['pid']);
|
||||
}
|
||||
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
if ($save) {
|
||||
TriggerService::updateMenu();
|
||||
$this->success('保存成功');
|
||||
} else {
|
||||
$this->error('保存失败');
|
||||
}
|
||||
}
|
||||
$pidMenuList = $this->model->getPidMenuList();
|
||||
$this->assign([
|
||||
'id' => $id,
|
||||
'pidMenuList' => $pidMenuList,
|
||||
'row' => $row,
|
||||
]);
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="删除")
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$row = $this->model->whereIn('id', $id)->select();
|
||||
empty($row) && $this->error('数据不存在');
|
||||
try {
|
||||
$save = $row->delete();
|
||||
} catch (\Exception $e) {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
if ($save) {
|
||||
TriggerService::updateMenu();
|
||||
$this->success('删除成功');
|
||||
} else {
|
||||
$this->error('删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="属性修改")
|
||||
*/
|
||||
public function modify()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$post = $this->request->post();
|
||||
$rule = [
|
||||
'id|ID' => 'require',
|
||||
'field|字段' => 'require',
|
||||
'value|值' => 'require',
|
||||
];
|
||||
$this->validate($post, $rule);
|
||||
$row = $this->model->find($post['id']);
|
||||
if (!$row) {
|
||||
$this->error('数据不存在');
|
||||
}
|
||||
if (!in_array($post['field'], $this->allowModifyFields)) {
|
||||
$this->error('该字段不允许修改:' . $post['field']);
|
||||
}
|
||||
$homeId = $this->model
|
||||
->where([
|
||||
'pid' => MenuConstant::HOME_PID,
|
||||
])
|
||||
->value('id');
|
||||
if ($post['id'] == $homeId && $post['field'] == 'status') {
|
||||
$this->error('首页状态不允许关闭');
|
||||
}
|
||||
try {
|
||||
$row->save([
|
||||
$post['field'] => $post['value'],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
TriggerService::updateMenu();
|
||||
$this->success('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加菜单提示")
|
||||
*/
|
||||
public function getMenuTips()
|
||||
{
|
||||
$node = input('get.keywords');
|
||||
$list = SystemNode::whereLike('node', "%{$node}%")
|
||||
->field('node,title')
|
||||
->limit(10)
|
||||
->select();
|
||||
return json([
|
||||
'code' => 0,
|
||||
'content' => $list,
|
||||
'type' => 'success',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
|
||||
use app\admin\model\SystemNode;
|
||||
use app\admin\service\TriggerService;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use app\admin\service\NodeService;
|
||||
use think\App;
|
||||
use base\admin\controller\system\NodeBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="系统节点管理")
|
||||
* @ControllerAnnotation(title="系统节点管理",module="系统")
|
||||
* Class Node
|
||||
* @package app\admin\controller\system
|
||||
*/
|
||||
class Node extends AdminController
|
||||
class Node extends NodeBase
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
$count = $this->model
|
||||
->count();
|
||||
$list = $this->model
|
||||
->getNodeTreeList();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
return json($data);
|
||||
}
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="系统节点更新")
|
||||
*/
|
||||
public function refreshNode($force = 0)
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$nodeList = (new NodeService())->getNodelist();
|
||||
empty($nodeList) && $this->error('暂无需要更新的系统节点');
|
||||
$model = new SystemNode();
|
||||
try {
|
||||
if ($force == 1) {
|
||||
$updateNodeList = $model->whereIn('node', array_column($nodeList, 'node'))->select();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($updateNodeList as $vo) {
|
||||
isset($formatNodeList[$vo['node']]) && $model->where('id', $vo['id'])->update([
|
||||
'title' => $formatNodeList[$vo['node']]['title'],
|
||||
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
$existNodeList = $model->field('node,title,type,is_auth')->select();
|
||||
foreach ($nodeList as $key => $vo) {
|
||||
foreach ($existNodeList as $v) {
|
||||
if ($vo['node'] == $v->node) {
|
||||
unset($nodeList[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$model->saveAll($nodeList);
|
||||
TriggerService::updateNode();
|
||||
} catch (\Exception $e) {
|
||||
$this->error('节点更新失败');
|
||||
}
|
||||
$this->success('节点更新成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="清除失效节点")
|
||||
*/
|
||||
public function clearNode()
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$nodeList = (new NodeService())->getNodelist();
|
||||
$model = new SystemNode();
|
||||
try {
|
||||
$existNodeList = $model->field('id,node,title,type,is_auth')->select()->toArray();
|
||||
$formatNodeList = array_format_key($nodeList, 'node');
|
||||
foreach ($existNodeList as $vo) {
|
||||
!isset($formatNodeList[$vo['node']]) && $model->where('id', $vo['id'])->delete();
|
||||
}
|
||||
TriggerService::updateNode();
|
||||
} catch (\Exception $e) {
|
||||
$this->error('节点更新失败');
|
||||
}
|
||||
$this->success('节点更新成功');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
|
||||
use app\admin\model\SystemQuick;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\system\QuickBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="快捷入口管理")
|
||||
* @ControllerAnnotation(title="快捷入口管理",module="系统")
|
||||
* Class Quick
|
||||
* @package app\admin\controller\system
|
||||
*/
|
||||
class Quick extends AdminController
|
||||
class Quick extends QuickBase
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
protected $sort = [
|
||||
'sort' => 'desc',
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemQuick();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
13
app/admin/controller/system/Status.php
Normal file
13
app/admin/controller/system/Status.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use base\admin\controller\system\StatusBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="system_status",module="系统")
|
||||
*/
|
||||
class Status extends StatusBase
|
||||
{
|
||||
}
|
||||
97
app/admin/controller/system/TimerConfig.php
Normal file
97
app/admin/controller/system/TimerConfig.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="定时任务协调配置表")
|
||||
*/
|
||||
class TimerConfig extends AdminController
|
||||
{
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->model = new \app\admin\model\SystemTimerConfig();
|
||||
|
||||
$this->assign('select_list_run_type', $this->model::SELECT_LIST_RUN_TYPE, true);
|
||||
$this->assign('select_list_status', $this->model::SELECT_LIST_STATUS, true);
|
||||
$this->assign('select_list_is_synced', $this->model::SELECT_LIST_IS_SYNCED, true);
|
||||
$this->assign('select_list_manual_trigger', $this->model::SELECT_LIST_MANUAL_TRIGGER, true);
|
||||
|
||||
// 允许通过行内修改的字段
|
||||
$this->allowModifyFields = [
|
||||
'status',
|
||||
'run_type',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加")
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->error('定时任务配置由系统同步生成,不支持手动添加');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="删除")
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->error('定时任务配置由系统管理,不支持删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
// 只允许修改 run_type 和 status
|
||||
$post = array_intersect_key($post, array_flip(['run_type', 'status']));
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败:' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="手动触发")
|
||||
*/
|
||||
public function trigger($id)
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error('数据不存在');
|
||||
}
|
||||
if ($row->getAttr('run_type') !== 'manual') {
|
||||
$this->error('只有manual类型的任务支持手动触发');
|
||||
}
|
||||
if ($row->getAttr('status') != 1) {
|
||||
$this->error('任务已停用,请先启用');
|
||||
}
|
||||
try {
|
||||
$row->save(['manual_trigger' => 1]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('触发失败:' . $e->getMessage());
|
||||
}
|
||||
$this->success('触发成功,等待定时器执行');
|
||||
}
|
||||
}
|
||||
109
app/admin/controller/system/TimerLog.php
Normal file
109
app/admin/controller/system/TimerLog.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="定时器执行日志")
|
||||
*/
|
||||
class TimerLog extends AdminController
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
protected $sort = [
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->model = new \app\admin\model\SystemTimerLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where, $excludes, $request_options, $group) = $this->buildTableParames();
|
||||
$count = $this->model
|
||||
->where($where)
|
||||
->group($group)
|
||||
->count();
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->group($group)
|
||||
->select();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
|
||||
return json($data);
|
||||
}
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="详情")
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
|
||||
$title = $row->title;
|
||||
|
||||
$this->assign('row', $row);
|
||||
$this->assign('title', $title);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加")
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->error('日志表不允许手动添加');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$this->error('日志表不允许编辑');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="删除")
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->error('日志表不允许删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="属性修改")
|
||||
*/
|
||||
public function modify()
|
||||
{
|
||||
$this->error('日志表不允许修改');
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
|
||||
use app\admin\model\SystemUploadfile;
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
use base\admin\controller\system\UploadfileBase;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="上传文件管理")
|
||||
* @ControllerAnnotation(title="上传文件管理",module="系统")
|
||||
* Class Uploadfile
|
||||
* @package app\admin\controller\system
|
||||
*/
|
||||
class Uploadfile extends AdminController
|
||||
class Uploadfile extends UploadfileBase
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SystemUploadfile();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
// !当前文件的内容应当与 /extend/base/admin/event.php的内容一致,然后根据实际情况设置
|
||||
|
||||
// 事件定义文件
|
||||
return [
|
||||
'bind' => [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<?php
|
||||
|
||||
// !当前文件的内容应当与 /extend/base/admin/middleware.php 的内容一致,然后再根据实际情况设置
|
||||
|
||||
// 全局中间件定义文件
|
||||
|
||||
use think\middleware\AllowCrossDomain;
|
||||
|
||||
return [
|
||||
|
||||
// Session初始化
|
||||
@@ -11,4 +17,6 @@ return [
|
||||
// Csrf安全校验
|
||||
\app\admin\middleware\CsrfMiddleware::class,
|
||||
|
||||
AllowCrossDomain::class,
|
||||
|
||||
];
|
||||
|
||||
@@ -2,36 +2,8 @@
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use think\Request;
|
||||
use base\admin\middleware\CsrfMiddlewareBase;
|
||||
|
||||
class CsrfMiddleware
|
||||
class CsrfMiddleware extends CsrfMiddlewareBase
|
||||
{
|
||||
use \app\common\traits\JumpTrait;
|
||||
|
||||
public function handle(Request $request, \Closure $next)
|
||||
{
|
||||
if (env('adminsystem.IS_CSRF', true)) {
|
||||
if (!in_array($request->method(), ['GET', 'HEAD', 'OPTIONS'])) {
|
||||
// 跨域校验
|
||||
$refererUrl = $request->header('REFERER', null);
|
||||
$refererInfo = parse_url($refererUrl);
|
||||
$host = $request->host(true);
|
||||
if (!isset($refererInfo['host']) || $refererInfo['host'] != $host) {
|
||||
$this->error('当前请求不合法!');
|
||||
}
|
||||
|
||||
// CSRF校验
|
||||
// @todo 兼容CK编辑器上传功能
|
||||
$ckCsrfToken = $request->post('ckCsrfToken', null);
|
||||
$data = !empty($ckCsrfToken) ? ['__token__' => $ckCsrfToken] : [];
|
||||
|
||||
$check = $request->checkToken('__token__', $data);
|
||||
if (!$check) {
|
||||
$this->error('请求验证失败,请重新刷新页面!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,55 +2,12 @@
|
||||
|
||||
namespace app\admin\middleware;
|
||||
|
||||
use think\facade\Log;
|
||||
use think\facade\Request as FacadeRequest;
|
||||
use think\Request;
|
||||
use base\admin\middleware\SystemLogBase;
|
||||
|
||||
/**
|
||||
* 系统操作日志中间件
|
||||
* Class SystemLog.
|
||||
*/
|
||||
class SystemLog
|
||||
class SystemLog extends SystemLogBase
|
||||
{
|
||||
/**
|
||||
* 敏感信息字段,日志记录时需要加密.
|
||||
* @var array
|
||||
*/
|
||||
protected $sensitiveParams = [
|
||||
'password',
|
||||
'password_again',
|
||||
'phone',
|
||||
'mobile',
|
||||
];
|
||||
|
||||
public function handle(Request $request, \Closure $next)
|
||||
{
|
||||
$params = $request->param();
|
||||
if (isset($params['s'])) {
|
||||
unset($params['s']);
|
||||
}
|
||||
foreach ($params as $key => $val) {
|
||||
in_array($key, $this->sensitiveParams) && $params[$key] = '***********';
|
||||
}
|
||||
$method = strtolower($request->method());
|
||||
$url = $request->url();
|
||||
|
||||
if ($request->isAjax()) {
|
||||
if (in_array($method, ['post', 'put', 'delete'])) {
|
||||
$ip = FacadeRequest::ip();
|
||||
$data = [
|
||||
'admin_id' => session('admin.id'),
|
||||
'url' => $url,
|
||||
'method' => $method,
|
||||
'ip' => $ip,
|
||||
'content' => json_encode($params, JSON_UNESCAPED_UNICODE),
|
||||
'useragent' => $_SERVER['HTTP_USER_AGENT'],
|
||||
'create_time' => time(),
|
||||
];
|
||||
Log::debug(print_r($data, true));
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,8 @@
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use base\admin\model\DebugLogBase;
|
||||
|
||||
class DebugLog extends TimeModel
|
||||
class DebugLog extends DebugLogBase
|
||||
{
|
||||
|
||||
protected $name = "debug_log";
|
||||
|
||||
protected $deleteTime = false;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\MallCateBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class MallCate extends TimeModel
|
||||
class MallCate extends MallCateBase
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\MallGoodsBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class MallGoods extends TimeModel
|
||||
class MallGoods extends MallGoodsBase
|
||||
{
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
public function cate()
|
||||
{
|
||||
return $this->belongsTo('app\admin\model\MallCate', 'cate_id', 'id');
|
||||
}
|
||||
|
||||
public function getTagListTitleAttr()
|
||||
{
|
||||
$tags = $this->getAttr('tag');
|
||||
|
||||
$list_tag = MallTag::whereIn('id', $tags)->column('title');
|
||||
|
||||
return $list_tag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,8 @@
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
use base\admin\model\MallTagBase;
|
||||
|
||||
class MallTag extends TimeModel
|
||||
class MallTag extends MallTagBase
|
||||
{
|
||||
|
||||
protected $name = "mall_tag";
|
||||
|
||||
protected $deleteTime = "delete_time";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemAdminBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemAdmin extends TimeModel
|
||||
class SystemAdmin extends SystemAdminBase
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
public static $autoCache = [
|
||||
[
|
||||
'name' => 'info',
|
||||
'field' => 'id'
|
||||
]
|
||||
];
|
||||
|
||||
public function getAuthList()
|
||||
{
|
||||
$list = (new SystemAuth())
|
||||
->where('status', 1)
|
||||
->column('title', 'id');
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemAuthBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemAuth extends TimeModel
|
||||
class SystemAuth extends SystemAuthBase
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
/**
|
||||
* 根据角色ID获取授权节点
|
||||
* @param $authId
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function getAuthorizeNodeListByAdminId($authId)
|
||||
{
|
||||
$checkNodeList = (new SystemAuthNode())
|
||||
->where('auth_id', $authId)
|
||||
->column('node_id');
|
||||
$systemNode = new SystemNode();
|
||||
$nodelList = $systemNode
|
||||
->where('is_auth', 1)
|
||||
->field('id,node,title,type,is_auth')
|
||||
->select()
|
||||
->toArray();
|
||||
$newNodeList = [];
|
||||
foreach ($nodelList as $vo) {
|
||||
if ($vo['type'] == 1) {
|
||||
$vo = array_merge($vo, ['field' => 'node', 'spread' => true]);
|
||||
$vo['checked'] = false;
|
||||
$vo['title'] = "{$vo['title']}【{$vo['node']}】";
|
||||
$children = [];
|
||||
foreach ($nodelList as $v) {
|
||||
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
|
||||
$v = array_merge($v, ['field' => 'node', 'spread' => true]);
|
||||
$v['checked'] = in_array($v['id'], $checkNodeList) ? true : false;
|
||||
$v['title'] = "{$v['title']}【{$v['node']}】";
|
||||
$children[] = $v;
|
||||
}
|
||||
}
|
||||
!empty($children) && $vo['children'] = $children;
|
||||
$newNodeList[] = $vo;
|
||||
}
|
||||
}
|
||||
return $newNodeList;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemAuthNodeBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemAuthNode extends TimeModel
|
||||
class SystemAuthNode extends SystemAuthNodeBase
|
||||
{
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
protected $deleteTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemConfigBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemConfig extends TimeModel
|
||||
class SystemConfig extends SystemConfigBase
|
||||
{
|
||||
protected $deleteTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
app/admin/model/SystemHost.php
Normal file
9
app/admin/model/SystemHost.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemHostBase;
|
||||
|
||||
class SystemHost extends SystemHostBase
|
||||
{
|
||||
}
|
||||
@@ -1,61 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemMenuBase;
|
||||
|
||||
|
||||
use app\common\constants\MenuConstant;
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemMenu extends TimeModel
|
||||
class SystemMenu extends SystemMenuBase
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
public function getPidMenuList()
|
||||
{
|
||||
$list = $this->field('id,pid,title')
|
||||
->where([
|
||||
['pid', '<>', MenuConstant::HOME_PID],
|
||||
['status', '=', 1],
|
||||
])
|
||||
->select()
|
||||
->toArray();
|
||||
$pidMenuList = $this->buildPidMenu(0, $list);
|
||||
$pidMenuList = array_merge([[
|
||||
'id' => 0,
|
||||
'pid' => 0,
|
||||
'title' => '顶级菜单',
|
||||
]], $pidMenuList);
|
||||
return $pidMenuList;
|
||||
}
|
||||
|
||||
protected function buildPidMenu($pid, $list, $level = 0)
|
||||
{
|
||||
$newList = [];
|
||||
foreach ($list as $vo) {
|
||||
if ($vo['pid'] == $pid) {
|
||||
$level++;
|
||||
foreach ($newList as $v) {
|
||||
if ($vo['pid'] == $v['pid'] && isset($v['level'])) {
|
||||
$level = $v['level'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$vo['level'] = $level;
|
||||
if ($level > 1) {
|
||||
$repeatString = " ";
|
||||
$markString = str_repeat("{$repeatString}├{$repeatString}", $level - 1);
|
||||
$vo['title'] = $markString . $vo['title'];
|
||||
}
|
||||
$newList[] = $vo;
|
||||
$childList = $this->buildPidMenu($vo['id'], $list, $level);
|
||||
!empty($childList) && $newList = array_merge($newList, $childList);
|
||||
}
|
||||
|
||||
}
|
||||
return $newList;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemNode extends TimeModel
|
||||
{
|
||||
|
||||
protected $deleteTime = false;
|
||||
|
||||
public function getNodeTreeList()
|
||||
{
|
||||
$list = $this->select()->toArray();
|
||||
$list = $this->buildNodeTree($list);
|
||||
return $list;
|
||||
}
|
||||
|
||||
protected function buildNodeTree($list)
|
||||
{
|
||||
$newList = [];
|
||||
$repeatString = " ";
|
||||
foreach ($list as $vo) {
|
||||
if ($vo['type'] == 1) {
|
||||
$newList[] = $vo;
|
||||
foreach ($list as $v) {
|
||||
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
|
||||
$v['node'] = "{$repeatString}├{$repeatString}" . $v['node'];
|
||||
$newList[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $newList;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemQuickBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemQuick extends TimeModel
|
||||
class SystemQuick extends SystemQuickBase
|
||||
{
|
||||
|
||||
protected $deleteTime = 'delete_time';
|
||||
|
||||
public static $autoCache = [
|
||||
[
|
||||
'name' => 'welcome_list'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
37
app/admin/model/SystemTimerConfig.php
Normal file
37
app/admin/model/SystemTimerConfig.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $task_name 任务名称
|
||||
* @property string $run_type 运行类型:main/auto/all/manual main:main,auto:auto,all:all,manual:manual
|
||||
* @property int $status 状态:0=停用,1=启用 0:停用,1:启用
|
||||
* @property int $is_synced 是否已同步:0=未同步,1=已同步 0:未同步,1:已同步
|
||||
* @property string $last_execute_node 最后执行节点ID
|
||||
* @property int $last_execute_time 最后执行时间戳
|
||||
* @property int $manual_trigger 手动触发标记:0=未触发,1=已触发 0:未触发,1:已触发
|
||||
* @property int $create_time 创建时间
|
||||
*/
|
||||
class SystemTimerConfig extends TimeModel
|
||||
{
|
||||
|
||||
protected $name = "system_timer_config";
|
||||
|
||||
protected $deleteTime = false;
|
||||
|
||||
|
||||
public const SELECT_LIST_RUN_TYPE = ['main' => 'main', 'auto' => 'auto', 'all' => 'all', 'manual' => 'manual'];
|
||||
|
||||
public const SELECT_LIST_STATUS = ['0' => '停用', '1' => '启用'];
|
||||
|
||||
public const SELECT_LIST_IS_SYNCED = ['0' => '未同步', '1' => '已同步'];
|
||||
|
||||
public const SELECT_LIST_MANUAL_TRIGGER = ['0' => '未触发', '1' => '已触发'];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
31
app/admin/model/SystemTimerLog.php
Normal file
31
app/admin/model/SystemTimerLog.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $task_name 任务名称
|
||||
* @property string $node_id 执行节点ID
|
||||
* @property string $run_type 运行类型
|
||||
* @property int $start_time 开始时间戳
|
||||
* @property int $end_time 结束时间戳
|
||||
* @property int $duration 耗时
|
||||
* @property string $status 状态:running/success/error
|
||||
* @property string $error_message 错误信息
|
||||
* @property int $concurrency_id 并发分片ID
|
||||
* @property int $create_time 创建时间
|
||||
*/
|
||||
class SystemTimerLog extends TimeModel
|
||||
{
|
||||
|
||||
protected $name = "system_timer_log";
|
||||
|
||||
protected $deleteTime = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use base\admin\model\SystemUploadfileBase;
|
||||
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemUploadfile extends TimeModel
|
||||
class SystemUploadfile extends SystemUploadfileBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
41
app/admin/scheme/DebugLog.php
Normal file
41
app/admin/scheme/DebugLog.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_debug_log', comment: '')]
|
||||
#[Index(columns: ['uid'], name: 'uid', type: 'NORMAL')]
|
||||
class DebugLog extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '')]
|
||||
public $uid;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '')]
|
||||
public $level;
|
||||
|
||||
#[Field(type: 'longtext')]
|
||||
public $content;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '')]
|
||||
public $create_time_title;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0')]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '')]
|
||||
public $app_name;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '')]
|
||||
public $controller_name;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '')]
|
||||
public $action_name;
|
||||
}
|
||||
44
app/admin/scheme/MallCate.php
Normal file
44
app/admin/scheme/MallCate.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_mall_cate', comment: '商品分类')]
|
||||
#[Index(columns: ['title'], name: 'title', type: 'NORMAL')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class MallCate extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: '', comment: '分类名')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '分类图片')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $image;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '2', comment: '状态')]
|
||||
#[Component(type: 'radio', options: [1 => '禁用', 2 => '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
88
app/admin/scheme/MallGoods.php
Normal file
88
app/admin/scheme/MallGoods.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_mall_goods', comment: '商品列表')]
|
||||
#[Index(columns: ['cate_id'], name: 'cate_id', type: 'NORMAL')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class MallGoods extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, comment: '分类ID', unsigned: true)]
|
||||
public $cate_id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: '', comment: '商品名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '商品标签')]
|
||||
public $tag;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '商品logo')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $logo;
|
||||
|
||||
#[Field(type: 'text', comment: '商品图片')]
|
||||
#[Component(type: 'images', options: [])]
|
||||
public $images;
|
||||
|
||||
#[Field(type: 'text', comment: '商品描述')]
|
||||
#[Component(type: 'editor', options: [])]
|
||||
public $describe;
|
||||
|
||||
#[Field(type: 'text', comment: '商品属性')]
|
||||
public $property;
|
||||
|
||||
#[Field(type: 'decimal', length: 8, precision: 8, scale: 2, default: '0.00', comment: '市场价')]
|
||||
public $market_price;
|
||||
|
||||
#[Field(type: 'decimal', length: 8, precision: 8, scale: 2, default: '0.00', comment: '折扣价')]
|
||||
public $discount_price;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '销量', unsigned: true)]
|
||||
public $sales;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '虚拟销量', unsigned: true)]
|
||||
public $virtual_sales;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '库存', unsigned: true)]
|
||||
public $stock;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '总库存', unsigned: true)]
|
||||
public $total_stock;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序', unsigned: true)]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'radio', options: ['正常', '禁用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '')]
|
||||
public $license;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '')]
|
||||
public $license_name;
|
||||
|
||||
#[Field(type: 'text', comment: '属性(静态字段)')]
|
||||
public $property_static;
|
||||
}
|
||||
29
app/admin/scheme/MallTag.php
Normal file
29
app/admin/scheme/MallTag.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_mall_tag', comment: '商品标签')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class MallTag extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: '', comment: '商品名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
59
app/admin/scheme/SystemAdmin.php
Normal file
59
app/admin/scheme/SystemAdmin.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_admin', comment: '系统用户表')]
|
||||
#[Index(columns: ['username'], name: 'username', type: 'NORMAL')]
|
||||
#[Index(columns: ['phone'], name: 'phone', type: 'NORMAL')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemAdmin extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '角色权限ID')]
|
||||
public $auth_ids;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '头像')]
|
||||
public $head_img;
|
||||
|
||||
#[Field(type: 'char', length: 50, precision: 50, default: '', comment: '用户登录名')]
|
||||
public $username;
|
||||
|
||||
#[Field(type: 'char', length: 40, precision: 40, default: '', comment: '用户密码')]
|
||||
public $password;
|
||||
|
||||
#[Field(type: 'char', length: 16, precision: 16, default: '', comment: '联系手机号')]
|
||||
public $phone;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, default: '0', comment: '登录次数', unsigned: true)]
|
||||
public $login_num;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'radio', options: ['禁用', '启用', ''])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
|
||||
#[Field(length: 50, precision: 50, default: '', comment: '昵称')]
|
||||
public $nickname;
|
||||
}
|
||||
40
app/admin/scheme/SystemAuth.php
Normal file
40
app/admin/scheme/SystemAuth.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_auth', comment: '系统权限表')]
|
||||
#[Index(columns: ['title'], name: 'title', type: 'UNIQUE')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemAuth extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, comment: '权限名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '状态')]
|
||||
#[Component(type: 'radio', options: [1 => '禁用', 2 => '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
23
app/admin/scheme/SystemAuthNode.php
Normal file
23
app/admin/scheme/SystemAuthNode.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_auth_node', comment: '角色与节点关系表')]
|
||||
#[Index(columns: ['auth_id'], name: 'auth_id', type: 'NORMAL')]
|
||||
class SystemAuthNode extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, comment: '角色ID', unsigned: true)]
|
||||
public $auth_id;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '')]
|
||||
public $node;
|
||||
}
|
||||
39
app/admin/scheme/SystemConfig.php
Normal file
39
app/admin/scheme/SystemConfig.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_config', comment: '系统配置表')]
|
||||
#[Index(columns: ['name'], name: 'name', type: 'NORMAL')]
|
||||
#[Index(columns: ['group'], name: 'group', type: 'NORMAL')]
|
||||
class SystemConfig extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '', comment: '变量名')]
|
||||
public $name;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '', comment: '分组')]
|
||||
public $group;
|
||||
|
||||
#[Field(type: 'text', comment: '变量值')]
|
||||
public $value;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '备注信息')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
}
|
||||
41
app/admin/scheme/SystemData.php
Normal file
41
app/admin/scheme/SystemData.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_data', comment: '')]
|
||||
#[Index(columns: ['key'], name: 'ul_system_data_key_IDX', type: 'NORMAL')]
|
||||
#[Index(columns: ['admin_id'], name: 'ul_system_data_admin_id_IDX', type: 'NORMAL')]
|
||||
class SystemData extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false)]
|
||||
public $key;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, nullable: false, comment: '用户', unsigned: true)]
|
||||
public $admin_id;
|
||||
|
||||
#[Field(length: 100, precision: 100, default: 'system', comment: '类型')]
|
||||
#[Component(type: 'select', options: ['system' => '系统产生的数据', 'temp' => '临时数据', 'user' => '用户存储的数据'])]
|
||||
public $type;
|
||||
|
||||
#[Field(type: 'text', comment: '值')]
|
||||
#[Component(type: 'textarea', options: [])]
|
||||
public $value;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, nullable: false, default: '0', comment: '过期时间', unsigned: true)]
|
||||
public $expire_time;
|
||||
}
|
||||
69
app/admin/scheme/SystemHost.php
Normal file
69
app/admin/scheme/SystemHost.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_host', comment: '系统节点表')]
|
||||
#[Index(columns: ['node_id'], name: 'node_id', type: 'UNIQUE')]
|
||||
class SystemHost extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '节点ID')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $node_id;
|
||||
|
||||
#[Field(length: 45, precision: 45, comment: 'IP地址')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $ip_address;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '1', comment: '状态')]
|
||||
#[Component(type: 'switch', options: ['离线', '在线'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'tinyint', length: 4, default: '0', comment: '是否主节点:0=否,1=是')]
|
||||
#[Component(type: 'switch', options: ['否', '是'])]
|
||||
public $is_master;
|
||||
|
||||
#[Field(type: 'datetime', comment: '最后心跳时间')]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $last_heartbeat_at;
|
||||
|
||||
#[Field(length: 255, precision: 255, comment: '系统信息')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $os_info;
|
||||
|
||||
#[Field(length: 50, precision: 50, comment: 'PHP版本')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $php_version;
|
||||
|
||||
#[Field(length: 50, precision: 50, comment: 'CPU负载')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $cpu_load;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, comment: '内存占用(byte)')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $memory_usage;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, comment: '磁盘可用空间(byte)')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $disk_free;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, comment: '磁盘总空间(byte)')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $disk_total;
|
||||
|
||||
#[Field(type: 'int', length: 11, comment: '首次运行时间', unsigned: true)]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, comment: '更新时间', unsigned: true)]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $update_time;
|
||||
}
|
||||
59
app/admin/scheme/SystemMenu.php
Normal file
59
app/admin/scheme/SystemMenu.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_menu', comment: '系统菜单表')]
|
||||
#[Index(columns: ['title'], name: 'title', type: 'NORMAL')]
|
||||
#[Index(columns: ['href'], name: 'href', type: 'NORMAL')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemMenu extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, default: '0', comment: '父id', unsigned: true)]
|
||||
public $pid;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '菜单图标')]
|
||||
public $icon;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '链接')]
|
||||
public $href;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '权限标志')]
|
||||
public $auth_node;
|
||||
|
||||
#[Field(type: 'text', comment: '链接参数')]
|
||||
public $params;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: '_self', comment: '链接打开方式')]
|
||||
public $target;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态')]
|
||||
#[Component(type: 'radio', options: ['禁用', '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
35
app/admin/scheme/SystemNode.php
Normal file
35
app/admin/scheme/SystemNode.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_node', comment: '')]
|
||||
#[Index(columns: ['node'], name: 'node', type: 'NORMAL')]
|
||||
class SystemNode extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '节点代码')]
|
||||
public $node;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '节点标题')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'int', length: 11, comment: '节点类型(1:控制器,2:节点)', unsigned: true)]
|
||||
public $type;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '是否启动RBAC权限控制', unsigned: true)]
|
||||
public $is_auth;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
}
|
||||
45
app/admin/scheme/SystemQuick.php
Normal file
45
app/admin/scheme/SystemQuick.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_quick', comment: '系统快捷入口表')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemQuick extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: '', comment: '快捷入口名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '图标')]
|
||||
public $icon;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, comment: '快捷链接')]
|
||||
public $href;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'radio', options: ['禁用', '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
53
app/admin/scheme/SystemTimerConfig.php
Normal file
53
app/admin/scheme/SystemTimerConfig.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_timer_config', comment: '定时任务协调配置表')]
|
||||
#[Index(columns: ['task_name'], name: 'task_name', type: 'UNIQUE')]
|
||||
class SystemTimerConfig extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'varchar', length: 100, nullable: false, comment: '任务名称')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $task_name;
|
||||
|
||||
#[Field(type: 'varchar', length: 20, default: 'auto', comment: '运行类型:main/auto/all/manual')]
|
||||
#[Component(type: 'select', options: ['main' => 'main', 'auto' => 'auto', 'all' => 'all', 'manual' => 'manual'])]
|
||||
public $run_type;
|
||||
|
||||
#[Field(type: 'tinyint', length: 4, default: '1', comment: '状态:0=停用,1=启用')]
|
||||
#[Component(type: 'switch', options: ['停用', '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'tinyint', length: 4, default: '0', comment: '是否已同步:0=未同步,1=已同步')]
|
||||
#[Component(type: 'switch', options: ['未同步', '已同步'])]
|
||||
public $is_synced;
|
||||
|
||||
#[Field(type: 'varchar', length: 100, nullable: true, comment: '最后执行节点ID')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $last_execute_node;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '最后执行时间戳', unsigned: true)]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $last_execute_time;
|
||||
|
||||
#[Field(type: 'tinyint', length: 4, default: '0', comment: '手动触发标记:0=未触发,1=已触发')]
|
||||
#[Component(type: 'switch', options: ['未触发', '已触发'])]
|
||||
public $manual_trigger;
|
||||
|
||||
#[Field(type: 'int', length: 11, comment: '创建时间', unsigned: true)]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, comment: '更新时间', unsigned: true)]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $update_time;
|
||||
}
|
||||
64
app/admin/scheme/SystemTimerLog.php
Normal file
64
app/admin/scheme/SystemTimerLog.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_timer_log', comment: '定时器执行日志表')]
|
||||
#[Index(columns: ['task_name'], name: 'idx_task_name', type: 'NORMAL')]
|
||||
#[Index(columns: ['node_id'], name: 'idx_node_id', type: 'NORMAL')]
|
||||
#[Index(columns: ['start_time'], name: 'idx_start_time', type: 'NORMAL')]
|
||||
#[Index(columns: ['status'], name: 'idx_status', type: 'NORMAL')]
|
||||
class SystemTimerLog extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '任务名称')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $task_name;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '执行节点ID')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $node_id;
|
||||
|
||||
#[Field(length: 20, precision: 20, comment: '运行类型')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $run_type;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, comment: '开始时间戳')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $start_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, unsigned: true, default: '0', comment: '结束时间戳')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $end_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, unsigned: true, default: '0', comment: '耗时(毫秒)')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $duration;
|
||||
|
||||
#[Field(length: 20, precision: 20, default: 'running', comment: '状态:running/success/error')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $status;
|
||||
|
||||
#[Field(type: 'text', nullable: true, comment: '错误信息')]
|
||||
#[Component(type: 'textarea', options: [])]
|
||||
public $error_message;
|
||||
|
||||
#[Field(type: 'text', nullable: true, comment: '执行结果(任务返回值)')]
|
||||
#[Component(type: 'textarea', options: [])]
|
||||
public $result;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '并发分片ID')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $concurrency_id;
|
||||
|
||||
#[Field(type: 'int', length: 11, unsigned: true, comment: '创建时间')]
|
||||
#[Component(type: 'date', options: [])]
|
||||
public $create_time;
|
||||
}
|
||||
61
app/admin/scheme/SystemUploadfile.php
Normal file
61
app/admin/scheme/SystemUploadfile.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_uploadfile', comment: '上传文件表')]
|
||||
#[Index(columns: ['upload_type'], name: 'upload_type', type: 'NORMAL')]
|
||||
#[Index(columns: ['original_name'], name: 'original_name', type: 'NORMAL')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemUploadfile extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: 'local', comment: '存储位置')]
|
||||
public $upload_type;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '文件原名')]
|
||||
public $original_name;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '链接')]
|
||||
public $url;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '存储名称')]
|
||||
public $save_name;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '', comment: '宽度')]
|
||||
public $image_width;
|
||||
|
||||
#[Field(type: 'char', length: 30, precision: 30, default: '', comment: '高度')]
|
||||
public $image_height;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '图片帧数', unsigned: true)]
|
||||
public $image_frames;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: 'mime类型')]
|
||||
public $mime_type;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '文件大小', unsigned: true)]
|
||||
public $file_size;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '扩展名')]
|
||||
public $file_ext;
|
||||
|
||||
#[Field(type: 'char', length: 40, precision: 40, default: '', comment: '文件 sha1编码')]
|
||||
public $sha1;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
121
app/admin/scheme/TestGoods.php
Normal file
121
app/admin/scheme/TestGoods.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_test_goods', comment: '商品列表')]
|
||||
#[Index(columns: ['uid'], name: 'uid', type: 'UNIQUE')]
|
||||
#[Index(columns: ['cate_id'], name: 'cate_id', type: 'NORMAL')]
|
||||
#[Index(columns: ['detail'], name: 'detail', type: 'FULLTEXT')]
|
||||
class TestGoods extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, nullable: false, default: '0', comment: '分类ID', unsigned: true)]
|
||||
#[Component(type: 'relation', options: ['table' => 'mall_cate', 'relationBindSelect' => 'title'])]
|
||||
public $cate_id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, nullable: false, default: '', comment: '商品名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, nullable: false, comment: '商品logo')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $logo;
|
||||
|
||||
#[Field(type: 'text', nullable: false, comment: '商品图片')]
|
||||
#[Component(type: 'images', options: [])]
|
||||
public $images;
|
||||
|
||||
#[Field(type: 'text', nullable: false, comment: '商品描述')]
|
||||
#[Component(type: 'editor', options: [])]
|
||||
public $describe;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '总库存', unsigned: true)]
|
||||
public $total_stock;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '100', comment: '排序', unsigned: true)]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'radio', options: ['正常', '禁用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '合格证')]
|
||||
#[Component(type: 'file', options: [])]
|
||||
public $cert_file;
|
||||
|
||||
#[Field(type: 'text', nullable: false, comment: '检测报告')]
|
||||
#[Component(type: 'files', options: [])]
|
||||
public $verfiy_file;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, nullable: false, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, comment: '发布日期', unsigned: true)]
|
||||
#[Component(type: 'date', options: ['date'])]
|
||||
public $publish_time;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, nullable: false, comment: '售卖日期', unsigned: true)]
|
||||
#[Component(type: 'date', options: ['datetime'])]
|
||||
public $sale_time;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '简介')]
|
||||
#[Component(type: 'textarea', options: [])]
|
||||
public $intro;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, comment: '秒杀状态', unsigned: true)]
|
||||
#[Component(type: 'select', options: [0 => '未参加', 1 => '已开始', 3 => '已结束'])]
|
||||
public $time_status;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, comment: '是否推荐')]
|
||||
#[Component(type: 'switch', options: ['不推荐', '推荐'])]
|
||||
public $is_recommend;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '商品类型')]
|
||||
#[Component(type: 'checkbox', options: ['taobao' => '淘宝', 'jd' => '京东'])]
|
||||
public $shop_type;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '商品标签')]
|
||||
#[Component(type: 'table', options: ['table' => 'mall_tag', 'type' => 'checkbox', 'valueField' => 'id', 'fieldName' => 'title'])]
|
||||
public $tag;
|
||||
|
||||
#[Field(length: 100, precision: 100, comment: '商品标签(单选)')]
|
||||
#[Component(type: 'table', options: ['table' => 'mall_tag', 'type' => 'radio', 'valueField' => 'id', 'fieldName' => 'title'])]
|
||||
public $tag_backup;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '产地')]
|
||||
#[Component(type: 'city', options: ['name-province' => '0', 'code' => '0'])]
|
||||
public $from_area;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '山东省/临沂市', comment: '仓库')]
|
||||
#[Component(type: 'city', options: ['level' => 'city'])]
|
||||
public $store_city;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '商品标签 (输入)')]
|
||||
#[Component(type: 'tag', options: [])]
|
||||
public $tag_input;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, comment: '唯一id')]
|
||||
public $uid;
|
||||
|
||||
#[Field(type: 'decimal', length: 10, precision: 10, scale: 2, comment: '价格')]
|
||||
public $price;
|
||||
|
||||
#[Field(type: 'text', comment: '详情')]
|
||||
public $detail;
|
||||
}
|
||||
55
app/admin/scheme/TreeTree.php
Normal file
55
app/admin/scheme/TreeTree.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_tree_tree', comment: '')]
|
||||
#[Index(columns: ['path'], name: 'ul_tree_tree_path_IDX', type: 'NORMAL')]
|
||||
#[Index(columns: ['pid'], name: 'ul_tree_tree_pid_IDX', type: 'NORMAL')]
|
||||
#[Index(columns: ['title'], name: 'ul_tree_tree_title_IDX', type: 'NORMAL')]
|
||||
class TreeTree extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, comment: 'ID', autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(length: 255, precision: 255, nullable: false, default: '', comment: '分类名称')]
|
||||
#[Component(type: 'text', options: [])]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'int', length: 11, comment: '父级ID')]
|
||||
public $pid;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '1', comment: '分类层级')]
|
||||
public $level;
|
||||
|
||||
#[Field(length: 255, precision: 255, nullable: false, comment: '分类路径')]
|
||||
public $path;
|
||||
|
||||
#[Field(length: 50, precision: 50, nullable: false, default: 'default', comment: '类型')]
|
||||
public $type;
|
||||
|
||||
#[Field(type: 'tinyint', length: 11, nullable: false, default: '1', comment: '状态')]
|
||||
#[Component(type: 'switch', options: ['禁用', '启用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(length: 255, precision: 255, default: '', comment: '分类图片')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $image;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '创建时间', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '更新时间', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(length: 100, precision: 100, comment: '备注')]
|
||||
public $comment;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
}
|
||||
120
app/admin/scheme/UlthonDemoGoods.php
Normal file
120
app/admin/scheme/UlthonDemoGoods.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_ulthon_demo_goods', comment: '')]
|
||||
#[Index(columns: ['cate_id'], name: 'cate_id', type: 'NORMAL')]
|
||||
#[Index(columns: ['detail'], name: 'detail', type: 'FULLTEXT')]
|
||||
class UlthonDemoGoods extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, nullable: false, default: '0', comment: '分类ID', unsigned: true)]
|
||||
#[Component(type: 'relation', options: ['table' => 'mall_cate', 'relationBindSelect' => 'title'])]
|
||||
public $cate_id;
|
||||
|
||||
#[Field(type: 'char', length: 20, precision: 20, nullable: false, default: '', comment: '商品名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, nullable: false, comment: '商品logo')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $logo;
|
||||
|
||||
#[Field(type: 'text', comment: '商品图片')]
|
||||
#[Component(type: 'images', options: [])]
|
||||
public $images;
|
||||
|
||||
#[Field(type: 'text', comment: '商品描述')]
|
||||
#[Component(type: 'editor', options: [])]
|
||||
public $describe;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '总库存', unsigned: true)]
|
||||
public $total_stock;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '排序', unsigned: true)]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'radio', options: ['正常', '禁用'])]
|
||||
public $status;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '合格证')]
|
||||
#[Component(type: 'file', options: [])]
|
||||
public $cert_file;
|
||||
|
||||
#[Field(type: 'text', comment: '检测报告')]
|
||||
#[Component(type: 'files', options: [])]
|
||||
public $verfiy_file;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, nullable: false, default: '', comment: '备注说明')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, comment: '发布日期', unsigned: true)]
|
||||
#[Component(type: 'date', options: ['date'])]
|
||||
public $publish_time;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, nullable: false, default: '0', comment: '售卖日期', unsigned: true)]
|
||||
#[Component(type: 'date', options: ['datetime'])]
|
||||
public $sale_time;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '简介')]
|
||||
#[Component(type: 'textarea', options: [])]
|
||||
public $intro;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '秒杀状态', unsigned: true)]
|
||||
#[Component(type: 'select', options: [0 => '未参加', 1 => '已开始', 3 => '已结束'])]
|
||||
public $time_status;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', comment: '是否推荐')]
|
||||
#[Component(type: 'switch', options: ['不推荐', '推荐'])]
|
||||
public $is_recommend;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '商品类型')]
|
||||
#[Component(type: 'checkbox', options: ['taobao' => '淘宝', 'jd' => '京东'])]
|
||||
public $shop_type;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '商品标签')]
|
||||
#[Component(type: 'table', options: ['table' => 'mall_tag', 'type' => 'checkbox', 'valueField' => 'id', 'fieldName' => 'title'])]
|
||||
public $tag;
|
||||
|
||||
#[Field(length: 100, precision: 100, default: '', comment: '商品标签(单选)')]
|
||||
#[Component(type: 'table', options: ['table' => 'mall_tag', 'type' => 'radio', 'valueField' => 'id', 'fieldName' => 'title'])]
|
||||
public $tag_backup;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '产地')]
|
||||
#[Component(type: 'city', options: ['name-province' => '0', 'code' => '0'])]
|
||||
public $from_area;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '山东省/临沂市', comment: '仓库')]
|
||||
#[Component(type: 'city', options: ['level' => 'city'])]
|
||||
public $store_city;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '商品标签 (输入)')]
|
||||
#[Component(type: 'tag', options: [])]
|
||||
public $tag_input;
|
||||
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '', comment: '唯一id')]
|
||||
public $uid;
|
||||
|
||||
#[Field(type: 'decimal', length: 10, precision: 10, default: '0', comment: '价格')]
|
||||
public $price;
|
||||
|
||||
#[Field(type: 'text', comment: '详情')]
|
||||
public $detail;
|
||||
}
|
||||
52
app/admin/scheme/UlthonDemoGoodsBatch.php
Normal file
52
app/admin/scheme/UlthonDemoGoodsBatch.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\scheme;
|
||||
|
||||
use app\common\scheme\BaseScheme;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_ulthon_demo_goods_batch', comment: '')]
|
||||
class UlthonDemoGoodsBatch extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
public $id;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $create_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $update_time;
|
||||
|
||||
#[Field(type: 'int', length: 11, nullable: false, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, unsigned: true)]
|
||||
public $goods_id;
|
||||
|
||||
#[Field(length: 100, precision: 100, default: '', comment: '批次名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(length: 100, precision: 100, default: '', comment: '批次编号')]
|
||||
public $num;
|
||||
|
||||
#[Field(type: 'tinyint', length: 11, default: '1', comment: '状态', unsigned: true)]
|
||||
#[Component(type: 'switch', options: ['关闭', '开启'])]
|
||||
public $status;
|
||||
|
||||
#[Field(length: 100, precision: 100, default: '', comment: '备注')]
|
||||
#[Component(type: 'textarea', options: [])]
|
||||
public $comment;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, default: '0', comment: '库存')]
|
||||
public $stock;
|
||||
|
||||
#[Field(length: 100, precision: 100, comment: '包装')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $title_image;
|
||||
|
||||
#[Field(length: 100, precision: 100)]
|
||||
public $add_flag;
|
||||
}
|
||||
9
app/admin/service/AdminInitService.php
Normal file
9
app/admin/service/AdminInitService.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use base\admin\service\AdminInitServiceBase;
|
||||
|
||||
class AdminInitService extends AdminInitServiceBase
|
||||
{
|
||||
}
|
||||
9
app/admin/service/AdminUpdateCodeService.php
Normal file
9
app/admin/service/AdminUpdateCodeService.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use base\admin\service\AdminUpdateCodeServiceBase;
|
||||
|
||||
class AdminUpdateCodeService extends AdminUpdateCodeServiceBase
|
||||
{
|
||||
}
|
||||
9
app/admin/service/AdminUpdateService.php
Normal file
9
app/admin/service/AdminUpdateService.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use base\admin\service\AdminUpdateServiceBase;
|
||||
|
||||
class AdminUpdateService extends AdminUpdateServiceBase
|
||||
{
|
||||
}
|
||||
@@ -1,28 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use base\admin\service\NodeServiceBase;
|
||||
|
||||
use app\admin\service\node\Node;
|
||||
|
||||
class NodeService
|
||||
class NodeService extends NodeServiceBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取节点服务
|
||||
* @return array
|
||||
* @throws \Doctrine\Common\Annotations\AnnotationException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getNodelist()
|
||||
{
|
||||
$basePath = base_path() . 'admin' . DIRECTORY_SEPARATOR . 'controller';
|
||||
$baseNamespace = "app\admin\controller";
|
||||
|
||||
$nodeList = (new Node($basePath, $baseNamespace))
|
||||
->getNodelist();
|
||||
|
||||
return $nodeList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use base\admin\service\TriggerServiceBase;
|
||||
|
||||
use think\facade\Cache;
|
||||
|
||||
class TriggerService
|
||||
class TriggerService extends TriggerServiceBase
|
||||
{
|
||||
|
||||
/**
|
||||
* 更新菜单缓存
|
||||
* @param null $adminId
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateMenu($adminId = null)
|
||||
{
|
||||
if(empty($adminId)){
|
||||
Cache::tag('initAdmin')->clear();
|
||||
}else{
|
||||
Cache::delete('initAdmin_' . $adminId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新节点缓存
|
||||
* @param null $adminId
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateNode($adminId = null)
|
||||
{
|
||||
if(empty($adminId)){
|
||||
Cache::tag('authNode')->clear();
|
||||
}else{
|
||||
Cache::delete('allAuthNode_' . $adminId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置缓存
|
||||
* @return bool
|
||||
*/
|
||||
public static function updateSysconfig()
|
||||
{
|
||||
Cache::tag('sysconfig')->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | EasyAdmin
|
||||
// | ulthon_admin
|
||||
// +----------------------------------------------------------------------
|
||||
// | PHP交流群: 763822524
|
||||
// | PHP交流群: 207160418
|
||||
// +----------------------------------------------------------------------
|
||||
// | 开源协议 https://mit-license.org
|
||||
// | 开源协议 http://license.coscl.org.cn/MulanPSL2
|
||||
// +----------------------------------------------------------------------
|
||||
// | github开源项目:https://github.com/zhongshaofa/EasyAdmin
|
||||
// | gitee开源项目:https://gitee.com/ulthon/ulthon_admin
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\admin\service\annotation;
|
||||
|
||||
use base\admin\service\annotation\ControllerAnnotationBase;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class ControllerAnnotation
|
||||
* Class ControllerAnnotation.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
@@ -27,23 +27,6 @@ use Doctrine\Common\Annotations\Annotation\Target;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
final class ControllerAnnotation
|
||||
final class ControllerAnnotation extends ControllerAnnotationBase
|
||||
{
|
||||
|
||||
/**
|
||||
* Route group prefix for the controller
|
||||
*
|
||||
* @Required()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* 是否开启权限控制
|
||||
* @Enum({true,false})
|
||||
* @var bool
|
||||
*/
|
||||
public $auth = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | EasyAdmin
|
||||
// | ulthon_admin
|
||||
// +----------------------------------------------------------------------
|
||||
// | PHP交流群: 763822524
|
||||
// | PHP交流群: 207160418
|
||||
// +----------------------------------------------------------------------
|
||||
// | 开源协议 https://mit-license.org
|
||||
// | 开源协议 http://license.coscl.org.cn/MulanPSL2
|
||||
// +----------------------------------------------------------------------
|
||||
// | github开源项目:https://github.com/zhongshaofa/EasyAdmin
|
||||
// | github开源项目:https://github.com/zhongshaofa/ulthon_admin
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\admin\service\annotation;
|
||||
|
||||
use base\admin\service\annotation\NodeAnotationBase;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
|
||||
/**
|
||||
@@ -23,25 +24,6 @@ use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
* @Attribute("time", type = "int")
|
||||
* })
|
||||
*/
|
||||
final class NodeAnotation
|
||||
final class NodeAnotation extends NodeAnotationBase
|
||||
{
|
||||
/**
|
||||
* 节点名称.
|
||||
* @Required()
|
||||
* @var string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* 是否开启权限控制.
|
||||
* @Enum({true,false})
|
||||
* @var bool
|
||||
*/
|
||||
public $auth = true;
|
||||
|
||||
/**
|
||||
* 节点 一般无需设置.
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\admin\service\curd\exceptions;
|
||||
|
||||
use base\admin\service\curd\exceptions\CurdExceptionBase;
|
||||
|
||||
class CurdException extends \Exception
|
||||
class CurdException extends CurdExceptionBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user