# 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 操作须在事务外预计算。