mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-01 07:22:49 +08:00
feat: 发布智能体版
This commit is contained in:
20
.agents/AGENTS.md
Normal file
20
.agents/AGENTS.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 项目使用者补充规则(业务侧)
|
||||
|
||||
本文件用于记录“使用框架的开发者(做业务)”在开发过程中补充的项目规则、团队偏好与临时约束。
|
||||
|
||||
## 定位与优先级
|
||||
|
||||
- 根目录 [AGENTS.md](../AGENTS.md) 的「项目级规则」为唯一权威;本文件不得覆盖或弱化其内容。
|
||||
- 本文件只记录**业务侧/项目侧**的增量约束(例如:交付格式、验证方式、代码生成偏好、团队约定等)。
|
||||
- 如发现需要变更框架基础规则,应由框架作者将规则整理并合并到根目录 `AGENTS.md` 的「项目级规则」或其他对应规则文件中。
|
||||
|
||||
## 维护方式(必须遵守)
|
||||
|
||||
- 智能体以“使用框架的开发者”身份执行任务时,如发现需要记录或调整的项目约束,应更新到本文件,并可按开发者要求随时调整。
|
||||
- 智能体以“框架作者”身份开发时,如需调整本文件内容,必须先与开发者确认记录位置与写法,并按确认结果执行。
|
||||
- 规则应可执行、可复现、可验证;避免空泛口号。
|
||||
|
||||
## 记录模板(按条新增)
|
||||
|
||||
| 日期 | 场景/触发 | 规则内容(可执行) | 影响范围 | 来源(文件/路径/命令) | 状态(临时/已合并) |
|
||||
|---|---|---|---|---|---|
|
||||
0
.agents/rules/.gitkeep
Normal file
0
.agents/rules/.gitkeep
Normal file
@@ -50,3 +50,4 @@ ThinkPHP 8 默认开启了 URL 自动转换(`url_convert`)。大驼峰命名
|
||||
|
||||
- **统一风格**:在代码中使用大驼峰命名类和方法,但在前端请求、模板链接(如 `{:url('...')}`)中建议明确指向转换后的路径或使用系统助手函数自动生成。
|
||||
- **路由定义**:对于复杂的 URL,建议在 `route/*.php` 中手动定义路由规则,以提供更友好的访问地址。
|
||||
|
||||
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`(只导出未删除菜单)。
|
||||
95
.agents/skills/ulthon-base-app-architecture/SKILL.md
Normal file
95
.agents/skills/ulthon-base-app-architecture/SKILL.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
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 类(除非你需要利用框架基类的功能)。
|
||||
|
||||
#### 场景 B:修改/扩展框架内置功能
|
||||
如果你对框架默认的某个功能(如登录逻辑、CURD流程)不满意,请按以下步骤操作:
|
||||
|
||||
1. **定位**:找到该功能对应的 `app/` 入口类(例如 `app/admin/model/SystemAdmin.php`)。
|
||||
2. **重写**:在该类中重写你需要修改的方法。
|
||||
- 保持方法签名(参数、返回值)一致。
|
||||
- 可以使用 `parent::method()` 复用父类逻辑。
|
||||
3. **生效**:系统会自动调用你的类,而不是底层的 Base 类。
|
||||
|
||||
### 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/`。
|
||||
- **辅助函数**:放在 `extend/base/helper.php`,通过 `app/common.php` 引入。
|
||||
- **初始化数据**:放在 `extend/base/adminInitData/`。
|
||||
|
||||
---
|
||||
|
||||
## 三、架构参考资料
|
||||
|
||||
### 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/command/CurdBase.php` | `app/common/command/Curd.php` |
|
||||
| `extend/base/common/service/SmsBase.php` | `app/common/service/Sms.php` |
|
||||
78
.agents/skills/ulthon-cli-reference/SKILL.md
Normal file
78
.agents/skills/ulthon-cli-reference/SKILL.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: "ulthon-cli-reference"
|
||||
description: "Ulthon Admin 框架常用 CLI 命令速查手册,包括菜单管理、Scheme 同步与 CURD 生成等命令说明。"
|
||||
---
|
||||
|
||||
# CLI 命令参考文档
|
||||
|
||||
本文档提供 Ulthon Admin 框架的常用 CLI 命令速查手册。
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 当需要查找 Ulthon Admin 特定的 CLI 命令(如菜单管理、Scheme 同步、CURD 生成)及其参数时。
|
||||
- 当需要验证或调试 CLI 命令功能时。
|
||||
|
||||
## 关键原则
|
||||
|
||||
- 菜单管理命令用于操作后台菜单结构,支持 JSON/Tree 格式输出。
|
||||
- Scheme 与 CURD 命令用于保持数据库与代码的一致性,以及生成基础代码。
|
||||
- CURD 操作建议先使用 `-r` 生成到临时目录进行预览。
|
||||
- Scheme 同步操作涉及数据库变更,需谨慎使用。
|
||||
|
||||
## 菜单管理
|
||||
|
||||
| 命令 | 说明 | 常用参数 |
|
||||
|------|------|----------|
|
||||
| `admin:menu:list` | 列出菜单(树形) | `--format`, `--status`, `--pid` |
|
||||
| `admin:menu:create` | 创建菜单 | - |
|
||||
| `admin:menu:update` | 编辑菜单 | - |
|
||||
| `admin:menu:delete` | 删除菜单 | - |
|
||||
| `admin:menu:export` | 导出菜单数据 | `--format=json` |
|
||||
|
||||
## Scheme & CURD
|
||||
|
||||
| 命令 | 说明 | 常用参数 |
|
||||
|------|------|----------|
|
||||
| `scheme:sync` | 同步 Scheme 到数据库 | `--force` (跳过确认) |
|
||||
| `scheme:make` | 从数据库生成 Scheme | `-t {table}` |
|
||||
| `curd` | 生成 CURD 代码 | `-t {table}` `-r` (临时) `-f` (强制) `-d` (删除) |
|
||||
|
||||
### CURD 参数说明
|
||||
|
||||
| 参数 | 简写 | 说明 |
|
||||
|------|------|------|
|
||||
| `--table` | `-t` | 主表名(支持带前缀或不带前缀) |
|
||||
| `--force` | `-f` | 强制覆盖模式(**谨慎使用**) |
|
||||
| `--delete` | `-d` | 删除模式(**删除生成的文件,不是数据库操作**) |
|
||||
| `--runtime` | `-r` | 临时生成模式(**推荐用于预览**) |
|
||||
| `--examples` | | 显示使用示例 |
|
||||
|
||||
## 快速示例
|
||||
|
||||
### 菜单管理
|
||||
|
||||
```bash
|
||||
php think admin:menu:list # 查看菜单树
|
||||
php think admin:menu:create # 创建菜单
|
||||
php think admin:menu:export --format=json # 导出菜单
|
||||
```
|
||||
|
||||
### CURD 操作
|
||||
|
||||
```bash
|
||||
# 预览生成(推荐)
|
||||
php think curd -t daka_record -r
|
||||
|
||||
# 正式生成
|
||||
php think curd -t daka_record
|
||||
|
||||
# 强制覆盖(小心)
|
||||
php think curd -t daka_record -f
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **表名参数**:CURD 和 Scheme 命令的表名支持带前缀或不带前缀(v2.x+ 自动检测)
|
||||
2. **安全确认**:`scheme:sync` 默认需要确认,使用 `-ff` 跳过
|
||||
3. **临时生成**:CURD 使用 `-r` 可生成到临时目录预览
|
||||
4. **删除模式**:CURD 的 `-d` 是删除生成的文件,不是数据库操作
|
||||
45
.agents/skills/ulthon-core-extend-pattern/SKILL.md
Normal file
45
.agents/skills/ulthon-core-extend-pattern/SKILL.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: "ulthon-core-extend-pattern"
|
||||
description: "业务开发默认只写 app;需要改内置能力时,通过 app 入口类覆盖 *Base 默认实现。"
|
||||
---
|
||||
|
||||
# 扩展内置能力(继承与重写)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 需要新增业务能力(新控制器/模型/服务/命令等):直接在 `app/` 写代码。
|
||||
- 需要扩展/调整系统已有能力(默认实现存在于 `extend/base/`):在 `app/` 下对应入口类重写以覆盖默认实现。
|
||||
|
||||
## 关键原则
|
||||
|
||||
- 新写业务代码:只写 `app/`,不需要、也不应该去 `extend/base/` 做任何“对齐”。
|
||||
- 改内置能力:只改 `app/` 下对应入口类(必要时 `extends` 对应 `*Base`)。
|
||||
- 严禁直接修改 `extend/base/` 下任何文件。
|
||||
- 重写时保持方法签名一致;可用 `parent::method()` 复用父类逻辑,或复制父类代码后自行实现。
|
||||
|
||||
## 操作流程
|
||||
|
||||
### A. 新写业务能力(推荐默认路径)
|
||||
|
||||
1. 直接在 `app/` 下创建需要的控制器/模型/服务/命令等类文件(按项目命名规范与目录约定放置)。
|
||||
2. 让业务入口只依赖 `app/` 下的类,不需要为“对应 Base 层文件”做额外映射。
|
||||
|
||||
### B. 覆盖框架默认行为(扩展/调整已有能力)
|
||||
|
||||
1. 定位对应的 `*Base` 默认实现文件路径(通常在 `extend/base/`)。
|
||||
2. 在 `app/` 下创建或修改同路径的入口类文件,并 `extends` 对应 `*Base`。
|
||||
3. 在入口类中重写需要调整的方法,确保方法签名一致。
|
||||
|
||||
## 常见误区(避免歧义)
|
||||
|
||||
- 误区:新增业务功能时,也要先在 `extend/base/` 建一个 `*Base` 再在 `app/` 继承
|
||||
结论:不需要;这是框架内核维护模式,不是业务开发默认模式
|
||||
- 误区:看到框架存在 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/admin/role/AdminRoleCreateBase.php` → `app/common/command/admin/role/AdminRoleCreate.php`
|
||||
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` 撤回后再删除。
|
||||
@@ -16,13 +16,34 @@ description: "指导使用 Scheme 与 CURD 的标准开发流程。需要新增/
|
||||
- 业务 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
|
||||
```
|
||||
2. 执行同步,将代码结构同步到数据库:
|
||||
|
||||
```bash
|
||||
@@ -115,3 +115,4 @@ class YourClassName extends BaseScheme
|
||||
- `images`, `photos`, `icons`: 默认为多图片。
|
||||
- `file`: 默认为单文件。
|
||||
- `files`: 默认为多文件。
|
||||
|
||||
140
.agents/skills/ulthon-timer/SKILL.md
Normal file
140
.agents/skills/ulthon-timer/SKILL.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: "ulthon-timer"
|
||||
description: "内置秒级定时器(php think timer)的使用与扩展规范;用于新增/调整定时任务(site/call、并发分片、TimerController 防刷、timer.mode normal/parallel)。"
|
||||
---
|
||||
|
||||
# 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` 控制节奏:用调度频率限制整体吞吐,避免单次占用过久
|
||||
|
||||
### 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):
|
||||
|
||||
```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 --quit`
|
||||
|
||||
### 本地调试(指定请求 Host)
|
||||
|
||||
site 任务会按站点域名发起请求,默认从 `sysconfig('site','site_domain')` 读取。
|
||||
|
||||
- 本地调试:`php think timer --local --local-host=http://localhost --local-port=8000`
|
||||
|
||||
### 运行模式
|
||||
|
||||
配置在 [timer.php](../../../config/timer.php):
|
||||
|
||||
- `normal`:单进程循环 + Guzzle async(默认)
|
||||
- `parallel`:Workerman 多进程模式(并发更高,相关连接参数在 `timer.php` 中)
|
||||
|
||||
## 常见坑位(快速自检)
|
||||
|
||||
- `name` 重复:会导致 Cache key 冲突,表现为任务“莫名其妙不跑/跑得不对”
|
||||
- `concurrency` 与控制器侧 `$concurrency` 不一致:会触发 `concurrency id/count error`
|
||||
- 只依赖控制器侧 `$frequency`:它只是防刷,不是调度;调度频率以定时器侧 Cache 节流为准
|
||||
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` 或调整权限节点。
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -47,5 +47,4 @@ extend/base/common/command/curd/migrate_output.php
|
||||
/source/**/.hbuilderx
|
||||
/source/**/.idea
|
||||
/source/**/.vscode
|
||||
.trae/documents/新增 source 目录与 uni-app(CLI)基础工程.md
|
||||
.trae/documents/uni-app 增加 env_request_utils 与用户状态机制.md
|
||||
.trae/documents/
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
## Ulthon Admin(ThinkPHP 8)项目级规则(Agent 必须遵守)
|
||||
|
||||
### 核心红线
|
||||
|
||||
- 严禁修改 `extend/base/` 下任何文件。
|
||||
- 所有业务逻辑必须放在 `app/` 下(优先 `app/admin/`),通过继承与重写扩展框架能力。
|
||||
- 即使是框架层能力,也必须从 `app/` 下入口类调用(业务类继承 base 层基础类),不得直接从 `extend/base/` 调用。
|
||||
|
||||
### 环境与配置
|
||||
|
||||
- 不自行安装/变更基础环境(Docker、MySQL、Redis 等),除非用户明确要求。
|
||||
- 不修改环境配置文件(如 `.env`);发现配置缺失或不合理时,仅提示需要由开发者补齐。
|
||||
- 不主动启动或常驻运行项目(由开发者提前运行以便调试);仅在用户明确要求时执行相关命令。
|
||||
|
||||
### 数据库与结构
|
||||
|
||||
- 设计表结构优先采用 Scheme 机制(`app/admin/scheme/`),确保 Code 与 DB 一致后再生成 CURD。
|
||||
- 一般不要使用枚举类型(ENUM);优先使用 `tinyint/int` + 注释/字典表等方式表达状态。
|
||||
- 调试数据库数据可以使用内置 tools:db 命令,但不要用它来“设计表结构”(临时调试改动需注意还原)。
|
||||
|
||||
### 前端与视图组织
|
||||
|
||||
- 遵循视图与脚本同名配对:每个视图 `*.html` 配套同名 `*.js`,并按模块维护 `_common.js`。
|
||||
|
||||
### 代码风格与命名
|
||||
|
||||
- 严格遵循项目命名规范与 PSR 风格;需要格式化时以仓库根目录的 `.php-cs-fixer.php` 配置为准(不假设本机已安装相关工具)。
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: "ulthon-core-extend-pattern"
|
||||
description: "指导如何在不修改 extend/base 的前提下扩展功能。需要新增/改造框架内置能力(权限、菜单、管理员等)时调用。"
|
||||
---
|
||||
|
||||
# 核心层扩展模式(继承与重写)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 需要修改或扩展框架内置模块(如管理员、菜单、权限、上传等)行为。
|
||||
- 看到代码位于 `extend/base/`,但业务需要调整其逻辑或返回数据。
|
||||
|
||||
## 必须遵守
|
||||
|
||||
- 不修改 `extend/base/` 下任何文件。
|
||||
- 业务实现放在 `app/` 下,并通过继承覆盖 base 层实现。
|
||||
- 所有调用从 `app/` 下入口类开始,不直接 new/调用 `extend/base/` 类。
|
||||
|
||||
## 推荐做法
|
||||
|
||||
1. 在 `extend/base/` 找到对应基础类(通常以 `*Base.php` 结尾)。
|
||||
2. 在 `app/` 下找到或创建同职责的业务类(不带 Base 后缀),让其继承基础类。
|
||||
3. 在业务类中重写需要变更的方法:
|
||||
- 方法签名保持一致
|
||||
- 能复用就 `parent::methodName()`,必要时复制父类逻辑后改造
|
||||
4. 保持对外调用点不变:控制器/服务统一调用 `app/` 下的类。
|
||||
|
||||
## 示例定位思路
|
||||
|
||||
- 控制器:`extend/base/admin/controller/*/*Base.php` ↔ `app/admin/controller/*/*.php`
|
||||
- 模型:`extend/base/admin/model/*Base.php` ↔ `app/admin/model/*.php`
|
||||
- 服务:`extend/base/admin/service/*Base.php` ↔ `app/admin/service/*.php`
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: "ulthon-page-api-dual-mode"
|
||||
description: "指导控制器实现“页面/接口同体”。需要同一路由同时返回 HTML 与 JSON 时调用。"
|
||||
---
|
||||
|
||||
# 页面 / 接口同体(Controller Dual Mode)
|
||||
|
||||
## 何时调用
|
||||
|
||||
- 希望同一个控制器方法既能渲染页面(HTML),也能作为接口返回 JSON。
|
||||
- 需要让现有页面接口在移动端/脚本调用时返回结构化数据。
|
||||
|
||||
## 触发与认证
|
||||
|
||||
- 触发:请求头 `Accept` 包含 `application/json`。
|
||||
- 认证:Header 传 `Authorization: Bearer <tokenContent>`。
|
||||
|
||||
## 实现要点
|
||||
|
||||
- 控制器方法必须调用 `$this->fetch()`,不要使用 `View::fetch()`。
|
||||
- 无论使用 `$this->assign()` 还是 `View::assign()`,其数据都会被转换为 JSON 返回。
|
||||
- 若不希望某个 assign 字段出现在 JSON 中,可使用:
|
||||
|
||||
```php
|
||||
$this->assign('name', 'value', -1);
|
||||
```
|
||||
|
||||
## 特殊行为
|
||||
|
||||
- `index` 方法 GET 默认返回分页数据;如果需要返回 assign 数据,URL 增加参数 `get_page_data=1`。
|
||||
- 仅在控制器模式(Controller Mode)生效;路由模式(Route Mode)不生效。
|
||||
|
||||
206
AGENTS.md
206
AGENTS.md
@@ -1,26 +1,198 @@
|
||||
## Agent 工作约束(Ulthon Admin)
|
||||
# 智能体协作规范(Ulthon Admin)
|
||||
|
||||
本仓库的开发规范以两类信息为准:
|
||||
- `AGENTS.md`(本文件):协作规则入口与导航
|
||||
- `.agent/`:工作流(skills)与补充文档(含业务侧规则:`.agent/AGENTS.md`)
|
||||
- 开发规范与标准流程:已整合到本文件「项目级规则」中
|
||||
|
||||
1. **规则(必须始终遵守)**:见 [.trae/rules/project_rules.md](./.trae/rules/project_rules.md) 与 [CODERULE.md](./CODERULE.md)。
|
||||
2. **技能(按场景调用的工作流)**:见 `.trae/skills/*/SKILL.md`。
|
||||
## 项目级规则(最高优先级 / 唯一权威)
|
||||
|
||||
在线文档(参考):https://doc.ulthon.com/read/augushong/ulthon_admin/home/zh-cn/2.x.html
|
||||
与其他文档、聊天内容、或智能体建议冲突时,一律以本节为准。
|
||||
除非明确要求,按照框架使用者的规则进行开发。
|
||||
|
||||
## 不应该做的事情
|
||||
### 通用基础规范(所有开发者必须遵守)
|
||||
|
||||
1. 一般不需要直接修改开发环境(除非安装 composer 依赖或开发相关资源)。不要自行安装 Docker、MySQL、Redis 等基础环境,这些由开发者维护。
|
||||
2. 一般不需要修改环境配置文件(如 `.env`)。发现配置缺失或错误时,应提示开发者完善相关配置。
|
||||
3. 一般不需要自行启动或常驻运行项目程序(开发者会提前运行以便调试);除非用户明确要求执行运行/调试命令。
|
||||
- 技术栈:ThinkPHP 8.x;PHP 8+;MySQL 8+;Layui 2.x;模板引擎:ThinkPHP 内置模板引擎
|
||||
- 命名规范(必读):<https://doc.ulthon.com/read/augushong/ulthon_admin/64fbcf8830640/zh-cn/2.x.html>
|
||||
- 代码风格(必读,遵循 `.php-cs-fixer.php`):<https://doc.ulthon.com/read/augushong/ulthon_admin/64360c249d66a/zh-cn/2.x.html>
|
||||
- 表结构设计规范(必读):<https://doc.ulthon.com/read/augushong/ulthon_admin/619efc9d7af62/zh-cn/2.x.html>
|
||||
- 项目文档主页:<https://doc.ulthon.com/read/augushong/ulthon_admin/home/zh-cn/2.x.html>
|
||||
|
||||
## 框架内置能力速查
|
||||
### 代码分层(3 层)
|
||||
|
||||
本段只保留入口与触发条件,细节以对应技能为准,包括但不限于以下内容:
|
||||
- ThinkPHP 层:框架本身;通过 Provider 机制可以替换/扩展 ThinkPHP 的行为(配置通常在 `app/provider.php`,例如绑定核心类实现、注册中间件等)
|
||||
- ulthon\_admin 层:框架内核与默认实现,主要位于 `extend/base/`(\*Base)与 `extend/think/`(对 ThinkPHP 行为的适配/替换)
|
||||
- App 层:业务代码与系统唯一调用入口,位于 `app/`(包含:用于覆盖框架默认实现的入口类;以及:业务自行新增的普通类)
|
||||
|
||||
- 扩展内置能力(不改 base):[ulthon-core-extend-pattern](./.trae/skills/ulthon-core-extend-pattern/SKILL.md)
|
||||
- Scheme + CURD 工作流:[ulthon-scheme-curd-workflow](./.trae/skills/ulthon-scheme-curd-workflow/SKILL.md)
|
||||
- 数据库调试命令(tools:db):[ulthon-db-tools-debug](./.trae/skills/ulthon-db-tools-debug/SKILL.md)
|
||||
- 页面 / 接口同体(同一控制器 HTML+JSON):[ulthon-page-api-dual-mode](./.trae/skills/ulthon-page-api-dual-mode/SKILL.md)
|
||||
- 登录认证(Session + Token):[ulthon-auth-session-token](./.trae/skills/ulthon-auth-session-token/SKILL.md)
|
||||
### 身份与职责(2 种)
|
||||
|
||||
一句话讲清楚:
|
||||
|
||||
- 框架作者:代码写在 `extend/base/`,但系统调用入口必须在 `app/`(保证使用者可在 `app/` 继承重写)
|
||||
- 框架使用者:内置能力要改就改 `app/` 的对应入口类;自己新增的业务代码直接写 `app/`,不需要关心 Base/App 双层机制
|
||||
|
||||
#### ulthon\_admin 框架作者(维护框架/内核)
|
||||
|
||||
- 新增通用能力:实现写到 `extend/base/`(必要时通过 Provider 机制扩展 ThinkPHP 行为)
|
||||
- 同时在 `app/` 下建立对应入口类(可为空类继承 Base),作为系统唯一调用入口
|
||||
- Base 内部引用模型/服务/控制器时,一律引用 `app/` 下的类(保证业务层扩展始终生效)
|
||||
- 静态文件/模板/配置支持分层加载:优先加载 `app/`,不存在时再回落到框架默认实现(例如 `app_file_path`)
|
||||
- Base层代码一般以 `*Base.php` 命名,放在 `extend/base/` 下
|
||||
- Base层代码和app层代码的文件路径和名称一般是对应的。
|
||||
- 核心层维护原则:稳定性优先(保证向下兼容);通用性优先(不引入具体业务逻辑)
|
||||
|
||||
**Base 层文件分类**:
|
||||
|
||||
- `*Base.php`:控制器/模型/服务/命令基类(被 App 层继承)
|
||||
- `helper.php`:全局辅助函数(通过 app/common.php 引用)
|
||||
- `adminInitData/`:框架核心初始化数据(带 @internal-framework 注解标记)
|
||||
- `adminUpdateCodeData/`:框架版本更新代码(带 @internal-framework 注解标记)
|
||||
|
||||
#### 使用框架的开发者(做业务)
|
||||
|
||||
- 严禁修改 `extend/base/` 下任何文件
|
||||
- 业务逻辑一般放在 `app/` 下(优先 `app/admin/`)
|
||||
- 修改/扩展框架内置能力:只改 `app/` 下对应入口类(必要时 `extends` 对应 `*Base`);仍严禁直接改动 `extend/base/`
|
||||
- 新写业务能力:直接在 `app/` 下实现即可;不需要、也不应该在 `extend/base/` 新建任何 `*Base.php`
|
||||
- 公共代码放在 `app/common/`(工具类、基础类等)
|
||||
- 命令类特殊一些,放在 `app/common/command/`(不放在 `app/admin/command/`)
|
||||
- 重写规范:保持方法签名一致;可用 `parent::method()` 复用父类逻辑,或复制父类代码后自行实现
|
||||
- 其他无任何特殊要求,可以按照任意方式写代码,以上只是对base层作说明。但框架仍然规定了大量的开发规范,需要参考在线文档。
|
||||
- 尽量按框架的规范、内置规则、内置技能进行开发,避免违反框架的设计初衷。
|
||||
|
||||
### Base/App 双层机制调用红线(内核开发约束)
|
||||
|
||||
- `extend/base/` 为框架内核实现(`*Base`),仅作为 `app/` 的继承基类使用,不作为系统调用入口
|
||||
- 在实现通用能力/内核代码时,禁止直接引用或调用 `extend/base/` 下的类(包括 `extend/base/` 内部互相调用)
|
||||
- 模型/服务/控制器/命令的引用与调用必须指向 `app/` 下的对应入口类(保证覆盖/重写始终生效)
|
||||
- 例外:
|
||||
1. 允许在 `app/` 下类定义中使用 `extends` 继承对应 `*Base` 类
|
||||
2. `app/common.php` 中包含 `include App::getRootPath() . '/extend/base/helper.php';`,这是全局辅助函数文件的引用特例
|
||||
- `extend/base/helper.php` 仅包含**全局辅助函数**(非类),属于框架内核能力的一部分
|
||||
- 这些函数需要在整个应用范围内可用(如 `__url()`, `password()`, `sysconfig()`, `auth()`, `app_file_path()` 等)
|
||||
- 该引用为框架设计要求的合理例外,不影响 Base/App 双层机制原则
|
||||
|
||||
### 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/admin/role/AdminRoleCreateBase.php` → `app/common/command/admin/role/AdminRoleCreate.php`
|
||||
|
||||
### 其他规则
|
||||
|
||||
- 数据库:表结构优先 Scheme(`app/admin/scheme/`);避免 ENUM;tools:db 用于调试,不用于“设计表结构”
|
||||
- 后端:代码优先使用CURD机制生成。
|
||||
- 前端:视图与脚本同名配对(`*.html` + 同名 `*.js`),并按模块维护 `_common.js`
|
||||
- 配套资源与多端代码:统一放在 `source/`;不影响现有 PHP/ThinkPHP 主工程运行与发布;目录约定与安全要求见 `source/README.md`(禁止提交构建产物、依赖目录等)
|
||||
- 接口:如果需要实现接口能力,需要利用框架的“页面接口同体机制”,框架支持直接所有“页面输出”改为“json输出”。
|
||||
- 风格:遵循项目命名规范与 PSR;格式化以仓库根目录 `.php-cs-fixer.php` 配置为准(不假设本机已安装工具)
|
||||
- 权限:基于 `auth` 注解生成节点与鉴权;以角色为中心管理(角色、角色权限、用户角色);命令行使用见技能:[ulthon-permission-cli](./.agent/skills/ulthon-permission-cli/SKILL.md)
|
||||
- 临时文件:智能体在任务中产生的临时文件(脚本、日志、缓存、产物等)统一输出到 `runtime/agent/`(可按智能体/任务再分子目录),不要放在仓库根目录;除非任务明确要求或框架约定位置属于根目录
|
||||
- 调试与验证:框架内置了完善的功能验证能力,可以通过命令行实现数据库操作、控制器的请求(页面接口同体机制)、模拟用户请求(直接获得用户登录状态)、日志管理、菜单管理、权限管理等等,具体查看命令说明和agents技能。你可以利用这些机制直接实现功能的测试和验证,无需借助各类数据库MCP、命令行脚本等方式。
|
||||
|
||||
### 标准开发流程(Scheme + CURD,默认必须执行)
|
||||
|
||||
1. 初始化开发环境(必须提前完成)
|
||||
- 按项目文档完成:依赖安装、`.env` 配置、数据库连接与基础数据初始化、确保 `php think` 可用
|
||||
- 需要命令行联调与验证时,确保本地 CLI 与服务端使用同一套环境配置(尤其是缓存与数据库)
|
||||
2. 设计/调整表结构(Scheme)
|
||||
- 严格遵循表结构设计规范
|
||||
- 优先通过 Scheme 机制在 `app/admin/scheme/` 代码化维护表结构(避免 ENUM)
|
||||
3. 同步并确保一致(Scheme <-> DB)
|
||||
- 以代码为准(推荐):`php think scheme:sync`
|
||||
- 以数据库为准:`php think scheme:make -t {table}`
|
||||
- 生成 CURD 前,Scheme 与数据库结构必须完全一致,否则会被拒绝
|
||||
- 数据调试仅用于排错:`php think tools:db:*`(不要用来“设计表结构”)
|
||||
4. 生成基础代码(CURD),重要,只要是数据库表的功能开发,必须使用CURD机制生成代码。
|
||||
- 推荐先生成到临时目录确认:`php think curd -t {table} -r`
|
||||
- 确认无误后再正式生成/覆盖:`php think curd -t {table}` / `php think curd -t {table} -f`
|
||||
- 一旦开始在生成代码上做业务改造,后续结构变更默认走“临时生成 + 按需合并”,避免 `-f` 覆盖丢失业务修改
|
||||
- CURD 命令详解:<https://doc.ulthon.com/read/augushong/ulthon_admin/curd-command/zh-cn/2.x.html>
|
||||
5. 业务代码定制(仅改 `app/`)
|
||||
- 在生成代码基础上进行修改,仅操作 `app/` 目录
|
||||
- CURD 页面不仅包含列表表格页,还会生成若干配套页面/脚本;需要结合业务逐页检查与调整:按钮、字段展示、搜索项、表单校验、交互流程等
|
||||
- 如涉及接口输出或客户端(uni-app / Vue 等)调用,优先使用“页面 / 接口同体机制”,并确保同一路由在 HTML 与 JSON 两种模式下都符合预期
|
||||
6. 用内置 tools 与 CLI 完成功能验证(把增删改查跑通)
|
||||
- 用 `php think tools:http:call` 在命令行模拟已登录管理员请求,验证列表/详情/新增/编辑/删除链路与返回结构
|
||||
- 必要时用 `php think tools:log:*` 定位异常、追踪请求链路与数据变化
|
||||
7. 完成功能配套(按需)
|
||||
- 菜单管理:新增/调整菜单(`php think admin:menu:*`),并与页面路径/节点保持一致
|
||||
- 权限管理:基于 `auth` 注解生成/核对节点(`php think admin:permission:nodes`),为角色分配节点并为用户分配角色(`php think admin:role:*` / `php think admin:user:role:*`)
|
||||
- 数据库排错:必要时使用 `php think tools:db:*` 做只读检查或最小化变更
|
||||
8. 最终检查与交付(完成“新功能开发”的定义)
|
||||
- 后台页面:列表/表单/详情/删除等核心路径可用,交互与权限符合预期
|
||||
- 命令行验证:接口增删改查已通过 tools 命令跑通,关键异常可通过日志命令回溯
|
||||
- 代码规范:按仓库根目录 `.php-cs-fixer.php` 约束自查,避免引入不符合规范的写法
|
||||
|
||||
## 规则维护机制(框架基础 / 使用者补充)
|
||||
|
||||
本仓库的规则分两类:
|
||||
|
||||
- **框架基础规则(稳定)**:根目录 `AGENTS.md`(本文件)中的「项目级规则」为唯一权威;默认不随任务动态增长。
|
||||
- **使用者补充规则(业务侧)**:开发中用户/开发者补充的“项目规则/团队偏好/临时约束”,统一记录到 [.agent/AGENTS.md](./.agent/AGENTS.md)。
|
||||
|
||||
维护约束(必须遵守):
|
||||
|
||||
- 智能体以“框架作者”身份开发时,如需新增/调整规则,必须先与开发者确认是否记录、记录位置与具体写法,并按确认结果落到对应规则文件。
|
||||
- 智能体以“使用框架的开发者”身份执行任务时,如发现需要记录或调整的项目约束,应更新到对应规则文件(业务侧约束优先记录到 `.agent/AGENTS.md`),并可按开发者要求随时调整。
|
||||
|
||||
## 工作流(Skills)
|
||||
|
||||
Skills 是“按场景调用的工作流说明”,统一以 `.agent/skills/*/SKILL.md` 为准;`.trae/skills/` 为 Trae 集成的镜像目录(内容保持同步)。
|
||||
|
||||
- 扩展内置能力(继承与重写):[ulthon-core-extend-pattern](./.agent/skills/ulthon-core-extend-pattern/SKILL.md)
|
||||
- Base/App 架构指南:[ulthon-base-app-architecture](./.agent/skills/ulthon-base-app-architecture/SKILL.md)
|
||||
- CLI 命令参考文档:[ulthon-cli-reference](./.agent/skills/ulthon-cli-reference/SKILL.md)
|
||||
- Scheme + CURD 工作流:[ulthon-scheme-curd-workflow](./.agent/skills/ulthon-scheme-curd-workflow/SKILL.md)
|
||||
- Scheme 定义指南:[ulthon-scheme-definition](./.agent/skills/ulthon-scheme-definition/SKILL.md)
|
||||
- 数据库调试命令(tools:db):[ulthon-db-tools-debug](./.agent/skills/ulthon-db-tools-debug/SKILL.md)
|
||||
- HTTP 调用工具(tools:http:call):[ulthon-tools-http-call](./.agent/skills/ulthon-tools-http-call/SKILL.md)
|
||||
- 日志命令(tools:log):[ulthon-tools-log](./.agent/skills/ulthon-tools-log/SKILL.md)
|
||||
- 内置定时器与定时任务扩展:[ulthon-timer](./.agent/skills/ulthon-timer/SKILL.md)
|
||||
- 页面 / 接口同体:[ulthon-page-api-dual-mode](./.agent/skills/ulthon-page-api-dual-mode/SKILL.md)
|
||||
- 登录认证(Session + Token):[ulthon-auth-session-token](./.agent/skills/ulthon-auth-session-token/SKILL.md)
|
||||
- 权限与角色管理(RBAC CLI):[ulthon-permission-cli](./.agent/skills/ulthon-permission-cli/SKILL.md)
|
||||
- 菜单管理(admin:menu:\* CLI):[ulthon-admin-menu-cli](./.agent/skills/ulthon-admin-menu-cli/SKILL.md)
|
||||
- ThinkPHP 控制器 URL 规则:[tp-controller-url-rules](./.agent/skills/tp-controller-url-rules/SKILL.md)
|
||||
|
||||
## 智能体指导
|
||||
|
||||
使用命令可以将内置智能体规则应用到各类智能体中。设计规则时以 AGENTS.md 和 `.agent/` 目录为主。
|
||||
|
||||
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/ # 多端客户端代码(uni-app、Vue)
|
||||
│ └── clients/uniapp/ # uni-app 前端工程
|
||||
└── .agent/ # 智能体技能与规则
|
||||
```
|
||||
|
||||
## 快速命令参考
|
||||
|
||||
| 场景 | 命令 |
|
||||
| ---------------- | --------------------------------------- |
|
||||
| 同步表结构(代码→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` |
|
||||
|
||||
命令行通用参数:多数命令支持 `--force-force`(`-ff`)跳过交互确认。
|
||||
|
||||
141
CODERULE.md
141
CODERULE.md
@@ -1,141 +0,0 @@
|
||||
# Ulthon Admin 开发规则手册
|
||||
|
||||
本文档分为三部分:
|
||||
1. **通用基础规范**:所有开发者(框架作者与业务开发者)均需遵守的基础准则。
|
||||
2. **框架开发规则**:针对框架本身的维护者和贡献者。
|
||||
3. **业务开发规则**:针对使用框架开发具体业务系统的开发者。
|
||||
|
||||
---
|
||||
|
||||
## 第一部分:通用基础规范
|
||||
> 适用对象:所有开发者(必须遵守)
|
||||
|
||||
### 1. 技术栈标准
|
||||
|
||||
- **核心框架**: ThinkPHP 8.0
|
||||
- **开发语言**: PHP 8+
|
||||
- **数据库**: MySQL 8+
|
||||
- **前端框架**: Layui 2.8.6
|
||||
- **模板引擎**: ThinkPHP 内置模板引擎
|
||||
- **代码生成器**: 自定义命令行工具
|
||||
|
||||
### 2. 命名与代码规范
|
||||
|
||||
所有代码(无论是核心框架还是业务代码)必须严格遵守以下规范:
|
||||
|
||||
- **命名规范**: [官方命名规范文档](https://doc.ulthon.com/read/augushong/ulthon_admin/64fbcf8830640/zh-cn/2.x.html)
|
||||
- 涵盖:类名、方法名、变量名、目录名等。
|
||||
- **代码风格**: [官方代码规范文档 (PHP-CS-Fixer)](https://doc.ulthon.com/read/augushong/ulthon_admin/64360c249d66a/zh-cn/2.x.html)
|
||||
- 统一使用 PSR 标准,建议配置自动格式化工具。
|
||||
|
||||
### 3. 数据库设计规范
|
||||
|
||||
- **设计文档**: [官方表结构设计规范](https://doc.ulthon.com/read/augushong/ulthon_admin/619efc9d7af62/zh-cn/2.x.html)
|
||||
- **核心要求**:
|
||||
- 表名小写,使用下划线分隔。
|
||||
- 按照前缀、模块、功能的顺序命名,例如:`ul_mall_goods`。
|
||||
- 必须为所有字段编写清晰、完整的注释。
|
||||
- 严格遵循文档中的字段类型定义。
|
||||
|
||||
### 4.在线文档
|
||||
|
||||
- **项目文档**: [Ulthon Admin 项目文档](https://doc.ulthon.com/read/augushong/ulthon_admin/home/zh-cn/2.x.html)
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:框架开发规则
|
||||
> 适用对象:框架作者、核心贡献者
|
||||
|
||||
### 1. 架构设计理念
|
||||
|
||||
#### 1.1 双层架构核心
|
||||
Ulthon Admin 采用独特的双层架构设计,其中 **基础核心层 (`extend/base/`)** 是框架的基石:
|
||||
|
||||
- **职责**: 存放系统内置的所有核心功能代码,提供标准化的类和接口。
|
||||
- **原则**: 保持高度稳定,**严禁依赖具体的业务逻辑**。
|
||||
- **框架代码写到 `extend/base/` 目录下**。但所有的调用都是从app目录下开始的。即便是纯框架要用到的代码,也是从app目录下开始调用的。
|
||||
- 比如base层有一个 AuthServiceBase 类,在app目录下有一个 AuthService 类,它继承了 AuthServiceBase 类。实际调用时,是从app目录下调用 AuthService 类的方法,而不是从base目录下调用 AuthServiceBase 类的方法。
|
||||
- 这样做的好处是,业务代码可以根据需要自由扩展和定制,而不会受到框架核心的影响。
|
||||
|
||||
### 2. 核心层维护原则
|
||||
|
||||
- **稳定性优先**: 核心层的任何修改都必须保证向下兼容。
|
||||
- **通用性**: 核心代码应具有高度的抽象性,不应包含特定项目的业务逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 第三部分:业务系统开发规则
|
||||
> 适用对象:业务功能开发者
|
||||
|
||||
### 1. 开发红线与原则
|
||||
|
||||
- **核心保护**: 严禁修改 `extend/base/` 目录下的任何文件。
|
||||
- **业务隔离**: 所有业务逻辑必须放在 `app/` 目录下。
|
||||
|
||||
### 2. 扩展机制:继承与重写
|
||||
|
||||
当需要修改或扩展框架内置功能(如管理员管理、菜单权限)时,请遵循以下机制:
|
||||
|
||||
- **继承**: 业务类继承 `extend/base/` 中的基础类。
|
||||
- **重写**: 在业务类中重写父类方法,保持方法签名一致。
|
||||
- **调用父类**: 使用 `parent::methodName()` 复用原有逻辑,或者从父类复制代码重新实现。
|
||||
- **注意**: 全新开发的业务模块不受此限制,直接在 `app/` 下开发即可。
|
||||
|
||||
### 3. 标准开发流程
|
||||
|
||||
1. **设计/调整表结构**
|
||||
- 严格遵循【第一部分:通用基础规范】中的数据库设计规范。
|
||||
- 优先通过 Scheme 机制在 `app/admin/scheme/` 代码化维护表结构。
|
||||
|
||||
2. **同步并确保一致**
|
||||
- 以代码为准:`scheme:sync`
|
||||
- 以数据库为准:`scheme:make -t {table}`
|
||||
- **注意**:生成 CURD 前,Scheme 与数据库结构必须完全一致,否则会被拒绝。
|
||||
|
||||
3. **生成基础代码(CURD)**
|
||||
- 推荐先生成到临时目录确认:`php think curd -t {table} -r`
|
||||
- 确认无误后再正式生成/覆盖:`php think curd -t {table}` / `php think curd -t {table} -f`
|
||||
|
||||
4. **业务代码定制**
|
||||
- 在生成代码基础上进行修改,仅操作 `app/` 目录。
|
||||
|
||||
5. **测试与验证**
|
||||
- 功能测试、数据完整性检查、代码规范检查(遵循项目 PHP-CS-Fixer 配置)。
|
||||
|
||||
### 4. 常用开发工具
|
||||
|
||||
#### 4.1 代码生成命令 (CURD)
|
||||
> 文档: [CURD 命令详解](https://doc.ulthon.com/read/augushong/ulthon_admin/curd-command/zh-cn/2.x.html)
|
||||
|
||||
更完整的命令组合与参数说明,优先以在线文档为准;仓库内的工作流摘要可参考 `.trae/skills/ulthon-scheme-curd-workflow/`。
|
||||
|
||||
### 5. 前端开发规范
|
||||
|
||||
#### 5.1 文件组织
|
||||
每个视图 HTML 文件应搭配一个同名的 JS 文件,并按模块维护 `_common.js`,结构示例:
|
||||
|
||||
```text
|
||||
goods/
|
||||
├── add.html <-- 视图
|
||||
├── add.js <-- 对应逻辑
|
||||
├── index.html
|
||||
├── index.js
|
||||
└── _common.js <-- 模块通用逻辑
|
||||
```
|
||||
|
||||
这种结构确保了逻辑与视图的解耦,便于维护。
|
||||
|
||||
### 6. Scheme 机制(数据库代码化)
|
||||
|
||||
Ulthon Admin 引入了 Scheme 层,实现数据库结构与 PHP 代码的双向同步,便于版本控制和快速迁移。
|
||||
|
||||
#### 6.1 核心概念
|
||||
- **Code to DB (`scheme:sync`)**: 通过编写 PHP 类定义表结构,自动同步到数据库(支持备份原表)。
|
||||
- **DB to Code (`scheme:make`)**: 读取现有数据库表结构,反向生成 PHP Scheme 类。
|
||||
|
||||
#### 6.2 目录规范
|
||||
- **业务 Scheme**: `app/admin/scheme/` (所有生成的业务表结构类存放在此)
|
||||
|
||||
#### 6.3 参考资料
|
||||
|
||||
- 标准工作流摘要:`.trae/skills/ulthon-scheme-curd-workflow/SKILL.md`
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use base\admin\controller\system\ConfigBase;
|
||||
|
||||
/**
|
||||
* Class Config.
|
||||
* @ControllerAnnotation(title="系统配置管理",module="系统")
|
||||
*/
|
||||
class Config extends ConfigBase
|
||||
{
|
||||
|
||||
@@ -7,3 +7,4 @@ use base\admin\model\SystemConfigBase;
|
||||
class SystemConfig extends SystemConfigBase
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_mall_cate', comment: '')]
|
||||
#[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
|
||||
@@ -23,7 +23,7 @@ class MallCate extends BaseScheme
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $image;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序')]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '2', comment: '状态')]
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_mall_goods', comment: '')]
|
||||
#[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
|
||||
@@ -25,7 +25,7 @@ class MallGoods extends BaseScheme
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '商品标签')]
|
||||
public $tag;
|
||||
|
||||
#[Field(type: 'char', length: 255, precision: 255, comment: '商品logo')]
|
||||
#[Field(type: 'char', length: 255, precision: 255, default: '', comment: '商品logo')]
|
||||
#[Component(type: 'image', options: [])]
|
||||
public $logo;
|
||||
|
||||
@@ -40,10 +40,10 @@ class MallGoods extends BaseScheme
|
||||
#[Field(type: 'text', comment: '商品属性')]
|
||||
public $property;
|
||||
|
||||
#[Field(type: 'decimal', length: 8, precision: 8, default: '0', comment: '市场价')]
|
||||
#[Field(type: 'decimal', length: 8, precision: 8, scale: 2, default: '0.00', comment: '市场价')]
|
||||
public $market_price;
|
||||
|
||||
#[Field(type: 'decimal', length: 8, precision: 8, default: '0', comment: '折扣价')]
|
||||
#[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)]
|
||||
@@ -58,7 +58,7 @@ class MallGoods extends BaseScheme
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '总库存', unsigned: true)]
|
||||
public $total_stock;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序', unsigned: true)]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序', unsigned: true)]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '状态', unsigned: true)]
|
||||
@@ -77,10 +77,10 @@ class MallGoods extends BaseScheme
|
||||
#[Field(type: 'int', length: 11, default: '0', unsigned: true)]
|
||||
public $delete_time;
|
||||
|
||||
#[Field(length: 300, precision: 300, nullable: false, default: '')]
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '')]
|
||||
public $license;
|
||||
|
||||
#[Field(length: 300, precision: 300, nullable: false, default: '')]
|
||||
#[Field(length: 100, precision: 100, nullable: false, default: '')]
|
||||
public $license_name;
|
||||
|
||||
#[Field(type: 'text', comment: '属性(静态字段)')]
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_mall_tag', comment: '')]
|
||||
#[Table(name: 'ul_mall_tag', comment: '商品标签')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class MallTag extends BaseScheme
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_admin', comment: '')]
|
||||
#[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')]
|
||||
@@ -38,7 +38,7 @@ class SystemAdmin extends BaseScheme
|
||||
#[Field(type: 'bigint', length: 11, default: '0', comment: '登录次数', unsigned: true)]
|
||||
public $login_num;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序')]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态', unsigned: true)]
|
||||
|
||||
@@ -8,9 +8,8 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_auth', comment: '')]
|
||||
#[Table(name: 'ul_system_auth', comment: '系统权限表')]
|
||||
#[Index(columns: ['title'], name: 'title', type: 'UNIQUE')]
|
||||
#[Index(columns: ['title'], name: 'title_2', type: 'NORMAL')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemAuth extends BaseScheme
|
||||
{
|
||||
@@ -20,7 +19,7 @@ class SystemAuth extends BaseScheme
|
||||
#[Field(type: 'char', length: 20, precision: 20, comment: '权限名称')]
|
||||
public $title;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序')]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '状态')]
|
||||
|
||||
@@ -8,9 +8,8 @@ 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: '')]
|
||||
#[Table(name: 'ul_system_auth_node', comment: '角色与节点关系表')]
|
||||
#[Index(columns: ['auth_id'], name: 'auth_id', type: 'NORMAL')]
|
||||
#[Index(columns: ['node_id'], name: 'node_id', type: 'NORMAL')]
|
||||
class SystemAuthNode extends BaseScheme
|
||||
{
|
||||
#[Field(type: 'int', length: 11, nullable: false, unsigned: true, autoIncrement: true, primary: true)]
|
||||
@@ -19,9 +18,6 @@ class SystemAuthNode extends BaseScheme
|
||||
#[Field(type: 'bigint', length: 11, comment: '角色ID', unsigned: true)]
|
||||
public $auth_id;
|
||||
|
||||
#[Field(type: 'bigint', length: 11, comment: '节点ID', unsigned: true)]
|
||||
public $node_id;
|
||||
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '')]
|
||||
public $node;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_config', comment: '')]
|
||||
#[Table(name: 'ul_system_config', comment: '系统配置表')]
|
||||
#[Index(columns: ['name'], name: 'name', type: 'NORMAL')]
|
||||
#[Index(columns: ['group'], name: 'group', type: 'NORMAL')]
|
||||
class SystemConfig extends BaseScheme
|
||||
@@ -28,7 +28,7 @@ class SystemConfig extends BaseScheme
|
||||
#[Field(type: 'char', length: 100, precision: 100, default: '', comment: '备注信息')]
|
||||
public $remark;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序')]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '创建时间', unsigned: true)]
|
||||
|
||||
65
app/admin/scheme/SystemHost.php
Normal file
65
app/admin/scheme/SystemHost.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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: '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;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_menu', comment: '')]
|
||||
#[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')]
|
||||
@@ -38,7 +38,7 @@ class SystemMenu extends BaseScheme
|
||||
#[Field(type: 'char', length: 20, precision: 20, default: '_self', comment: '链接打开方式')]
|
||||
public $target;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序')]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态')]
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_quick', comment: '')]
|
||||
#[Table(name: 'ul_system_quick', comment: '系统快捷入口表')]
|
||||
#[Index(columns: ['delete_time'], name: 'delete_time', type: 'NORMAL')]
|
||||
class SystemQuick extends BaseScheme
|
||||
{
|
||||
@@ -24,7 +24,7 @@ class SystemQuick extends BaseScheme
|
||||
#[Field(type: 'char', length: 255, precision: 255, comment: '快捷链接')]
|
||||
public $href;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '0', comment: '排序')]
|
||||
#[Field(type: 'int', length: 11, default: '100', comment: '排序')]
|
||||
public $sort;
|
||||
|
||||
#[Field(type: 'int', length: 11, default: '1', comment: '状态', unsigned: true)]
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\scheme\attribute\Field;
|
||||
use app\common\scheme\attribute\Component;
|
||||
use app\common\scheme\attribute\Index;
|
||||
|
||||
#[Table(name: 'ul_system_uploadfile', comment: '')]
|
||||
#[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')]
|
||||
|
||||
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;
|
||||
}
|
||||
14
app/common/command/admin/menu/AdminMenuCreate.php
Normal file
14
app/common/command/admin/menu/AdminMenuCreate.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\menu;
|
||||
|
||||
use base\common\command\admin\menu\AdminMenuCreateBase;
|
||||
|
||||
/**
|
||||
* admin:menu:create command - 创建菜单
|
||||
*/
|
||||
class AdminMenuCreate extends AdminMenuCreateBase
|
||||
{
|
||||
}
|
||||
14
app/common/command/admin/menu/AdminMenuDelete.php
Normal file
14
app/common/command/admin/menu/AdminMenuDelete.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\menu;
|
||||
|
||||
use base\common\command\admin\menu\AdminMenuDeleteBase;
|
||||
|
||||
/**
|
||||
* admin:menu:delete command - 删除菜单
|
||||
*/
|
||||
class AdminMenuDelete extends AdminMenuDeleteBase
|
||||
{
|
||||
}
|
||||
14
app/common/command/admin/menu/AdminMenuExport.php
Normal file
14
app/common/command/admin/menu/AdminMenuExport.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\menu;
|
||||
|
||||
use base\common\command\admin\menu\AdminMenuExportBase;
|
||||
|
||||
/**
|
||||
* admin:menu:export command - 导出菜单
|
||||
*/
|
||||
class AdminMenuExport extends AdminMenuExportBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/menu/AdminMenuList.php
Normal file
11
app/common/command/admin/menu/AdminMenuList.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\menu;
|
||||
|
||||
use base\common\command\admin\menu\AdminMenuListBase;
|
||||
|
||||
class AdminMenuList extends AdminMenuListBase
|
||||
{
|
||||
}
|
||||
14
app/common/command/admin/menu/AdminMenuUpdate.php
Normal file
14
app/common/command/admin/menu/AdminMenuUpdate.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\menu;
|
||||
|
||||
use base\common\command\admin\menu\AdminMenuUpdateBase;
|
||||
|
||||
/**
|
||||
* admin:menu:update command - 编辑菜单
|
||||
*/
|
||||
class AdminMenuUpdate extends AdminMenuUpdateBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/permission/AdminPermissionNodes.php
Normal file
11
app/common/command/admin/permission/AdminPermissionNodes.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\permission;
|
||||
|
||||
use base\common\command\admin\permission\AdminPermissionNodesBase;
|
||||
|
||||
class AdminPermissionNodes extends AdminPermissionNodesBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/permission/PermissionUser.php
Normal file
11
app/common/command/admin/permission/PermissionUser.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\permission;
|
||||
|
||||
use base\common\command\admin\permission\AdminPermissionUserBase;
|
||||
|
||||
class PermissionUser extends AdminPermissionUserBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRoleCreate.php
Normal file
11
app/common/command/admin/role/AdminRoleCreate.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRoleCreateBase;
|
||||
|
||||
class AdminRoleCreate extends AdminRoleCreateBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRoleDelete.php
Normal file
11
app/common/command/admin/role/AdminRoleDelete.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRoleDeleteBase;
|
||||
|
||||
class AdminRoleDelete extends AdminRoleDeleteBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRoleInfo.php
Normal file
11
app/common/command/admin/role/AdminRoleInfo.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRoleInfoBase;
|
||||
|
||||
class AdminRoleInfo extends AdminRoleInfoBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRoleList.php
Normal file
11
app/common/command/admin/role/AdminRoleList.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRoleListBase;
|
||||
|
||||
class AdminRoleList extends AdminRoleListBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRolePermissionAssign.php
Normal file
11
app/common/command/admin/role/AdminRolePermissionAssign.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRolePermissionAssignBase;
|
||||
|
||||
class AdminRolePermissionAssign extends AdminRolePermissionAssignBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRolePermissionList.php
Normal file
11
app/common/command/admin/role/AdminRolePermissionList.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRolePermissionListBase;
|
||||
|
||||
class AdminRolePermissionList extends AdminRolePermissionListBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/role/AdminRolePermissionRevoke.php
Normal file
11
app/common/command/admin/role/AdminRolePermissionRevoke.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\role;
|
||||
|
||||
use base\common\command\admin\role\AdminRolePermissionRevokeBase;
|
||||
|
||||
class AdminRolePermissionRevoke extends AdminRolePermissionRevokeBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/user/AdminUserRoleAssign.php
Normal file
11
app/common/command/admin/user/AdminUserRoleAssign.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\user;
|
||||
|
||||
use base\common\command\admin\user\AdminUserRoleAssignBase;
|
||||
|
||||
class AdminUserRoleAssign extends AdminUserRoleAssignBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/user/AdminUserRoleList.php
Normal file
11
app/common/command/admin/user/AdminUserRoleList.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\user;
|
||||
|
||||
use base\common\command\admin\user\AdminUserRoleListBase;
|
||||
|
||||
class AdminUserRoleList extends AdminUserRoleListBase
|
||||
{
|
||||
}
|
||||
11
app/common/command/admin/user/AdminUserRoleRevoke.php
Normal file
11
app/common/command/admin/user/AdminUserRoleRevoke.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command\admin\user;
|
||||
|
||||
use base\common\command\admin\user\AdminUserRoleRevokeBase;
|
||||
|
||||
class AdminUserRoleRevoke extends AdminUserRoleRevokeBase
|
||||
{
|
||||
}
|
||||
10
app/common/command/tools/agent/ToolsAgentPublish.php
Normal file
10
app/common/command/tools/agent/ToolsAgentPublish.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command\tools\agent;
|
||||
|
||||
use base\common\command\tools\agent\ToolsAgentPublishBase;
|
||||
|
||||
class ToolsAgentPublish extends ToolsAgentPublishBase
|
||||
{
|
||||
}
|
||||
|
||||
13
app/common/command/tools/http/ToolsHttpCall.php
Normal file
13
app/common/command/tools/http/ToolsHttpCall.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command\tools\http;
|
||||
|
||||
use base\common\command\tools\http\ToolsHttpCallBase;
|
||||
|
||||
class ToolsHttpCall extends ToolsHttpCallBase
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
}
|
||||
}
|
||||
9
app/common/command/tools/log/ToolsLogSearch.php
Normal file
9
app/common/command/tools/log/ToolsLogSearch.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command\tools\log;
|
||||
|
||||
use base\common\command\tools\log\ToolsLogSearchBase;
|
||||
|
||||
class ToolsLogSearch extends ToolsLogSearchBase
|
||||
{
|
||||
}
|
||||
9
app/common/command/tools/log/ToolsLogShow.php
Normal file
9
app/common/command/tools/log/ToolsLogShow.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command\tools\log;
|
||||
|
||||
use base\common\command\tools\log\ToolsLogShowBase;
|
||||
|
||||
class ToolsLogShow extends ToolsLogShowBase
|
||||
{
|
||||
}
|
||||
9
app/common/command/tools/log/ToolsLogStats.php
Normal file
9
app/common/command/tools/log/ToolsLogStats.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\command\tools\log;
|
||||
|
||||
use base\common\command\tools\log\ToolsLogStatsBase;
|
||||
|
||||
class ToolsLogStats extends ToolsLogStatsBase
|
||||
{
|
||||
}
|
||||
18
app/common/console/Command.php
Normal file
18
app/common/console/Command.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\console;
|
||||
|
||||
use base\common\command\CommandBase;
|
||||
|
||||
/**
|
||||
* Command 命令基类(app 层入口)
|
||||
*
|
||||
* 这是所有命令类使用的 CommandBase 的唯一调用入口。
|
||||
* 对应 ThinkPHP 的 console 目录结构(Input、Output 都在 console 下)
|
||||
*
|
||||
* 所有 extend/base 层的命令应该使用:use app\common\console\Command;
|
||||
*/
|
||||
class Command extends CommandBase
|
||||
{
|
||||
}
|
||||
|
||||
15
app/common/service/tools/DbService.php
Normal file
15
app/common/service/tools/DbService.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service\tools;
|
||||
|
||||
use base\common\service\tools\DbServiceBase;
|
||||
|
||||
/**
|
||||
* Db 服务类(app 层入口)
|
||||
*
|
||||
* 这是 tools:db 命令使用的数据库工具服务的唯一调用入口。
|
||||
* 空类继承 Base,确保业务层可以在 app/ 中扩展此服务。
|
||||
*/
|
||||
class DbService extends DbServiceBase
|
||||
{
|
||||
}
|
||||
@@ -5,5 +5,6 @@
|
||||
use app\common\provider\Console;
|
||||
|
||||
return [
|
||||
'think\Console'=>Console::class
|
||||
// 注意,必须在此处注册ConsoleProvider,否则命令行控制台无法正常工作
|
||||
'think\Console'=>Console::class
|
||||
];
|
||||
|
||||
@@ -3,32 +3,8 @@
|
||||
// | 控制台配置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use app\common\command\admin\Clear;
|
||||
use app\common\command\admin\Version;
|
||||
use app\common\command\admin\ResetPassword;
|
||||
use app\common\command\admin\Update;
|
||||
use app\common\command\admin\UpdateCode;
|
||||
use app\common\command\curd\Migrate;
|
||||
use app\common\command\Timer;
|
||||
use app\common\command\scheme\Make;
|
||||
use app\common\command\scheme\Sync;
|
||||
use app\common\command\scheme\Backup;
|
||||
|
||||
return [
|
||||
// 指令定义
|
||||
'commands' => [
|
||||
'curd' => 'app\common\command\Curd',
|
||||
'node' => 'app\common\command\Node',
|
||||
'OssStatic' => 'app\common\command\OssStatic',
|
||||
ResetPassword::class,
|
||||
Timer::class,
|
||||
Version::class,
|
||||
Migrate::class,
|
||||
Clear::class,
|
||||
Update::class,
|
||||
UpdateCode::class,
|
||||
Make::class,
|
||||
Sync::class,
|
||||
Backup::class,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -21,11 +21,12 @@ class AjaxBase extends AdminController
|
||||
*/
|
||||
public function initAdmin()
|
||||
{
|
||||
$cacheData = Cache::get('initAdmin_' . $this->sessionAdmin->id);
|
||||
$adminId = $this->getAdminId();
|
||||
$cacheData = Cache::get('initAdmin_' . $adminId);
|
||||
if (!empty($cacheData)) {
|
||||
return json($cacheData);
|
||||
}
|
||||
$menuService = new MenuService($this->sessionAdmin->id);
|
||||
$menuService = new MenuService($adminId);
|
||||
$data = [
|
||||
'logoInfo' => [
|
||||
'title' => sysconfig('site', 'logo_title'),
|
||||
@@ -35,7 +36,7 @@ class AjaxBase extends AdminController
|
||||
'homeInfo' => $menuService->getHomeInfo(),
|
||||
'menuInfo' => $menuService->getMenuTree(),
|
||||
];
|
||||
Cache::tag('initAdmin')->set('initAdmin_' . $this->sessionAdmin->id, $data);
|
||||
Cache::tag('initAdmin')->set('initAdmin_' . $adminId, $data);
|
||||
|
||||
return json($data);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class IndexBase extends AdminController
|
||||
*/
|
||||
public function editAdmin()
|
||||
{
|
||||
$id = $this->sessionAdmin->id;
|
||||
$id = $this->getAdminId();
|
||||
$row = (new SystemAdmin())
|
||||
->withoutField('password')
|
||||
->find($id);
|
||||
@@ -96,7 +96,7 @@ class IndexBase extends AdminController
|
||||
*/
|
||||
public function editPassword()
|
||||
{
|
||||
$id = $this->sessionAdmin->id;
|
||||
$id = $this->getAdminId();
|
||||
$row = (new SystemAdmin())
|
||||
->withoutField('password')
|
||||
->find($id);
|
||||
@@ -140,7 +140,7 @@ class IndexBase extends AdminController
|
||||
{
|
||||
$pid = $this->request->param('pid', 0);
|
||||
|
||||
$menuService = new MenuService($this->sessionAdmin->id);
|
||||
$menuService = new MenuService($this->getAdminId());
|
||||
|
||||
$home_info = $menuService->getHomeInfo();
|
||||
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:商城分类演示数据初始化
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_mall_cate = array(
|
||||
array(
|
||||
"title" => "手机",
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:商城商品演示数据初始化
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_mall_goods = array(
|
||||
array(
|
||||
"title" => "落地-风扇",
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:商城标签演示数据初始化
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_mall_tag = array(
|
||||
array(
|
||||
"id" => 1,
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:系统角色初始化
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_system_auth = array(
|
||||
array(
|
||||
"id" => 1,
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:系统权限节点初始化
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_system_auth_node = array(
|
||||
|
||||
);
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:系统配置初始化(包括上传、短信、站点、微信、OSS等配置项)
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_system_config = array(
|
||||
array(
|
||||
"name" => "alisms_access_key_id",
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:系统菜单初始化(系统管理、商城管理等菜单结构)
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_system_menu = array(
|
||||
array(
|
||||
"id" => 227,
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架核心初始化数据
|
||||
*
|
||||
* 用途:系统快捷入口初始化
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
$ul_system_quick = array(
|
||||
array(
|
||||
"title" => "管理员管理",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* @internal-framework
|
||||
*
|
||||
* 此文件为框架内置功能
|
||||
*
|
||||
* 用途:框架版本更新代码(v2.0.74)- JS代码属性名称替换
|
||||
* 维护者:框架维护者
|
||||
*
|
||||
* 注意:此文件属于框架内核,业务开发者不应修改
|
||||
*/
|
||||
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
|
||||
@@ -263,6 +263,11 @@ class BuildCurdServiceBase
|
||||
{
|
||||
$this->table = $table;
|
||||
|
||||
// 自动检测并处理表前缀
|
||||
// 支持用户输入带前缀(ul_daka_record)或不带前缀(daka_record)两种方式
|
||||
// 注意:tablePrefix 可能已包含下划线(如 'ul_'),需要先检测
|
||||
$this->table = $this->stripTablePrefix($this->table);
|
||||
|
||||
$schemeClass = 'app\\admin\\scheme\\' . Str::studly($this->table);
|
||||
if (!class_exists($schemeClass)) {
|
||||
throw new TableException("未找到 {$schemeClass},请先执行:php think scheme:make -t {$this->table} 或手动创建 Scheme");
|
||||
@@ -352,6 +357,28 @@ class BuildCurdServiceBase
|
||||
return $controllerFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除表前缀.
|
||||
* @param string $tableName
|
||||
* @return string
|
||||
*/
|
||||
protected function stripTablePrefix($tableName)
|
||||
{
|
||||
$prefixToStrip = $this->tablePrefix;
|
||||
|
||||
// 如果 tablePrefix 不以 _ 结尾,加上下划线
|
||||
if (!str_ends_with($prefixToStrip, '_')) {
|
||||
$prefixToStrip .= '_';
|
||||
}
|
||||
|
||||
if (str_starts_with($tableName, $prefixToStrip)) {
|
||||
// 用户输入了带前缀的表名,自动去除前缀
|
||||
return substr($tableName, strlen($prefixToStrip));
|
||||
}
|
||||
|
||||
return $tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联表.
|
||||
* @param $relationTable
|
||||
@@ -365,6 +392,9 @@ class BuildCurdServiceBase
|
||||
*/
|
||||
public function setRelation($relationTable, $foreignKey, $primaryKey = null, $modelFilename = null, $onlyShowFileds = [], $bindSelectField = null)
|
||||
{
|
||||
// 自动检测并处理关联表前缀(与 setTable 保持一致)
|
||||
$relationTable = $this->stripTablePrefix($relationTable);
|
||||
|
||||
if (!isset($this->tableColumns[$foreignKey])) {
|
||||
throw new TableException("主表不存在外键字段:{$foreignKey}");
|
||||
}
|
||||
|
||||
23
extend/base/common/command/CommandBase.php
Normal file
23
extend/base/common/command/CommandBase.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use think\console\Command;
|
||||
|
||||
/**
|
||||
* 命令基类(extend/base 层)
|
||||
*
|
||||
* 提供所有命令的基础功能
|
||||
* 命令行(CLI)只使用文本输出
|
||||
* JSON 输出由 Web API 统一处理
|
||||
*/
|
||||
abstract class CommandBase extends Command
|
||||
{
|
||||
/**
|
||||
* 配置命令参数
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace base\common\command;
|
||||
|
||||
use app\admin\service\curd\BuildCurdService;
|
||||
use app\common\console\Command;
|
||||
use app\common\tools\PathTools;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
@@ -15,6 +15,8 @@ class CurdBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('curd')
|
||||
->addOption('table', 't', Option::VALUE_REQUIRED, '主表名', null)
|
||||
->addOption('controllerFilename', 'c', Option::VALUE_REQUIRED, '控制器文件名', null)
|
||||
@@ -22,12 +24,55 @@ class CurdBase extends Command
|
||||
//
|
||||
->addOption('force', 'f', Option::VALUE_NONE, '强制覆盖模式')
|
||||
->addOption('delete', 'd', Option::VALUE_NONE, '删除模式')
|
||||
->addOption('runtime', 'r', Option::VALUE_NONE, '临时生成')
|
||||
->setDescription('一键curd命令服务');
|
||||
->addOption('runtime', 'r', Option::VALUE_NONE, '临时生成(生成到 runtime 目录)')
|
||||
->addOption('examples', null, Option::VALUE_NONE, '显示使用示例')
|
||||
->setDescription('一键生成 CURD 代码(控制器、模型、视图、JS)');
|
||||
}
|
||||
|
||||
protected function getExamplesText(): string
|
||||
{
|
||||
return <<<'EXAMPLES'
|
||||
<info>CURD 命令使用示例</info>
|
||||
|
||||
<comment>示例 1:首次生成(基本用法)</comment>
|
||||
<info>php think curd -t daka_record</info>
|
||||
说明:直接生成到项目目录,如文件已存在会跳过
|
||||
|
||||
<comment>示例 2:增量更新(已有业务代码)</comment>
|
||||
<info>php think curd -t daka_record -r</info>
|
||||
说明:生成到 runtime 临时目录,用于手动对比并合并新增字段代码(避免覆盖业务逻辑)
|
||||
|
||||
<comment>示例 3:带前缀的表名</comment>
|
||||
<info>php think curd -t ul_daka_record</info>
|
||||
说明:自动识别并去除前缀
|
||||
|
||||
<comment>示例 4:强制覆盖(谨慎使用)</comment>
|
||||
<info>php think curd -t daka_record -f</info>
|
||||
说明:覆盖已存在的文件,<error>会丢失手动修改的内容</error>
|
||||
|
||||
<comment>示例 5:删除已生成的文件</comment>
|
||||
<info>php think curd -t daka_record -d</info>
|
||||
说明:删除之前生成的所有文件(会要求确认)
|
||||
注意:<error>这是删除文件,不是删除数据库记录</error>
|
||||
|
||||
<comment>常见错误及解决:</comment>
|
||||
错误:表不存在
|
||||
→ 先创建表:php think scheme:sync
|
||||
→ 或使用迁移:php think migrate:run
|
||||
|
||||
错误:Scheme 与数据库不一致
|
||||
→ 同步 Scheme:php think scheme:sync
|
||||
EXAMPLES;
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 显示使用示例
|
||||
if ($input->hasOption('examples')) {
|
||||
$output->writeln($this->getExamplesText());
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = $input->getOption('table');
|
||||
$controllerFilename = $input->getOption('controllerFilename');
|
||||
$modelFilename = $input->getOption('modelFilename');
|
||||
@@ -49,11 +94,15 @@ class CurdBase extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集警告信息
|
||||
$warnings = [];
|
||||
|
||||
try {
|
||||
$build = (new BuildCurdService())
|
||||
->setTable($table)
|
||||
->setForce($force);
|
||||
|
||||
$runtime_path = '';
|
||||
if ($input->hasOption('runtime')) {
|
||||
$runtime_path = App::getRuntimePath() . 'source' . DS . 'build' . DS . date('YmdHis') . DS;
|
||||
PathTools::intiDir($runtime_path . 'a.temp');
|
||||
@@ -70,8 +119,9 @@ class CurdBase extends Command
|
||||
$define = $column['define'];
|
||||
|
||||
if (!isset($define['table'])) {
|
||||
$output->error("关联字段{$field}没有设置关联表名称");
|
||||
|
||||
$error = "关联字段{$field}没有设置关联表名称";
|
||||
$output->error($error);
|
||||
$warnings[] = ['message' => $error];
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -96,6 +146,7 @@ class CurdBase extends Command
|
||||
$build = $build->render();
|
||||
$fileList = $build->getFileList();
|
||||
|
||||
$result = [];
|
||||
if (!$delete) {
|
||||
if ($force) {
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
@@ -104,10 +155,9 @@ class CurdBase extends Command
|
||||
}
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
|
||||
$ask_force_delete_result = $output->confirm($input, '确定强制生成上方所有文件? 如果文件存在会直接覆盖。');
|
||||
|
||||
if (!$ask_force_delete_result) {
|
||||
throw new Exception('取消文件CURD生成操作');
|
||||
if (!$output->confirm($input, '确定强制生成上方所有文件? 如果文件存在会直接覆盖。', true)) {
|
||||
$output->comment('已取消。');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$result = $build->create();
|
||||
@@ -119,10 +169,9 @@ class CurdBase extends Command
|
||||
}
|
||||
$output->writeln('>>>>>>>>>>>>>>>');
|
||||
|
||||
$ask_force_delete_result = $output->confirm($input, '确定删除上方所有文件? ');
|
||||
|
||||
if (!$ask_force_delete_result) {
|
||||
throw new Exception('取消删除文件操作');
|
||||
if (!$output->confirm($input, '确定删除上方所有文件? ', true)) {
|
||||
$output->comment('已取消。');
|
||||
return false;
|
||||
}
|
||||
$result = $build->delete();
|
||||
$output->info('>>>>>>>>>>>>>>>');
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\common\console\Command;
|
||||
use app\common\service\UploadService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Filesystem;
|
||||
@@ -13,37 +13,53 @@ class OssStaticBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('OssStatic')
|
||||
->setDescription('将静态资源上传到oss上');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('========正在上传静态资源到OSS上:========' . date('Y-m-d H:i:s'));
|
||||
try {
|
||||
$start_time = date('Y-m-d H:i:s');
|
||||
$list = Filesystem::disk('local_static')->listContents('/', true);
|
||||
$upload_service = new UploadService();
|
||||
$uploadPrefix = config('app.oss_static_prefix', 'oss_static_prefix');
|
||||
|
||||
$list = Filesystem::disk('local_static')->listContents('/', true);
|
||||
$upload_service = new UploadService();
|
||||
$uploadPrefix = config('app.oss_static_prefix', 'oss_static_prefix');
|
||||
$success_count = 0;
|
||||
$failed_count = 0;
|
||||
|
||||
foreach ($list as $file_item) {
|
||||
if ($file_item['type'] != 'file') {
|
||||
continue;
|
||||
foreach ($list as $file_item) {
|
||||
if ($file_item['type'] != 'file') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_path = $file_item['path'];
|
||||
|
||||
$file_path = Filesystem::disk('local_static')->path($file_path);
|
||||
|
||||
$file = new File($file_path, false);
|
||||
|
||||
$save_name = $file_item['path'];
|
||||
try {
|
||||
$model_file = $upload_service->save($file, $save_name, true, $uploadPrefix, true);
|
||||
$success_count++;
|
||||
$output->info('文件上传成功:' . $save_name . '。上传地址:' . $model_file['url']);
|
||||
} catch (\Throwable $th) {
|
||||
$failed_count++;
|
||||
$output->error('文件上传失败:' . $save_name . '。错误信息:' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$file_path = $file_item['path'];
|
||||
// 文本模式输出
|
||||
$output->writeln('========正在上传静态资源到OSS上:========' . $start_time);
|
||||
$output->writeln('========已完成静态资源上传到OSS上:========' . date('Y-m-d H:i:s'));
|
||||
$output->writeln('总计: ' . ($success_count + $failed_count) . ' 个文件,成功: ' . $success_count . ' 个,失败: ' . $failed_count . ' 个');
|
||||
|
||||
$file_path = Filesystem::disk('local_static')->path($file_path);
|
||||
|
||||
$file = new File($file_path, false);
|
||||
|
||||
$save_name = $file_item['path'];
|
||||
try {
|
||||
$model_file = $upload_service->save($file, $save_name, true, $uploadPrefix, true);
|
||||
$output->info('文件上传成功:' . $save_name . '。上传地址:' . $model_file['url']);
|
||||
} catch (\Throwable $th) {
|
||||
$output->error('文件上传失败:' . $save_name . '。错误信息:' . $th->getMessage());
|
||||
}
|
||||
return $failed_count > 0 ? 1 : 0;
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
$output->writeln('========已完成静态资源上传到OSS上:========' . date('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\common\console\Command;
|
||||
use app\common\interface\test\CommandTestInterface;
|
||||
use app\common\service\test\LogTestService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\common\console\Command;
|
||||
use app\common\service\HostService;
|
||||
use app\common\service\TimerService;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Promise\Utils;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
@@ -29,6 +29,8 @@ class TimerBase extends Command
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('timer')
|
||||
->addOption('temp', null, Option::VALUE_NONE)
|
||||
@@ -41,52 +43,57 @@ class TimerBase extends Command
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('start timer');
|
||||
try {
|
||||
// 指令输出
|
||||
$output->writeln('start timer');
|
||||
|
||||
$site_domain = sysconfig('site', 'site_domain');
|
||||
if (empty($site_domain)) {
|
||||
$output->writeln('请前往后台设置站点域名(site_domain)配置项');
|
||||
$site_domain = sysconfig('site', 'site_domain');
|
||||
if (empty($site_domain)) {
|
||||
$output->writeln('请前往后台设置站点域名(site_domain)配置项');
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$host = $site_domain;
|
||||
$host = $site_domain;
|
||||
|
||||
if ($input->hasOption('local')) {
|
||||
$host = $input->getOption('local-host') . ':' . $input->getOption('local-port');
|
||||
}
|
||||
if ($input->hasOption('local')) {
|
||||
$host = $input->getOption('local-host') . ':' . $input->getOption('local-port');
|
||||
}
|
||||
|
||||
$output->writeln('站点域名:' . $host);
|
||||
$site_host = parse_url($host, PHP_URL_HOST);
|
||||
$output->writeln('站点域名:' . $host);
|
||||
$site_host = parse_url($host, PHP_URL_HOST);
|
||||
|
||||
// 设置配置的任务
|
||||
$timer_service = new TimerService();
|
||||
$request_list = $timer_service->generateAllRequestList();
|
||||
$call_list = $timer_service->generateAllCallList();
|
||||
// 设置配置的任务
|
||||
$timer_service = new TimerService();
|
||||
$request_list = $timer_service->generateAllRequestList();
|
||||
$call_list = $timer_service->generateAllCallList();
|
||||
|
||||
// 内置的节点注册任务
|
||||
$system_host_register =
|
||||
[
|
||||
'name' => 'system_host_register', // 定时任务的名称,不能重复
|
||||
'type' => 'call', // 定时任务的类型,默认只支持site,你也可以重写定时器命令行以支持其他命令
|
||||
'target' => [HostService::class, 'heartbeat'], // 要访问的地址,如果不是以https开头,那么以后台的系统配置中读取相关配置,如果没有配置则不执行
|
||||
'frequency' => 30, // 执行频率,单位:秒,填写10,则每10秒过后执行一次
|
||||
];
|
||||
$system_host_call_list = TimerService::generateTaskInstanceFromConfig($system_host_register);
|
||||
$call_list = array_merge($call_list, $system_host_call_list);
|
||||
// 内置的节点注册任务
|
||||
$system_host_register =
|
||||
[
|
||||
'name' => 'system_host_register', // 定时任务的名称,不能重复
|
||||
'type' => 'call', // 定时任务的类型,默认只支持site,你也可以重写定时器命令行以支持其他命令
|
||||
'target' => [HostService::class, 'heartbeat'], // 要访问的地址,如果不是以https开头,那么以后台的系统配置中读取相关配置,如果没有配置则不执行
|
||||
'frequency' => 30, // 执行频率,单位:秒,填写10,则每10秒过后执行一次
|
||||
];
|
||||
$system_host_call_list = TimerService::generateTaskInstanceFromConfig($system_host_register);
|
||||
$call_list = array_merge($call_list, $system_host_call_list);
|
||||
|
||||
$this->host = $host;
|
||||
$this->siteDomain = $site_domain;
|
||||
$this->siteHost = $site_host;
|
||||
$this->requestList = $request_list;
|
||||
$this->callList = $call_list;
|
||||
$this->host = $host;
|
||||
$this->siteDomain = $site_domain;
|
||||
$this->siteHost = $site_host;
|
||||
$this->requestList = $request_list;
|
||||
$this->callList = $call_list;
|
||||
|
||||
$timer_mode = Config::get('timer.mode', 'normal');
|
||||
if ($timer_mode == 'normal') {
|
||||
$this->runNormal();
|
||||
} else {
|
||||
$this->runParallel();
|
||||
// 文本模式:正常运行定时器
|
||||
$timer_mode = Config::get('timer.mode', 'normal');
|
||||
if ($timer_mode == 'normal') {
|
||||
$this->runNormal();
|
||||
} else {
|
||||
$this->runParallel();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use think\console\Command;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\App;
|
||||
@@ -13,6 +13,8 @@ class ClearBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:clear')
|
||||
->setDescription('删除开发临时生成目录');
|
||||
@@ -20,31 +22,34 @@ class ClearBase extends Command
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 指令输出
|
||||
$output->writeln('删除测试目录');
|
||||
|
||||
$dir = App::getRootPath() . '/runtime/source/';
|
||||
$deleted = false;
|
||||
$message = '';
|
||||
$command_line = '';
|
||||
|
||||
$dir = App::getRootPath() . '/runtime/source/';
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
$output->writeln('删除成功');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (strpos(strtolower(PHP_OS), 'win') === 0) {
|
||||
$command_line = implode(' ', ['rd', '/s', '/q', str_replace('/', '\\', $dir)]);
|
||||
$deleted = true;
|
||||
$message = '目录不存在,无需删除';
|
||||
} else {
|
||||
$command_line = implode(' ', ['rm', '-rf', $dir]);
|
||||
if (strpos(strtolower(PHP_OS), 'win') === 0) {
|
||||
$command_line = implode(' ', ['rd', '/s', '/q', str_replace('/', '\\', $dir)]);
|
||||
} else {
|
||||
$command_line = implode(' ', ['rm', '-rf', $dir]);
|
||||
}
|
||||
|
||||
exec($command_line);
|
||||
$deleted = !is_dir($dir);
|
||||
$message = $deleted ? '删除成功' : '删除失败';
|
||||
}
|
||||
|
||||
$output->info('删除目录:' . $command_line);
|
||||
// 文本模式输出
|
||||
$output->writeln('删除测试目录');
|
||||
if ($command_line) {
|
||||
$output->info('删除目录:' . $command_line);
|
||||
$output->info('run command: ' . $command_line);
|
||||
}
|
||||
$output->info($message);
|
||||
|
||||
$output->info('run command: ' . $command_line);
|
||||
|
||||
exec($command_line);
|
||||
|
||||
$output->info('删除成功');
|
||||
return $deleted ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use app\admin\service\AdminUpdateService;
|
||||
use think\console\Command;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
@@ -17,11 +17,13 @@ class UpdateBase extends Command
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:update')
|
||||
->addOption('reinstall', null, Option::VALUE_NONE, '重装版本')
|
||||
->addOption('update-ulthon', null, Option::VALUE_NONE, '重装版本')
|
||||
->setDescription('the admin:update command');
|
||||
->addOption('update-ulthon', null, Option::VALUE_NONE, '更新 ulthon_admin')
|
||||
->setDescription('更新系统代码');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
@@ -38,6 +40,7 @@ class UpdateBase extends Command
|
||||
$update_service = new AdminUpdateService($repo);
|
||||
$update_service->input = $input;
|
||||
$update_service->output = $output;
|
||||
|
||||
$update_service->update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin;
|
||||
|
||||
use app\common\console\Command;
|
||||
use app\common\tools\PathTools;
|
||||
use think\App as ThinkApp;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
@@ -85,6 +85,8 @@ class VersionBase extends Command
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:version')
|
||||
->addOption('generate-comment', null, Option::VALUE_NONE, '使用git命令生成说明文件')
|
||||
@@ -96,6 +98,7 @@ class VersionBase extends Command
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 文本模式输出
|
||||
// 指令输出
|
||||
if (!empty(static::PRODUCT_VERSION)) {
|
||||
$output->info('当前版本号为:' . static::PRODUCT_VERSION);
|
||||
|
||||
106
extend/base/common/command/admin/menu/AdminMenuCreateBase.php
Normal file
106
extend/base/common/command/admin/menu/AdminMenuCreateBase.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\menu;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* admin:menu:create command - 创建菜单
|
||||
*/
|
||||
class AdminMenuCreateBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 命令配置
|
||||
$this->setName('admin:menu:create')
|
||||
->addOption('title', null, Option::VALUE_REQUIRED, '菜单标题(必填)')
|
||||
->addOption('path', null, Option::VALUE_OPTIONAL, '菜单路径', '')
|
||||
->addOption('icon', null, Option::VALUE_OPTIONAL, '菜单图标', '')
|
||||
->addOption('parent-id', null, Option::VALUE_OPTIONAL, '父菜单ID(默认为0)', 0)
|
||||
->addOption('sort', null, Option::VALUE_OPTIONAL, '排序(默认为100)', 100)
|
||||
->addOption('node', null, Option::VALUE_OPTIONAL, '权限节点', '')
|
||||
->addOption('remark', null, Option::VALUE_OPTIONAL, '备注说明', '')
|
||||
->setDescription('创建菜单');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 获取参数
|
||||
$title = $input->getOption('title');
|
||||
$path = $input->getOption('path');
|
||||
$icon = $input->getOption('icon');
|
||||
$parentId = $input->getOption('parent-id');
|
||||
$sort = $input->getOption('sort');
|
||||
$node = $input->getOption('node');
|
||||
$remark = $input->getOption('remark');
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($title)) {
|
||||
$output->error('菜单标题不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 准备菜单数据
|
||||
$menuData = [
|
||||
'title' => $title,
|
||||
'path' => $path,
|
||||
'icon' => $icon,
|
||||
'parent_id' => (int)$parentId,
|
||||
'sort' => (int)$sort,
|
||||
'node' => $node,
|
||||
'remark' => $remark,
|
||||
];
|
||||
|
||||
// 实例化菜单服务(使用 app\admin\service\MenuService,它继承自 base\common\service\MenuServiceBase)
|
||||
// 由于 MenuServiceBase 需要 adminId 参数,我们使用 0 表示命令行操作
|
||||
$menuService = new \app\common\service\MenuService(0);
|
||||
|
||||
// 创建菜单
|
||||
$menuId = $menuService->create($menuData);
|
||||
|
||||
// 获取创建的菜单详情
|
||||
$menuModel = SystemMenu::find($menuId);
|
||||
$menu = $menuModel ? $menuModel->toArray() : null;
|
||||
|
||||
if (empty($menu)) {
|
||||
throw new \Exception('菜单创建成功,但无法获取菜单详情');
|
||||
}
|
||||
|
||||
$outputData = [
|
||||
'id' => (int)$menu['id'],
|
||||
'title' => $menu['title'] ?? '',
|
||||
'path' => $menu['href'] ?? '',
|
||||
'icon' => $menu['icon'] ?? '',
|
||||
'parent_id' => (int)($menu['pid'] ?? 0),
|
||||
'sort' => (int)($menu['sort'] ?? 0),
|
||||
'node' => $menu['auth_node'] ?? '',
|
||||
];
|
||||
|
||||
// 输出结果
|
||||
$output->info('菜单创建成功');
|
||||
$output->info('菜单ID: ' . $outputData['id']);
|
||||
$output->info('菜单标题: ' . $outputData['title']);
|
||||
$output->info('菜单路径: ' . $outputData['path']);
|
||||
$output->info('菜单图标: ' . $outputData['icon']);
|
||||
$output->info('父菜单ID: ' . $outputData['parent_id']);
|
||||
$output->info('排序: ' . $outputData['sort']);
|
||||
if (!empty($outputData['node'])) {
|
||||
$output->info('权限节点: ' . $outputData['node']);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('创建菜单失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\menu;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* admin:menu:delete command - 删除菜单
|
||||
*/
|
||||
class AdminMenuDeleteBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:menu:delete')
|
||||
->addOption('id', null, Option::VALUE_REQUIRED, '菜单ID')
|
||||
->setDescription('删除菜单');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 获取参数
|
||||
$id = $input->getOption('id');
|
||||
|
||||
// 验证参数
|
||||
if (empty($id)) {
|
||||
$output->error('菜单ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 验证菜单是否存在
|
||||
$menu = SystemMenu::find($id);
|
||||
if (empty($menu)) {
|
||||
$output->error('菜单ID ' . $id . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查是否有子菜单
|
||||
$childCount = SystemMenu::where('pid', $id)
|
||||
->where('delete_time', 0)
|
||||
->count();
|
||||
|
||||
if ($childCount > 0) {
|
||||
$output->error('菜单ID ' . $id . ' 存在 ' . $childCount . ' 个子菜单,请先删除子菜单');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 保存菜单信息用于输出
|
||||
$menuInfo = [
|
||||
'id' => (int)$menu->id,
|
||||
'title' => $menu->title,
|
||||
'path' => $menu->href ?? '',
|
||||
'icon' => $menu->icon ?? '',
|
||||
'parent_id' => (int)($menu->pid ?? 0),
|
||||
'sort' => (int)($menu->sort ?? 0),
|
||||
'node' => $menu->auth_node ?? '',
|
||||
];
|
||||
|
||||
// 4. 执行删除(软删除)
|
||||
$menu->delete();
|
||||
|
||||
// 5. 输出结果
|
||||
$output->info('菜单删除成功');
|
||||
$output->info('菜单ID: ' . $menuInfo['id']);
|
||||
$output->info('菜单名称: ' . $menuInfo['title']);
|
||||
$output->info('菜单路径: ' . $menuInfo['path']);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('删除菜单失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
127
extend/base/common/command/admin/menu/AdminMenuExportBase.php
Normal file
127
extend/base/common/command/admin/menu/AdminMenuExportBase.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\menu;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* admin:menu:export command - 导出菜单
|
||||
*/
|
||||
class AdminMenuExportBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:menu:export')
|
||||
->setDescription('导出菜单数据')
|
||||
->addOption('format', null, Option::VALUE_OPTIONAL, '输出格式(text/json)', 'text')
|
||||
->addOption('output', null, Option::VALUE_OPTIONAL, '输出文件路径(可选)');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 获取参数
|
||||
$outputPath = $input->getOption('output');
|
||||
|
||||
try {
|
||||
// 1. 查询所有菜单
|
||||
$menus = SystemMenu::where('delete_time', 0)
|
||||
->order(['sort' => 'desc', 'id' => 'asc'])
|
||||
->select()
|
||||
->hidden(['create_time', 'update_time', 'delete_time']);
|
||||
|
||||
// 2. 格式化菜单数据
|
||||
$menuData = [];
|
||||
foreach ($menus as $menu) {
|
||||
$menuData[] = [
|
||||
'id' => (int)$menu->id,
|
||||
'pid' => (int)($menu->pid ?? 0),
|
||||
'title' => $menu->title ?? '',
|
||||
'href' => $menu->href ?? '',
|
||||
'icon' => $menu->icon ?? '',
|
||||
'sort' => (int)($menu->sort ?? 0),
|
||||
'auth_node' => $menu->auth_node ?? '',
|
||||
'status' => (int)($menu->status ?? 0),
|
||||
'type' => (int)($menu->type ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
// JSON 模式输出
|
||||
if ($input->getOption('format') === 'json') {
|
||||
if (!empty($outputPath)) {
|
||||
$jsonData = json_encode($menuData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
if ($jsonData === false) {
|
||||
throw new \RuntimeException('JSON 编码失败');
|
||||
}
|
||||
|
||||
$result = file_put_contents($outputPath, $jsonData);
|
||||
if ($result === false) {
|
||||
throw new \RuntimeException('无法写入文件: ' . $outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
$json = json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'menus' => $menuData,
|
||||
'output' => !empty($outputPath) ? $outputPath : null,
|
||||
],
|
||||
'warnings' => [],
|
||||
'metadata' => [
|
||||
'count' => count($menuData),
|
||||
'exported_at' => date('c'),
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$output->writeln($json);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 如果指定了输出路径,写入文件
|
||||
if (!empty($outputPath)) {
|
||||
$jsonData = json_encode($menuData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
$result = file_put_contents($outputPath, $jsonData);
|
||||
|
||||
if ($result === false) {
|
||||
$output->error('无法写入文件: ' . $outputPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
$output->info('成功导出 ' . count($menuData) . ' 个菜单到文件: ' . $outputPath);
|
||||
} else {
|
||||
// 4. 输出结果到控制台
|
||||
$output->info('成功导出 ' . count($menuData) . ' 个菜单');
|
||||
|
||||
// 以表格形式输出菜单数据
|
||||
$output->writeln('菜单ID | 父菜单ID | 菜单标题 | 菜单路径 | 图标 | 排序 | 权限节点 | 状态 | 类型');
|
||||
$output->writeln('--------|----------|----------|----------|------|------|----------|------|------');
|
||||
|
||||
foreach ($menuData as $menu) {
|
||||
$output->writeln($menu['id'] . ' | ' .
|
||||
$menu['pid'] . ' | ' .
|
||||
$menu['title'] . ' | ' .
|
||||
$menu['href'] . ' | ' .
|
||||
$menu['icon'] . ' | ' .
|
||||
$menu['sort'] . ' | ' .
|
||||
$menu['auth_node'] . ' | ' .
|
||||
$menu['status'] . ' | ' .
|
||||
$menu['type']);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('导出菜单失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
132
extend/base/common/command/admin/menu/AdminMenuListBase.php
Normal file
132
extend/base/common/command/admin/menu/AdminMenuListBase.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\menu;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminMenuListBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:menu:list')
|
||||
->setDescription('列出所有菜单(树形结构)')
|
||||
->addOption('format', null, Option::VALUE_OPTIONAL, '输出格式(tree/table/json)', 'tree')
|
||||
->addOption('status', null, Option::VALUE_OPTIONAL, '筛选状态(0=禁用,1=启用,all=全部)', 'all')
|
||||
->addOption('pid', null, Option::VALUE_OPTIONAL, '指定父菜单ID(默认从根菜单开始)', null);
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$format = $input->getOption('format');
|
||||
$statusFilter = $input->getOption('status');
|
||||
$pidFilter = $input->getOption('pid');
|
||||
|
||||
try {
|
||||
$query = SystemMenu::where('delete_time', 0);
|
||||
|
||||
if ($statusFilter !== 'all') {
|
||||
$query->where('status', (int)$statusFilter);
|
||||
}
|
||||
|
||||
$menus = $query->order(['sort' => 'desc', 'id' => 'asc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
if (empty($menus)) {
|
||||
$output->comment('没有找到菜单数据');
|
||||
return true;
|
||||
}
|
||||
|
||||
$menuTree = $this->buildTree($menus, $pidFilter ? (int)$pidFilter : 0);
|
||||
|
||||
switch ($format) {
|
||||
case 'json':
|
||||
$this->outputJson($output, $menuTree, count($menus));
|
||||
break;
|
||||
case 'table':
|
||||
$this->outputTable($output, $menus);
|
||||
break;
|
||||
case 'tree':
|
||||
default:
|
||||
$this->outputTree($output, $menuTree);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('获取菜单列表失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function buildTree(array $menus, int $pid = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($menus as $menu) {
|
||||
if ((int)$menu['pid'] === $pid) {
|
||||
$children = $this->buildTree($menus, (int)$menu['id']);
|
||||
if (!empty($children)) {
|
||||
$menu['children'] = $children;
|
||||
}
|
||||
$tree[] = $menu;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
protected function outputTree(Output $output, array $tree, int $level = 0): void
|
||||
{
|
||||
$prefix = str_repeat(' ', $level);
|
||||
$branch = $level > 0 ? '├─ ' : '';
|
||||
|
||||
foreach ($tree as $node) {
|
||||
$status = $node['status'] ? '<info>●</info>' : '<comment>○</comment>';
|
||||
$href = $node['href'] ?? '-';
|
||||
$output->writeln("{$prefix}{$branch}{$status} [{$node['id']}] {$node['title']} <comment>({$href})</comment>");
|
||||
|
||||
if (!empty($node['children'])) {
|
||||
$this->outputTree($output, $node['children'], $level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function outputTable(Output $output, array $menus): void
|
||||
{
|
||||
$output->writeln('ID | PID | 标题 | 路径 | 状态');
|
||||
$output->writeln('---|-----|------|------|------');
|
||||
|
||||
foreach ($menus as $menu) {
|
||||
$status = $menu['status'] ? '启用' : '禁用';
|
||||
$output->writeln(sprintf(
|
||||
'%d | %d | %s | %s | %s',
|
||||
$menu['id'],
|
||||
$menu['pid'],
|
||||
$menu['title'],
|
||||
$menu['href'] ?? '-',
|
||||
$status
|
||||
));
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
$output->info('共 ' . count($menus) . ' 个菜单');
|
||||
}
|
||||
|
||||
protected function outputJson(Output $output, array $tree, int $total): void
|
||||
{
|
||||
$data = [
|
||||
'success' => true,
|
||||
'data' => ['menus' => $tree],
|
||||
'metadata' => ['total' => $total],
|
||||
];
|
||||
$output->writeln(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
129
extend/base/common/command/admin/menu/AdminMenuUpdateBase.php
Normal file
129
extend/base/common/command/admin/menu/AdminMenuUpdateBase.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\menu;
|
||||
|
||||
use app\admin\model\SystemMenu;
|
||||
use app\common\console\Command;
|
||||
use app\common\service\MenuService;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* admin:menu:update command - 更新菜单
|
||||
*/
|
||||
class AdminMenuUpdateBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:menu:update')
|
||||
->addOption('id', null, Option::VALUE_REQUIRED, '菜单ID(必填)')
|
||||
->addOption('title', null, Option::VALUE_OPTIONAL, '菜单标题')
|
||||
->addOption('path', null, Option::VALUE_OPTIONAL, '菜单路径')
|
||||
->addOption('icon', null, Option::VALUE_OPTIONAL, '菜单图标')
|
||||
->addOption('parent-id', null, Option::VALUE_OPTIONAL, '父级菜单ID')
|
||||
->addOption('sort', null, Option::VALUE_OPTIONAL, '排序')
|
||||
->setDescription('编辑菜单');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 获取参数
|
||||
$id = (int)($input->getOption('id') ?? 0);
|
||||
$title = $input->getOption('title');
|
||||
$path = $input->getOption('path');
|
||||
$icon = $input->getOption('icon');
|
||||
$parentId = $input->getOption('parent-id');
|
||||
$sort = $input->getOption('sort');
|
||||
|
||||
// 验证参数:id 是必填的
|
||||
if (empty($id)) {
|
||||
$output->error('菜单ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 验证菜单是否存在
|
||||
$menu = SystemMenu::find($id);
|
||||
if (empty($menu)) {
|
||||
$output->writeln('<error>菜单ID ' . $id . ' 不存在</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 准备更新数据
|
||||
$updateData = [];
|
||||
|
||||
if ($title !== null) {
|
||||
$updateData['title'] = $title;
|
||||
}
|
||||
|
||||
if ($path !== null) {
|
||||
$updateData['path'] = $path;
|
||||
}
|
||||
|
||||
if ($icon !== null) {
|
||||
$updateData['icon'] = $icon;
|
||||
}
|
||||
|
||||
if ($parentId !== null) {
|
||||
$updateData['parent_id'] = (int)$parentId;
|
||||
}
|
||||
|
||||
if ($sort !== null) {
|
||||
$updateData['sort'] = (int)$sort;
|
||||
}
|
||||
|
||||
// 检查是否有需要更新的字段
|
||||
if (empty($updateData)) {
|
||||
$output->error('没有提供需要更新的字段');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 调用 MenuService 更新菜单
|
||||
$menuService = new MenuService(0);
|
||||
$success = $menuService->update($id, $updateData);
|
||||
|
||||
if (!$success) {
|
||||
$output->writeln('<error>菜单更新失败</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 重新查询菜单数据
|
||||
$updatedMenu = SystemMenu::find($id);
|
||||
if (empty($updatedMenu)) {
|
||||
$output->writeln('<error>菜单更新后查询失败</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
$outputData = [
|
||||
'id' => (int)$updatedMenu->id,
|
||||
'title' => $updatedMenu->title ?? '',
|
||||
'path' => $updatedMenu->href ?? '',
|
||||
'icon' => $updatedMenu->icon ?? '',
|
||||
'parent_id' => (int)($updatedMenu->pid ?? 0),
|
||||
'sort' => (int)($updatedMenu->sort ?? 0),
|
||||
'node' => $updatedMenu->auth_node ?? '',
|
||||
];
|
||||
|
||||
// 5. 输出结果
|
||||
$output->writeln('<info>菜单编辑成功</info>');
|
||||
$output->writeln('<info>菜单ID: ' . $outputData['id'] . '</info>');
|
||||
$output->writeln('<info>菜单标题: ' . $outputData['title'] . '</info>');
|
||||
$output->writeln('<info>菜单路径: ' . $outputData['path'] . '</info>');
|
||||
$output->writeln('<info>菜单图标: ' . $outputData['icon'] . '</info>');
|
||||
$output->writeln('<info>父级菜单ID: ' . $outputData['parent_id'] . '</info>');
|
||||
$output->writeln('<info>排序: ' . $outputData['sort'] . '</info>');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('编辑菜单失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\permission;
|
||||
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Input\Option;
|
||||
use think\console\Output;
|
||||
use app\admin\service\NodeService;
|
||||
|
||||
/**
|
||||
* admin:permission:nodes command - 查看权限节点
|
||||
*/
|
||||
class AdminPermissionNodesBase extends Command
|
||||
{
|
||||
/**
|
||||
* 配置命令参数
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
// 指令配置
|
||||
$this->setName('admin:permission:nodes')
|
||||
->setDescription('查看权限节点')
|
||||
->addOption('level', null, Option::VALUE_OPTIONAL, '按级别过滤 (1=控制器, 2=方法)')
|
||||
->addOption('module', null, Option::VALUE_OPTIONAL, '按模块过滤');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param Input $input
|
||||
* @param Output $output
|
||||
* @return int
|
||||
* @throws \Doctrine\Common\Annotations\AnnotationException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
try {
|
||||
// 获取所有权限节点
|
||||
$nodeService = new NodeService();
|
||||
$nodeList = $nodeService->getNodelist();
|
||||
|
||||
// 过滤条件
|
||||
$filterLevel = $input->getOption('level');
|
||||
$filterModule = $input->getOption('module');
|
||||
|
||||
// 应用过滤
|
||||
$filteredNodes = $nodeList;
|
||||
if ($filterLevel !== null) {
|
||||
$level = (int)$filterLevel;
|
||||
$filteredNodes = array_filter($filteredNodes, function ($node) use ($level) {
|
||||
return isset($node['type']) && $node['type'] === $level;
|
||||
});
|
||||
}
|
||||
|
||||
if ($filterModule !== null) {
|
||||
$filteredNodes = array_filter($filteredNodes, function ($node) use ($filterModule) {
|
||||
return isset($node['module']) && $node['module'] === $filterModule;
|
||||
});
|
||||
}
|
||||
|
||||
// 重新索引数组
|
||||
$filteredNodes = array_values($filteredNodes);
|
||||
|
||||
// 输出表头
|
||||
$output->info('权限节点列表');
|
||||
$output->writeln('================================');
|
||||
|
||||
if (empty($filteredNodes)) {
|
||||
$output->comment('没有找到符合条件的权限节点');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 输出权限节点信息
|
||||
foreach ($filteredNodes as $node) {
|
||||
$levelText = $node['type'] == 1 ? '控制器' : '方法';
|
||||
$authText = isset($node['auth']) && $node['auth'] ? '是' : '否';
|
||||
|
||||
$output->info('节点: ' . ($node['node'] ?? ''));
|
||||
$output->comment(' 标题: ' . ($node['title'] ?? ''));
|
||||
$output->comment(' 级别: ' . $levelText . ' (' . ($node['type'] ?? 0) . ')');
|
||||
$output->comment(' 模块: ' . ($node['module'] ?? ''));
|
||||
$output->comment(' 需要授权: ' . $authText);
|
||||
$output->writeln('--------------------------------');
|
||||
}
|
||||
|
||||
$output->info('总计: ' . count($filteredNodes) . ' 个权限节点');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$output->error('获取权限节点失败: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\permission;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\admin\service\NodeService;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminPermissionUserBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:permission:user')
|
||||
->addOption('user-id', 'u', Option::VALUE_OPTIONAL, '用户ID', null)
|
||||
->setDescription('查看用户权限');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$userId = $input->getOption('user-id');
|
||||
|
||||
// 验证用户ID参数
|
||||
if (empty($userId)) {
|
||||
$output->error('缺少必要参数 --user-id');
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
$user = SystemAdmin::find($userId);
|
||||
|
||||
if (empty($user)) {
|
||||
$output->error('用户不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询用户角色
|
||||
$roles = [];
|
||||
$authIds = $user->auth_ids;
|
||||
$authIdArray = [];
|
||||
|
||||
if (!empty($authIds)) {
|
||||
$authIdArray = array_filter(explode(',', $authIds));
|
||||
|
||||
if (!empty($authIdArray)) {
|
||||
$roles = SystemAuth::whereIn('id', $authIdArray)
|
||||
->where('delete_time', 0)
|
||||
->field('id,title')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
// 查询用户权限节点
|
||||
$nodes = [];
|
||||
if (!empty($authIdArray)) {
|
||||
$nodes = SystemAuthNode::whereIn('auth_id', $authIdArray)
|
||||
->column('node');
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
$output->info('用户权限信息');
|
||||
$output->info('用户ID: ' . $user->id);
|
||||
$output->info('用户名: ' . $user->username);
|
||||
|
||||
if (!empty($roles)) {
|
||||
$output->info('角色列表:');
|
||||
foreach ($roles as $role) {
|
||||
$output->comment(' - ID: ' . $role['id'] . ', 标题: ' . $role['title']);
|
||||
}
|
||||
} else {
|
||||
$output->comment('用户没有分配任何角色');
|
||||
}
|
||||
|
||||
if (!empty($nodes)) {
|
||||
$output->info('权限节点列表:');
|
||||
foreach ($nodes as $node) {
|
||||
$output->comment(' - ' . $node);
|
||||
}
|
||||
} else {
|
||||
$output->comment('用户没有分配任何权限节点');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRoleCreateBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:create')
|
||||
->addOption('title', null, Option::VALUE_REQUIRED, '角色名称')
|
||||
->addOption('remark', null, Option::VALUE_OPTIONAL, '角色备注')
|
||||
->setDescription('创建角色');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$title = $input->getOption('title');
|
||||
$remark = $input->getOption('remark');
|
||||
|
||||
if (empty($title)) {
|
||||
$output->error('角色名称不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$auth = new SystemAuth();
|
||||
$auth->title = $title;
|
||||
$auth->remark = $remark ?? '';
|
||||
$auth->sort = 0;
|
||||
$auth->status = 1;
|
||||
$auth->save();
|
||||
|
||||
$output->info('角色创建成功');
|
||||
$output->info('角色ID: ' . $auth->id);
|
||||
$output->info('角色名称: ' . $auth->title);
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('创建角色失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRoleDeleteBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:delete')
|
||||
->addOption('role-id', null, Option::VALUE_REQUIRED, '角色ID')
|
||||
->setDescription('删除角色');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$roleId = $input->getOption('role-id');
|
||||
|
||||
if (empty($roleId)) {
|
||||
$output->error('角色ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$auth = SystemAuth::find($roleId);
|
||||
if (empty($auth)) {
|
||||
$output->error('角色ID ' . $roleId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$checkUsers = SystemAdmin::where('auth_ids', 'like', '%' . $roleId . '%')
|
||||
->where('delete_time', 0)
|
||||
->count();
|
||||
|
||||
if ($checkUsers > 0) {
|
||||
$output->error('角色ID ' . $roleId . ' 已分配给 ' . $checkUsers . ' 个用户,无法删除');
|
||||
return false;
|
||||
}
|
||||
|
||||
SystemAuthNode::where('auth_id', $roleId)->delete();
|
||||
$auth->delete();
|
||||
|
||||
$output->info('角色删除成功');
|
||||
$output->info('角色ID: ' . $roleId);
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('删除角色失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
65
extend/base/common/command/admin/role/AdminRoleInfoBase.php
Normal file
65
extend/base/common/command/admin/role/AdminRoleInfoBase.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRoleInfoBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:info')
|
||||
->addOption('role-id', null, Option::VALUE_REQUIRED, '角色ID')
|
||||
->setDescription('查看角色详情');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$roleId = $input->getOption('role-id');
|
||||
|
||||
if (empty($roleId)) {
|
||||
$output->error('角色ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$role = SystemAuth::find($roleId);
|
||||
if (empty($role)) {
|
||||
$output->error('角色ID ' . $roleId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$nodes = SystemAuthNode::where('auth_id', $roleId)
|
||||
->column('node');
|
||||
|
||||
$output->info('角色详情');
|
||||
$output->writeln('================================');
|
||||
$output->info('角色ID: ' . $role->id);
|
||||
$output->comment('角色名称: ' . $role->title);
|
||||
$output->comment('状态: ' . ($role->status == 1 ? '启用' : '禁用'));
|
||||
$output->comment('排序: ' . $role->sort);
|
||||
if (!empty($role->remark)) {
|
||||
$output->comment('备注: ' . $role->remark);
|
||||
}
|
||||
$output->writeln('--------------------------------');
|
||||
$output->info('权限节点列表 (' . count($nodes) . ' 个):');
|
||||
foreach ($nodes as $node) {
|
||||
$output->comment(' - ' . $node);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('获取角色详情失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
64
extend/base/common/command/admin/role/AdminRoleListBase.php
Normal file
64
extend/base/common/command/admin/role/AdminRoleListBase.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRoleListBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:list')
|
||||
->setDescription('查看角色列表');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
try {
|
||||
$roles = SystemAuth::where('delete_time', 0)
|
||||
->field('id,title,sort,status,remark')
|
||||
->order('sort', 'asc')
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
if (empty($roles)) {
|
||||
$output->comment('没有找到任何角色');
|
||||
return true;
|
||||
}
|
||||
|
||||
$output->info('角色列表');
|
||||
$output->writeln('================================');
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$nodeCount = SystemAuthNode::where('auth_id', $role['id'])->count();
|
||||
$statusText = $role['status'] == 1 ? '启用' : '禁用';
|
||||
|
||||
$output->info('角色ID: ' . $role['id']);
|
||||
$output->comment(' 角色名称: ' . $role['title']);
|
||||
$output->comment(' 权限节点数: ' . $nodeCount);
|
||||
$output->comment(' 状态: ' . $statusText);
|
||||
$output->comment(' 排序: ' . $role['sort']);
|
||||
if (!empty($role['remark'])) {
|
||||
$output->comment(' 备注: ' . $role['remark']);
|
||||
}
|
||||
$output->writeln('--------------------------------');
|
||||
}
|
||||
|
||||
$output->info('总计: ' . count($roles) . ' 个角色');
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('获取角色列表失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\admin\service\NodeService;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRolePermissionAssignBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:permission:assign')
|
||||
->addOption('role-id', null, Option::VALUE_REQUIRED, '角色ID')
|
||||
->addOption('nodes', null, Option::VALUE_REQUIRED, '权限节点列表(逗号分隔)')
|
||||
->setDescription('给角色分配权限');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$roleId = $input->getOption('role-id');
|
||||
$nodesParam = $input->getOption('nodes');
|
||||
|
||||
if (empty($roleId)) {
|
||||
$output->error('角色ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($nodesParam)) {
|
||||
$output->error('权限节点不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$auth = SystemAuth::find($roleId);
|
||||
if (empty($auth)) {
|
||||
$output->error('角色ID ' . $roleId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$nodes = $this->parseNodes($nodesParam);
|
||||
if (empty($nodes)) {
|
||||
$output->error('权限节点列表为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
$validNodes = $this->validateNodes($nodes);
|
||||
$invalidNodes = array_diff($nodes, $validNodes);
|
||||
|
||||
if (!empty($invalidNodes)) {
|
||||
$invalidCount = count($invalidNodes);
|
||||
$output->error("[错误] 发现 {$invalidCount} 个无效的权限节点:");
|
||||
foreach (array_values($invalidNodes) as $node) {
|
||||
$output->error(" - {$node}");
|
||||
}
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
if (empty($validNodes)) {
|
||||
$output->error("有效权限节点数: 0");
|
||||
$output->error("分配失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
$existingNodes = SystemAuthNode::where('auth_id', $roleId)
|
||||
->column('node');
|
||||
$newNodes = array_diff($validNodes, $existingNodes);
|
||||
|
||||
if (empty($newNodes)) {
|
||||
$output->info('所有权限节点已存在,无需重复分配');
|
||||
return true;
|
||||
}
|
||||
|
||||
$authNodes = [];
|
||||
foreach ($newNodes as $node) {
|
||||
$authNodes[] = [
|
||||
'auth_id' => $roleId,
|
||||
'node' => $node,
|
||||
];
|
||||
}
|
||||
|
||||
(new SystemAuthNode())->saveAll($authNodes);
|
||||
|
||||
$output->info('权限分配成功');
|
||||
$output->info('角色ID: ' . $roleId);
|
||||
$output->info('新增的权限节点: ' . implode(', ', $newNodes));
|
||||
$output->info('该角色总权限节点数: ' . (count($existingNodes) + count($newNodes)));
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('分配权限失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function parseNodes(string $nodesParam): array
|
||||
{
|
||||
$nodes = explode(',', $nodesParam);
|
||||
$nodes = array_map('trim', $nodes);
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
return array_values($nodes);
|
||||
}
|
||||
|
||||
protected function validateNodes(array $nodes): array
|
||||
{
|
||||
$nodeService = new NodeService();
|
||||
$allNodes = $nodeService->getNodeParis();
|
||||
|
||||
$validNodes = [];
|
||||
foreach ($nodes as $node) {
|
||||
if (isset($allNodes[$node])) {
|
||||
$validNodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return $validNodes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRolePermissionListBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:permission:list')
|
||||
->addOption('role-id', null, Option::VALUE_REQUIRED, '角色ID')
|
||||
->setDescription('查看角色权限');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$roleId = $input->getOption('role-id');
|
||||
|
||||
if (empty($roleId)) {
|
||||
$output->error('角色ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$role = SystemAuth::find($roleId);
|
||||
if (empty($role)) {
|
||||
$output->error('角色ID ' . $roleId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$nodes = SystemAuthNode::where('auth_id', $roleId)
|
||||
->column('node');
|
||||
|
||||
$output->info('角色权限列表');
|
||||
$output->writeln('================================');
|
||||
$output->info('角色ID: ' . $role->id);
|
||||
$output->comment('角色名称: ' . $role->title);
|
||||
$output->writeln('--------------------------------');
|
||||
|
||||
if (empty($nodes)) {
|
||||
$output->comment('该角色没有分配任何权限节点');
|
||||
} else {
|
||||
$output->info('权限节点 (' . count($nodes) . ' 个):');
|
||||
foreach ($nodes as $node) {
|
||||
$output->comment(' - ' . $node);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('获取角色权限失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\role;
|
||||
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\admin\model\SystemAuthNode;
|
||||
use app\admin\service\NodeService;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminRolePermissionRevokeBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:role:permission:revoke')
|
||||
->addOption('role-id', null, Option::VALUE_REQUIRED, '角色ID')
|
||||
->addOption('nodes', null, Option::VALUE_REQUIRED, '权限节点列表(逗号分隔)')
|
||||
->setDescription('撤回角色权限');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$roleId = $input->getOption('role-id');
|
||||
$nodesParam = $input->getOption('nodes');
|
||||
|
||||
if (empty($roleId)) {
|
||||
$output->error('角色ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($nodesParam)) {
|
||||
$output->error('权限节点不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$auth = SystemAuth::find($roleId);
|
||||
if (empty($auth)) {
|
||||
$output->error('角色ID ' . $roleId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$nodes = $this->parseNodes($nodesParam);
|
||||
if (empty($nodes)) {
|
||||
$output->error('权限节点列表为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
$validNodes = $this->validateNodes($nodes);
|
||||
$invalidNodes = array_diff($nodes, $validNodes);
|
||||
|
||||
if (!empty($invalidNodes)) {
|
||||
$output->comment('警告: 以下权限节点无效: ' . implode(', ', array_values($invalidNodes)));
|
||||
}
|
||||
|
||||
if (empty($validNodes)) {
|
||||
$output->error('没有有效的权限节点');
|
||||
return false;
|
||||
}
|
||||
|
||||
$deletedCount = SystemAuthNode::where('auth_id', $roleId)
|
||||
->where('node', 'in', $validNodes)
|
||||
->delete();
|
||||
|
||||
if ($deletedCount == 0) {
|
||||
$output->error('没有权限可以撤回(角色不包含指定的权限节点)');
|
||||
return false;
|
||||
}
|
||||
|
||||
$remainingCount = SystemAuthNode::where('auth_id', $roleId)->count();
|
||||
|
||||
$output->info('权限撤回成功');
|
||||
$output->info('角色ID: ' . $roleId);
|
||||
$output->info('撤回的权限节点: ' . implode(', ', $validNodes));
|
||||
$output->info('该角色剩余权限节点数: ' . $remainingCount);
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('撤回权限失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function parseNodes(string $nodesParam): array
|
||||
{
|
||||
$nodes = explode(',', $nodesParam);
|
||||
$nodes = array_map('trim', $nodes);
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
return array_values($nodes);
|
||||
}
|
||||
|
||||
protected function validateNodes(array $nodes): array
|
||||
{
|
||||
$nodeService = new NodeService();
|
||||
$allNodes = $nodeService->getNodeParis();
|
||||
|
||||
$validNodes = [];
|
||||
foreach ($nodes as $node) {
|
||||
if (isset($allNodes[$node])) {
|
||||
$validNodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return $validNodes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\user;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminUserRoleAssignBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:user:role:assign')
|
||||
->addOption('user-id', null, Option::VALUE_REQUIRED, '用户ID')
|
||||
->addOption('role-ids', null, Option::VALUE_REQUIRED, '角色ID列表(逗号分隔)')
|
||||
->setDescription('给用户分配角色');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$userId = $input->getOption('user-id');
|
||||
$roleIdsParam = $input->getOption('role-ids');
|
||||
|
||||
if (empty($userId)) {
|
||||
$output->error('用户ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($roleIdsParam)) {
|
||||
$output->error('角色ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$admin = SystemAdmin::find($userId);
|
||||
if (empty($admin)) {
|
||||
$output->error('用户ID ' . $userId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleIds = $this->parseRoleIds($roleIdsParam);
|
||||
if (empty($roleIds)) {
|
||||
$output->error('角色ID列表为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
$validRoleIds = $this->validateRoleIds($roleIds);
|
||||
$invalidRoleIds = array_diff($roleIds, $validRoleIds);
|
||||
|
||||
if (!empty($invalidRoleIds)) {
|
||||
$invalidCount = count($invalidRoleIds);
|
||||
$output->error("[错误] 发现 {$invalidCount} 个无效的角色ID:");
|
||||
foreach (array_values($invalidRoleIds) as $roleId) {
|
||||
$output->error(" - {$roleId}");
|
||||
}
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
if (empty($validRoleIds)) {
|
||||
$output->error("有效角色ID数: 0");
|
||||
$output->error("分配失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
$existingAuthIds = $admin->auth_ids;
|
||||
$existingAuthIdArray = [];
|
||||
if (!empty($existingAuthIds)) {
|
||||
$existingAuthIdArray = array_filter(explode(',', $existingAuthIds));
|
||||
}
|
||||
|
||||
$newRoleIds = array_diff($validRoleIds, $existingAuthIdArray);
|
||||
|
||||
if (empty($newRoleIds)) {
|
||||
$output->info('所有角色已存在,无需重复分配');
|
||||
return true;
|
||||
}
|
||||
|
||||
$mergedRoleIds = array_merge($existingAuthIdArray, $newRoleIds);
|
||||
$mergedRoleIds = array_values(array_unique($mergedRoleIds));
|
||||
|
||||
$admin->auth_ids = implode(',', $mergedRoleIds);
|
||||
$admin->save();
|
||||
|
||||
$output->info('角色分配成功');
|
||||
$output->info('用户ID: ' . $userId);
|
||||
$output->info('新增的角色ID: ' . implode(', ', $newRoleIds));
|
||||
$output->info('该用户总角色数: ' . count($mergedRoleIds));
|
||||
$output->info('所有角色ID: ' . implode(', ', $mergedRoleIds));
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('分配角色失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function parseRoleIds(string $roleIdsParam): array
|
||||
{
|
||||
$roleIds = explode(',', $roleIdsParam);
|
||||
$roleIds = array_map('trim', $roleIds);
|
||||
$roleIds = array_filter($roleIds);
|
||||
|
||||
return array_values($roleIds);
|
||||
}
|
||||
|
||||
protected function validateRoleIds(array $roleIds): array
|
||||
{
|
||||
$validRoleIds = SystemAuth::whereIn('id', $roleIds)
|
||||
->where('delete_time', 0)
|
||||
->column('id');
|
||||
|
||||
return $validRoleIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\common\command\admin\user;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\admin\model\SystemAuth;
|
||||
use app\common\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class AdminUserRoleListBase extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
|
||||
$this->setName('admin:user:role:list')
|
||||
->addOption('user-id', null, Option::VALUE_REQUIRED, '用户ID')
|
||||
->setDescription('查看用户角色');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$userId = $input->getOption('user-id');
|
||||
|
||||
if (empty($userId)) {
|
||||
$output->error('用户ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = SystemAdmin::find($userId);
|
||||
if (empty($user)) {
|
||||
$output->error('用户ID ' . $userId . ' 不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
$authIds = $user->auth_ids;
|
||||
$authIdArray = [];
|
||||
|
||||
if (!empty($authIds)) {
|
||||
$authIdArray = array_filter(explode(',', $authIds));
|
||||
}
|
||||
|
||||
if (empty($authIdArray)) {
|
||||
$output->info('用户角色列表');
|
||||
$output->writeln('================================');
|
||||
$output->info('用户ID: ' . $user->id);
|
||||
$output->info('用户名: ' . $user->username);
|
||||
$output->writeln('--------------------------------');
|
||||
$output->comment('该用户没有分配任何角色');
|
||||
return true;
|
||||
}
|
||||
|
||||
$roles = SystemAuth::whereIn('id', $authIdArray)
|
||||
->where('delete_time', 0)
|
||||
->field('id,title,status,remark')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$output->info('用户角色列表');
|
||||
$output->writeln('================================');
|
||||
$output->info('用户ID: ' . $user->id);
|
||||
$output->info('用户名: ' . $user->username);
|
||||
$output->writeln('--------------------------------');
|
||||
|
||||
if (empty($roles)) {
|
||||
$output->comment('没有找到有效的角色信息');
|
||||
} else {
|
||||
foreach ($roles as $role) {
|
||||
$statusText = $role['status'] == 1 ? '启用' : '禁用';
|
||||
$output->info('角色ID: ' . $role['id']);
|
||||
$output->comment(' 角色名称: ' . $role['title']);
|
||||
$output->comment(' 状态: ' . $statusText);
|
||||
if (!empty($role['remark'])) {
|
||||
$output->comment(' 备注: ' . $role['remark']);
|
||||
}
|
||||
$output->writeln('--------------------------------');
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('获取用户角色失败: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user