Files
EasyGoAdmin_GoFrame_EleVue/document/architecture.md
T
2026-10-20 11:37:25 +08:00

366 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EasyGoAdmin_GoFrame_EleVue 全栈架构设计(最终定稿)
> 本文件为后端服务实施的唯一权威设计文档(定稿)。实施过程中的任何偏差以本文档为准。
## 0. 定稿变更清单
| # | 变更 | 状态 |
|---|---|---|
| 1 | `is_delete` → **`del_flag`** 全量统一(含索引名 `idx_*_del_flag`、唯一键 `(…, del_flag)`) | ✅ 定稿 |
| 2 | 每张数据表对应一个模块,给出全套文件(api/controller/logic/生成物) | ✅ §3 |
| 3 | 标准接口七件套:分页/列表/详情/新增/修改/删除/批量删除,+ 导入导出 | ✅ §1.3 |
| 4 | 路由与方法命名对齐 `ui/src/api` 与 PHP 参考控制器 | ✅ 全量提取 |
| 5 | 集成操作日志中间件 + 登录日志 + 两个查询模块 | ✅ §4 |
| 6 | 前端位于根目录 `ui/`(同仓库,后端 API 前缀与之逐条对齐) | ✅ §2 |
| 7 | `utility/` 保留顶层(官方案例一致) | ✅ |
| 8 | **主键统一 int 数据库自增,停用雪花算法**(2026-10-13 定稿:插入后 `LastInsertId()` 回读;`utility/id` 已移除) | ✅ 定稿 |
## 1. 全局设计规范
### 1.1 分层与调用链
```
main.go(5驱动) → internal/cmd(启动) → internal/router(注册+元数据)
→ middleware: CORS → Response → JWT → Tenant → Permission → OperationLog
→ controller(薄) → service接口(生成) → logic实现(sXxx) → dao → gdb → DB
```
### 1.2 标准接口七件套(所有模块统一)
| 方法名 | HTTP | 路由模板 | 说明 |
|---|---|---|---|
| `Page` | GET | `/{prefix}/page` | 分页,入参 `pageNo/pageSize` + 筛选 |
| `List` | GET | `/{prefix}/list` | 全量/树形列表(下拉、树选择) |
| `Detail` | GET | `/{prefix}/detail/:id` | 详情 |
| `Add` | POST | `/{prefix}/add` | 新增,返回 `{id}` |
| `Update` | PUT | `/{prefix}/update` | 修改(body 含 `id`) |
| `Delete` | DELETE | `/{prefix}/delete/:id` | 删除(软删 `del_flag=1`) |
| `BatchDelete` | DELETE | `/{prefix}/batchDelete` | 批量删除,body `{ids:[]}` |
| `Import` | POST | `/{prefix}/import` | 导入(multipart `file`,xls/xlsx) |
| `Export` | GET | `/{prefix}/export` | 导出(返回文件路径/流) |
**结构体命名**:`{Mod}{Action}Req` / `{Mod}{Action}Res`(如 `UserPageReq`、`LevelExportRes`)。
**controller 方法名 = logic 方法名 = 上表方法名**(与 PHP 控制器一致);logic 结构体命名 `sUser/sRole/…`,供 `gf gen service` 生成 `IUser/IRole` 接口。
### 1.3 注解规范(Go 版 `#[Log]`/`#[Permission]`,gmeta 扩展标签)
```go
type UserPageReq struct {
gmeta.Meta `method:"get" path:"/user/page" summary:"用户分页" permission:"sys:user:page" log:"用户管理-查询分页记录" logType:"query"`
common.PageReq // pageNo/pageSize
Keyword string `json:"keyword" dc:"关键词"`
Status int `json:"status" dc:"状态"`
}
```
`internal/router/bind.go` 在注册时反射解析上述标签 → 生成**路由元数据表** `map["GET /user/page"]{Permission, Log, LogType}`,供 `permission` 与 `operation_log` 中间件查表使用(等价 PHP 属性切面)。
### 1.4 权限码与日志标题规则(从参考项目提取后统一)
- 权限码:`sys:{module}:{action}`,module 用小驼峰(`fileTemplate/loginLog/operLog/configItem/dictItem/jobLog`),action 同方法名。`permission` 标签留空 = **仅登录即可**。
- 日志标题:`{模块中文名}-{动作}`,动作映射:
| action | 日志动作 | action | 日志动作 |
|---|---|---|---|
| page | 查询分页记录 | update | 修改记录 |
| list | 查询列表 | delete | 删除记录 |
| detail | 查询详情 | batchDelete | 批量删除记录 |
| add | 新增记录 | import/export | 导入数据 / 导出数据 |
(覆盖参考项目中“查询全量列表/查询树形列表”等混用写法,统一为上表;特殊日志标题在 §3 各模块单列。)
- `logType` 枚举(`consts/log_type.go`):`query/add/update/delete/import/export/reset`。
### 1.5 统一响应与分页
```json
{ "code": 0, "msg": "操作成功", "data": { } }
{ "list": [ ], "total": 0 }
```
错误码经 `consts/biz_err.go` 中 `gcode.Register` 注册;响应封装在 `utility/response` + `middleware/response.go`。
## 2. 模块总表(25 表 ↔ 27 模块)
| 域 | 模块 | 表 | 路由前缀 | api / controller / logic 文件(`{m}`=模块名全小写) |
|---|---|---|---|---|
| **auth** | auth | —(写 login_log) | 顶层 `/login /logout /captcha …` | `api/v1/auth/auth.go` | `controller/auth/auth.go` | `logic/auth/auth.go` |
| **index** | index | —(聚合 user/menu) | `/index/*` | `api/v1/index/index.go` | `controller/index/index.go` | `logic/index/index.go` |
| **system** | user | goframe_user | `/user` | `api/v1/system/user.go` | `controller/system/user.go` | `logic/system/user.go` |
| | role | goframe_role + **role_menu** | `/role` | `…/system/role.go` ×3 |
| | menu | goframe_menu | `/menu` | `…/system/menu.go` ×3 |
| | dept | goframe_dept | `/dept` | `…/system/dept.go` ×3 |
| | level | goframe_level | `/level` | `…/system/level.go` ×3 |
| | position | goframe_position | `/position` | `…/system/position.go` ×3 |
| | tenant | goframe_tenant | `/tenant` | `…/system/tenant.go` ×3 |
| **data** | config | goframe_config | `/config` | `…/data/config.go` ×3 |
| | configitem | goframe_config_item | `/config/item` | `…/data/configitem.go` ×3 |
| | param | goframe_param | `/param` | `…/data/param.go` ×3 |
| | dict | goframe_dict | `/dict` | `…/data/dict.go` ×3 |
| | dictitem | goframe_dict_item | `/dict/item` | `…/data/dictitem.go` ×3 |
| | notice | goframe_notice | `/notice` | `…/data/notice.go` ×3 |
| | city | goframe_city | `/city`(别名 `/area`) | `…/data/city.go` ×3 |
| **content** | article | goframe_article | `/article` | `…/content/article.go` ×3 |
| | category | goframe_category | `/category`(树形) | `…/content/category.go` ×3 |
| | link | goframe_link | `/link` | `…/content/link.go` ×3 |
| **monitor** | operlog | goframe_operation_log | `/oper/log` | `…/monitor/operlog.go` ×3 |
| | loginlog | goframe_login_log | `/login/log` | `…/monitor/loginlog.go` ×3 |
| | job | goframe_job | `/job` | `…/monitor/job.go` ×3 |
| | joblog | goframe_job_log | `/job/log` | `…/monitor/joblog.go` ×3 |
| **tool** | example | goframe_example | `/example` | `…/tool/example.go` ×3 |
| | filetemplate | goframe_file_template | `/file/template` | `…/tool/filetemplate.go` ×3 |
| | generator | —(读 information_schema) | `/generator` | `…/tool/generator.go` ×3 |
| | upload | —(存 resource/upload) | `/upload` | `…/tool/upload.go` ×3 |
> `user_role` 关联表不单列模块:归 `user` logic(保存用户-角色);`role_menu` 归 `role` logic(§3 扩展接口)。
> 生成物(每表一套,`make gen` 产出):`internal/dao/{m}.go`(可扩展)+ `internal/dao/internal/{m}.go`、`internal/model/entity/{m}.go`、`internal/model/do/{m}.go`、`internal/service/{m}.go`。以下各模块不再重复列出生成物。
**标准七件套启用矩阵**(✓=启用,—=豁免):
| 模块 | page | list | detail | add | update | delete | batchDelete | 导入 | 导出 |
|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| user | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | **✓** | **✓** |
| level | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | **✓** | **✓** |
| example | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | **✓** | **✓** |
| role / position / tenant / config / configitem / param / dict / notice / article / category / link / city / filetemplate | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
| menu / dept | ✓(新增权限点) | ✓(树形) | ✓ | ✓ | ✓ | ✓ | ✓(新增权限点) | — | — |
| operlog / loginlog / joblog | ✓ | ✓ | ✓ | **—**(日志不可增改) | **—** | ✓ | ✓ | — | — |
| job | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
> 导入导出定稿启用模块:**user、level** + **example**(前端已调用,PHP 参考亦有 `sys:example:import/export`)。
> 新增权限点(参考项目没有、本期补齐):`sys:menu:page`、`sys:menu:batchDelete`、`sys:dept:page`、`sys:dept:batchDelete`、`sys:city:page` 等——须同步写入 `goframe_menu` 初始化数据。
## 3. 模块文件明细(按域)
### 3.1 auth 认证域(无表)
**文件**:`api/v1/auth/auth.go`|`internal/controller/auth/auth.go`|`internal/logic/auth/auth.go`
| 方法 | HTTP | 路由 | 权限 | 日志 | 说明 |
|---|---|---|---|---|---|
| Login | POST | `/login` | 公开 | 写 **login_log**(logType=1) | `{username,password,tenantCode?,captcha?,uuid?}` → 校验租户 status/expire → 签发 access+refresh |
| Logout | GET | `/logout` | 登录 | 写 login_log(logType=2) | jti 入缓存黑名单 |
| Captcha | GET | `/captcha` | 公开 | — | 图形码,缓存 `captcha:{uuid}` TTL 300s |
| Refresh | POST | `/oauth2/token` | 公开(refresh_token) | — | 前端 `common/user.ts` 既有路由;轮换 token、旧 jti 拉黑 |
| CheckToken | GET | `/oauth2/check/token` | 登录 | — | 前端 `common/index.ts` 调用 |
| RemoveToken | DELETE | `/oauth2/remove/:token` | 登录 | — | 指定 token 拉黑 |
| Register | POST | `/register` | 公开 | — | 白名单已放行,路由未注册(启用时补实现) |
**api 结构体**:`LoginReq/LoginRes`(含 `accessToken/refreshToken/expiresIn/userInfo`)、`CaptchaRes`、`RefreshReq`、`CheckTokenRes`。
**logic 职责**:bcrypt 校验(`utility/crypto`)、租户校验(缓存)、双 token 签发(`utility/jwt`)、黑名单、login_log 落库。
### 3.2 index 个人中心/菜单聚合域(无表)
**文件**:`api/v1/index/index.go`|`controller/index/index.go`|`logic/index/index.go`
| 方法 | HTTP | 路由 | 权限 | 日志标题(取自 PHP 参考) |
|---|---|---|---|---|
| GetUser | GET | `/index/getUser` | 登录 | 个人中心-查询用户信息 |
| GetMenus | GET | `/index/getMenus` **和** `/index/getMenus/:id` | 登录 | 个人中心-查询菜单 |
| UpdatePassword | PUT | `/index/updatePassword` | 登录 | 个人中心-修改密码(旧密码校验+bcrypt) |
| UpdateProfile | PUT | `/index/updateProfile` | 登录 | 个人设置-更新个人信息 |
> `getMenus` 注册两条路由(`common/menu.ts` 无参、`system/menu.ts` 带 `/1`),同一 handler。
### 3.3 system 域
#### user 用户模块|表 `goframe_user`
**文件**:`api/v1/system/user.go`|`controller/system/user.go`|`logic/system/user.go`
**七件套**:全部启用(含 import/export)。扩展:
| 方法 | HTTP | 路由 | 权限 | 日志标题 |
|---|---|---|---|---|
| ResetPwd | PUT | `/user/resetPwd` | `sys:user:resetPwd` | 用户管理-重置密码 |
| Import | POST | `/user/import` | `sys:user:import` | 用户管理-导入数据 |
| Export | GET | `/user/export` | `sys:user:export` | 用户管理-导出数据(chunkList 分批防 OOM) |
**logic 私有方法**:`sUser.AssignRoles(userId, roleIds)`(写 `goframe_user_role`)、`checkTenantUserMax`(`tenant.max_user_num`)、手机号/用户名租户内唯一校验(走 uk)。
#### role 角色模块|表 `goframe_role` + `goframe_role_menu`
**文件**:`api/v1/system/role.go`|`controller/system/role.go`|`logic/system/role.go`
**七件套**:全部启用。扩展:
| 方法 | HTTP | 路由 | 权限 | 日志标题 |
|---|---|---|---|---|
| MenuList | GET | `/role/menu/list/:roleId` | `sys:role:list` | 角色管理-查询角色菜单 |
| SavePermission | POST | `/role/menu/save` | `sys:role:savePermission` | 角色管理-保存权限(重写 role_menu,清缓存) |
#### menu 菜单模块|表 `goframe_menu`
**文件**:`…/system/menu.go` ×3。七件套全启用;`List` 返回**树形**(日志:菜单管理-查询树形列表)。logic 含 `GetUserMenuTree(userId)`(供 index.GetMenus:user→role→role_menu→menu)。
#### dept 部门模块|表 `goframe_dept`
**文件**:`…/system/dept.go` ×3。七件套全启用;`List` 返回树形;禁止删除有子部门/用户的部门。
#### level 职级模块|表 `goframe_level`
**文件**:`…/system/level.go` ×3。七件套 + **Import/Export**:
`POST /level/import`、`GET /level/export`。
#### position 岗位模块|表 `goframe_position`
**文件**:`…/system/position.go` ×3。七件套全启用。
#### tenant 租户模块|表 `goframe_tenant`
**文件**:`…/system/tenant.go` ×3。七件套全启用。扩展:
| 方法 | HTTP | 路由 | 权限 | 日志标题 |
|---|---|---|---|---|
| Account | POST | `/tenant/account` | `sys:tenant:add` | 租户管理-创建租户账号(建租户+初始管理员,code=T+4位ID) |
| Users | GET | `/tenant/users/:tenantId` | `sys:tenant:list` | 租户管理-查询租户用户 |
### 3.4 data 域
| 模块 | 文件(`data/` 下三件套) | 扩展接口 |
|---|---|---|
| **config** | `config.go` ×3 | `GET /config/web/index`(设置页读取)、`POST /config/web/save`(Log:网站设置-保存,perm `sys:config:update`) |
| **configitem** | `configitem.go` ×3 | 七件套(路由前缀 `/config/item`) |
| **param** | `param.go` ×3 | 七件套 |
| **dict** | `dict.go` ×3 | 七件套 + `POST /dict/refreshCache`(Log:字典管理-刷新缓存,perm `sys:dict:update`)+ 别名 `GET /dictionary/list`、`GET /dictionary/info`(前端 `system/dictionary.ts`,**登录即可**) |
| **dictitem** | `dictitem.go` ×3 | 七件套 + 快捷 `GET /dict/item/getDictItemList/:code`(登录即可,缓存 `dict:item:{code}`) |
| **notice** | `notice.go` ×3 | 七件套 |
| **city** | `city.go` ×3 | 七件套 + `GET /city/getCityList/:parentCode`(Log:城市管理-查询下级城市)+ 别名 `GET /area/getParent`(Log:查询省份列表)、`GET /area/findByParentCode`(Log:查询城市列表)——均登录即可 |
### 3.5 content 域
| 模块 | 文件(`content/` 下三件套) | 说明 |
|---|---|---|
| article | `article.go` ×3 | 七件套,`List` 支持 `categoryId/status/keyword` |
| category | `category.go` ×3 | 七件套,`List` 返回**树形**(Log:分类管理-查询树形列表) |
| link | `link.go` ×3 | 七件套 |
### 3.6 monitor 域
#### operlog 操作日志|表 `goframe_operation_log`
**文件**:`…/monitor/operlog.go` ×3。启用 `page/list/detail/delete/batchDelete`(增改豁免)。
路由前缀 `/oper/log`:`GET /oper/log/page`、`GET /oper/log/list`、`GET /oper/log/detail/:id`、`DELETE /oper/log/delete/:id`、`DELETE /oper/log/batchDelete`。权限 `sys:operLog:{page|list|detail|delete|batchDelete}`。
#### loginlog 登录日志|表 `goframe_login_log`
**文件**:`…/monitor/loginlog.go` ×3。同上五件,前缀 `/login/log`,权限 `sys:loginLog:*`。
#### job 定时任务|表 `goframe_job`
**文件**:`…/monitor/job.go` ×3。七件套 + 控制接口(与 `monitor/job.ts` 逐条对齐):
| 方法 | HTTP | 路由 | 权限 | 日志标题 |
|---|---|---|---|---|
| RunOnce | GET | `/job/runOnce/:id` | `sys:job:execute` | 定时任务-执行任务 |
| Pause | GET | `/job/pause/:id` | `sys:job:pause` | 定时任务-暂停任务 |
| Resume | GET | `/job/resume/:id` | `sys:job:resume` | 定时任务-恢复任务 |
| Status | POST | `/job/status` | `sys:job:update` | 定时任务-设置状态 |
| DaemonStatus | GET | `/job/daemonStatus` | 登录即可 | 定时任务-查询调度器状态 |
**实现**:`internal/logic/job/daemon.go` — 基于 `gctrl` 的常驻调度器,启动时加载 `goframe_job` 中 `status=1` 任务,按 `cron_expression` 触发 `url+params`(HTTP 调用自身接口),执行结果写 `goframe_job_log`。
#### joblog 任务日志|表 `goframe_job_log`
**文件**:`…/monitor/joblog.go` ×3。五件套,前缀 `/job/log`,权限 `sys:jobLog:*`。
### 3.7 tool 域
| 模块 | 文件 | 接口 |
|---|---|---|
| **example** | `…/tool/example.go` ×3 | 七件套 + `POST /example/import`、`GET /example/export`(perm `sys:example:import/export`) |
| **filetemplate** | `…/tool/filetemplate.go` ×3 | 七件套(前缀 `/file/template`)+ `GET /file/template/getTemplateByCode/:code`(Log:文件模板-查询模板详情,登录即可) |
| **generator** | `…/tool/generator.go` ×3 | `GET /generator/page`(Log:代码生成-查询数据表,`sys:generator:page`)、`GET /generator/preview`(预览代码)、`POST /generator/generate`、`POST /generator/batchGenerate`(`sys:generator:generate`) |
| **upload** | `…/tool/upload.go` ×3 | `POST /upload/uploadFile`(Log:文件上传-上传文件,`sys:upload:file`;存 `resource/upload/`,返回相对 URL) |
## 4. 日志体系集成
### 4.1 操作日志(自动,中间件)
`internal/middleware/operation_log.go`:
1. 请求前记录 `start`;请求后查**路由元数据表**(§1.3 注册时生成),无 `log` 标签的路由(登录、公共查询别名)跳过;
2. 组装记录:`title`(log 标签)、`type`(logType)、`source=0`、`method`、`url`、`param`(JSON,**password/salt 脱敏**)、`result`(截断 8KB)、`ip`(gip)、`os/browser`(gclient)、`consume_time`、`status`(0 正常/1 异常,按 `code!=0`)、`error`;
3. **异步落库**:`chan` + 单 goroutine 批量写(100 条或 1s flush),失败降级 `glog`error,绝不阻塞业务;
4. 配置开关:`operationLog.enable=true`、`operationLog.logQuery=true`。
### 4.2 登录日志(业务写入)
`logic/auth` 内直接写 `goframe_login_log`:登录成功/失败(`log_type=1`,失败 `status=1` 存 `error`)、退出(`log_type=2`);记录 `username/ip/location/os/browser/consume_time`。
### 4.3 查询模块
即 §3.6 的 `operlog`、`loginlog` 两个模块(page/list/detail/delete/batchDelete)。
## 5. 认证 · 多租户 · 数据库(定稿要点)
- **JWT claims**:`user_id / tenant_id / jti / username / exp`;access 2h + refresh 7d 轮换;黑名单 `blacklist:token:{jti}` TTL=剩余时效。
- **ctx 键**(`consts/context_key.go`):`CtxUserID / CtxTenantID / CtxUsername / CtxRoles / CtxPerms`。
- **租户过滤**:`internal/model/hooks/hooks.go`(gdb model hook)——Insert 自动填 `create_user/create_time/del_flag=0/tenant_id`(主键交数据库自增);Select 自动 `del_flag=0` 且租户表追加 `tenant_id IN (0,当前)`;Update 填 `update_user/update_time`+租户条件;Delete 改写软删。**业务层禁止手写这些条件**。
- **中间件链**:`CORS → Response → JWT → Tenant(status/expire 缓存) → Permission(对照路由元数据表,超管放行) → OperationLog`。
- **五库驱动**(main.go blank import):`github.com/gogf/gf/contrib/drivers/{mysql,pgsql,mssql,oracle,sqlite}/v2`;切换只改 `database.default.type/link`。
- **DDL**:`resource/sql/{mysql,pgsql,mssql,oracle,sqlite}.sql`,表前缀 `goframe_`;MySQL 为规范基准;MSSQL 时间列 `datetime2`;主键自增(int),不用 ORM 回填主键。
- **红线**:禁方言 SQL、禁 `InsertIgnore/Replace`;主键由数据库自增(int),新增后经 `LastInsertId()` 回读(Oracle 方言差异由后续人工处理);删除一律软删、查询必经 dao。
## 6. 代码生成与工程文件
```yaml
# hack/config.yaml(键名已对 gf v2.10.3 源码核实)
gfcli:
gen:
dao:
link: "mysql:root:root@tcp(127.0.0.1:3306)/easygoadmin.goframe.elevue"
removePrefix: "goframe_"
jsonCase: "CamelLower"
descriptionTag: true
service:
srcFolder: "internal/logic"
dstFolder: "internal/service"
clear: true
```
```makefile
gen: ## 表结构变更后:重新生成 dao/service
gf gen dao -c hack/config.yaml && gf gen service -c hack/config.yaml
run: ; go run main.go
build: ; go build -o bin/admin-server main.go
test: ; go test ./...
```
前置:`go install github.com/gogf/gf/cmd/gf/v2@latest`。
## 7. 前端遗留与未启用项
| 项 | 前端路由 | 处理 |
|---|---|---|
| 站内信 message | `/message/*` | 前端废弃功能,后端无表无模块,不实现 |
| 数据源管理 | `/data/source/*` | 前端废弃功能,后端无表无模块,不实现 |
| 主控台统计 | `dashboard/console.ts` | **纯前端 mock,无需接口** ✅ |
| 短信/OSS/WebSocket | `/sms/*` `/aliyun/*` `/websocket/*` | 后端桩接口已删除;前端遗留调用为废弃代码 |
| 注册 | `/register` | 白名单已放行、路由未注册;启用时补建表逻辑+开放策略+租户归属规则 |
## 8. 落地实施顺序
1. **P0 骨架**:目录 + `main.go` 驱动 + config + Makefile + `hack/config.yaml` + router/bind + 6 中间件 + `utility/{jwt,response,crypto}` + `model/hooks` + MySQL DDL(`goframe_` 前缀、`del_flag`)
2. **P0**:auth + index(登录闭环)
3. **P1**:system 域 7 模块(含 RBAC、导入导出)
4. **P1**:data + content 域
5. **P1**:monitor(operation_log 中间件 + 双日志模块 + job 调度)
6. **P2**:tool 域(example/fileTemplate/upload/generator)
7. **P2**:其余 4 份方言 DDL + SQLite 内存库集成测试
## 9. 环境信息
- MySQL:`127.0.0.1:3306`,账号 `root`/`root`,数据库 `easygoadmin.goframe.elevue`(注意库名含点,DSN 直接使用,DDL 中引用需反引号)
- 参考表结构源:`document/easygoadmin.goframe.elevue.sql`(`think_` 前缀、`is_delete` 字段)
- 前端 API 定义:`ui/src/api/**`;方法命名参考:`D:\xampp8.2\htdocs\v3\RXThinkCMF_ThinkPhp8_EleVue\app\controller`
## 10. 文件与富文本处理契约
### 10.1 上传两段式
- 上传接口(POST /api/upload/uploadFile)先落 `resource/upload/temp/{日期}/{随机名}`,返回 `/upload/temp/**` URL(可直接预览);
- 表单提交保存时由 `SaveFileField/SaveFileFields`(internal/logic/base.go)把 temp 文件移动到正式子目录(如 `user/20261013/时分秒_随机.ext`),库中与响应统一存 `/upload/**` URL;
- 校验:扩展名白名单(file.imageExt / file.fileExt)、单文件上限(file.maxSize MB)、图片魔数嗅探;
- 运营闭环:每日 03:30 自动清理超过 `file.tempExpireDays` 天的 temp 日期目录;`/upload/**` 静态访问由 `SetServerRoot("resource")` 提供;`.svg` 响应强制 `Content-Disposition: attachment` + `nosniff`(防存储型 XSS)。
### 10.2 富文本占位符(持久化契约)
- 正文(如 article.content)存库前经 `LocalizeContentFields` 处理:
- 本站 `/upload/temp/**` 引用 → 转正并替换为 `[IMG_URL]/{相对路径}`;
- 本站 `/upload/**` 正式引用 → 去域名替换为 `[IMG_URL]/{相对路径}`;
- 外站 URL 不处理(规避 SSRF);
- 匹配范围:`img|video|source|embed` 的 src 与 `a` 的 href(带文件后缀);
- 读取侧统一经 `ExpandContentFields` 还原为 `/upload/**`(唯一还原出口,禁止业务代码手写字符串替换);
- `[IMG_URL]` 为持久化格式的一部分:库内 content 字段不供第三方系统直接消费。
### 10.3 logic 共享能力层(internal/logic/base.go)
- 分区:文件字段 / 富文本 / 枚举文本(DictGetText/SerializeMapOnRead)/ 唯一校验 / 事务 / 分块查询 / 树 / 临时目录清理;
- 纪律:只依赖 dao/utility/consts;导出函数上限15个,超出按能力拆子包;
- `CheckUnique` 仅为 UX 快速报错(先查后插存在 TOCTOU),并发唯一性以数据库唯一索引为准——调用点必须有对应 `uk_*` 索引(config/dict/param/file_template 已补 `uk_code`);
- `WithTrans` 用于多写原子性(如租户开户),事务内禁止 HTTP/文件IO,bcrypt 等 CPU 操作须在事务外预计算。