diff --git a/docs/audit/gva-interface-parity-plan.md b/docs/audit/gva-interface-parity-plan.md new file mode 100644 index 0000000..a550fcd --- /dev/null +++ b/docs/audit/gva-interface-parity-plan.md @@ -0,0 +1,314 @@ +# Kra 与官方 GVA 接口一致性审查与整改计划 + +## 1. 审查结论 + +本计划以官方 GVA 快照 +`C:\Users\Yvan\AppData\Local\Temp\gva-parity-02f3783`、commit +`02f37833255e0e339c3d69199cb5a468f17de9fc`(2026-08-06)为唯一行为基准, +逐项比较当前 Kra 的方法/路径、请求绑定与默认值、边界校验、鉴权与数据 +范围、事务与副作用、响应 envelope,以及前端调用覆盖。 + +当前本地路由契约测试固定了 **178 条已注册 Gin 路由**。这 178 条包含 +`/health` 与 Swagger;本地上传文件的 `GET`/`HEAD` 通过 +`NoRoute -> serveLocalStorage` 动态处理,不计入 `engine.Routes()`。GVA 则 +用 `StaticFS` 注册静态 wildcard,因此“178”只能表示本地注册表大小,不能 +作为两端完整路由集合相等的证明。 + +审查结果可以概括为: + +- 纳入范围的普通业务接口,在正常成功/失败路径上的绑定、权限、主要数据 + 变更、事务和 JSON envelope 基本与 GVA 一致;逐接口证据见下方三份矩阵。 +- 没有发现需要判为 P0 的已确认缺陷。 +- 有 4 项 P1 路由/管理契约差异、3 项 P2 部署或故障语义差异、2 项 P3 + 文档/输入兼容差异,详见第 4 节。 +- “操作审计额外脱敏/trace 元数据”不是缺口:复核后确认 GVA 与 Kra 都 + 具备同一组敏感字段脱敏、1 MiB 响应捕获上限以及 request/trace/device + 元数据;只需保留回归测试。 + +## 2. 范围与排除项 + +### 纳入范围 + +纳入所有已经迁移到 Kra 的系统、媒体、公告和邮件接口。模块级路由计数 +如下,合计 178 条: + +| 模块 | 路由数 | 逐接口矩阵 | +| --- | ---: | --- | +| Bootstrap/public(health、base、init、Swagger) | 6 | [identity/access](./parity-identity-access.md)、[crosscut](./parity-crosscut.md) | +| API registry + Casbin | 16 | [identity/access](./parity-identity-access.md) | +| User + JWT session | 14 | [identity/access](./parity-identity-access.md) | +| Authority + menu + button permission | 23 | [identity/access](./parity-identity-access.md) | +| Department + position | 14 | [identity/access](./parity-identity-access.md) | +| Dictionary + dictionary detail | 17 | [ops/content](./parity-ops-content.md) | +| Parameters + security + system + API token | 16 | [ops/content](./parity-ops-content.md) | +| Operation/login/data-access/log viewer/error audit | 19 | [ops/content](./parity-ops-content.md) | +| Export template + version | 19 | [ops/content](./parity-ops-content.md) | +| Timed task + SSE | 9 | [ops/content](./parity-ops-content.md) | +| Media files + categories + chunk upload | 15 | [ops/content](./parity-ops-content.md)、[crosscut](./parity-crosscut.md) | +| Announcement + email | 10 | [ops/content](./parity-ops-content.md) | + +完整的本地调用链、前端覆盖、未被前端调用的后端接口和 178 条逐行路由 +清单见 [local-api-inventory.md](./local-api-inventory.md)。其中除 +`GET /health`、`GET /swagger/*any` 两条平台路由外,其余 176 条已迁移业务 +路由都在 identity/access 或 ops/content 表中逐行出现;ops 表额外保留一行 +GVA 的 AI `getSysErrorSolution` 作为 `EXCLUDED` 对照,因此不能用该表的 +原始行数直接当作纳入计数。 + +覆盖核对结果:两个逐接口矩阵合计 177 行、去重后仍为 177 行;与本地 +178 条注册路由比较时,仅缺少上面两条平台路由,且唯一多出的行正是被标记 +为 `EXCLUDED` 的 GVA AI action。平台路由由 crosscut 审查,未被遗漏。 + +### 明确排除 + +按用户要求,不把下列未迁移或明确不需要迁移的模块计入一致性缺口: + +- `system/sys_auto_code.go`、`sys_auto_code_history.go`、`sys_skills.go`; +- GVA plugin 下的 AI/LLM/MCP/CLI/skills、auto/plugin-management 路由; +- `example/exa_customer.go` 及 customer/example 功能; +- `GET /sysError/getSysErrorSolution`:它调用 `AutoCodeService.LLMAuto`, + 仅在 ops 矩阵中标记为 `EXCLUDED`,不要求补迁移、Casbin seed 或前端动作。 + +公告和邮件虽然在 GVA 中以 plugin 目录组织,但 Kra 已迁移且存在对应路由, +因此纳入审查。 + +## 3. 逐接口审查口径 + +每一行接口都按以下顺序对照: + +1. HTTP method/path、router prefix、公共/私有分组和 operation-record 选择; +2. JSON/query/multipart/SSE 绑定、字段名、默认值及必填校验; +3. JWT、强制改密、Casbin、DataScope 和错误码/HTTP 状态; +4. usecase/repository 的数据变更、事务边界、缓存/黑名单/调度器/文件/邮件 + 等副作用; +5. 成功与失败的 `{code,data,msg}` envelope、特殊下载/流式响应和前端调用。 + +`OK` 只表示在该接口的正常行为和已检查的边界行为上未发现材料差异, +不表示两端内部实现必须相同。持久化黑名单取代 GVA 内存缓存、每请求读取 +Casbin 数据库、动态配置热加载等属于可接受实现差异,但必须覆盖故障测试。 + +## 4. 已确认差异与整改顺序 + +### P1-01:静态上传 URL 没有进入本地路由表 + +**接口/范围**:GVA `GET /uploads/file/*filepath`、`HEAD /uploads/file/*filepath`; +以及所有依赖 `engine.Routes()` 的 `/api/syncApi`、`/init/initdb`、启动路由 +日志和 API registry。 + +- GVA 在 `server/initialize/router.go:60-61` 用 + `StaticFS(global.GVA_CONFIG.Local.StorePath, ...)` 注册 GET/HEAD wildcard, + 并把它们放进 `global.GVA_ROUTERS`。 +- Kra 在 `internal/server/gin.go:63-69,117-141` 只在 `NoRoute` 中按 + `Local.PathPrefix` 动态服务文件,所以文件能下载,但不出现在 + `engine.Routes()` 或 `/api/syncApi` 输入;访问日志的 route 也会是 + `unmatched`。 +- 默认配置中 `store-path` 与 `path-prefix` 都是 `uploads/file`,但配置 + 分离时两端暴露的 URL 集合不同。基准 GVA 的预生成 Swagger 同样没有 + 静态 wildcard,故 Swagger 不单独计为已确认差异。 + +**整改选择**: + +- 严格兼容方案:注册与 GVA 同形的 GET/HEAD wildcard,以 `store-path` 为 + URL 前缀,同时保留 `nosniff`、非安全媒体 attachment 和目录拒绝;或 +- 保留 NoRoute 方案,但把静态 URL 明确列为“非 API registry 路由”,在 + sync/init/日志契约中固定排除并补充迁移说明。 + +**验收测试**:临时目录、`store-path != path-prefix`、GET/HEAD、目录、 + 不安全扩展名、router prefix、`syncApi` route diff、初始化 API seed。 + +### P1-02:`GET /api/freshCasbin` 的失败语义不一致 + +- GVA `server/api/v1/system/sys_api.go:314-329` 调用 + `CasbinService.FreshCasbin()`;`LoadPolicy`/enforcer 不可用时返回 HTTP + 200、`code=7`、`刷新失败`。 +- Kra `internal/server/handler/api.go:201-206` 把该接口实现为恒定成功的 + DB-policy no-op,返回 `code=0`、`刷新成功`,不会暴露策略存储故障。 + +**计划**:决定该兼容接口是执行实际策略加载/可用性检查,还是正式声明 + “无内存 enforcer 的 no-op”。若目标是 GVA parity,应在失败时返回同样的 + code/message;至少增加数据库故障注入测试,保证行为不是静默成功。 + +**验收测试**:正常刷新、策略表不可读、事务中断、无 enforcer/空连接, + 比较 status/code/msg 和后续授权是否生效。 + +### P1-03:非空 `routerPrefix` 时 API sync/init 的路径规范不同 + +**接口**:`GET /api/syncApi`、`POST /api/enterSyncApi`、`POST /init/initdb`, +以及由 API path 进入 Casbin policy 的授权链路。 + +- Kra `internal/service/api.go:20-27,136-145` 和 + `internal/service/system_init.go:40-48` 会去掉 `routerPrefix`,把路径 + 存成 `/api/...`;授权中间件也按无前缀路径查策略。 +- GVA 直接比较带前缀的 `global.GVA_ROUTERS`,因此配置 `/admin` 时 + `syncApi`/初始化看到的是 `/admin/api/...`;默认的无前缀 ignore seed + 可能无法命中。 + +**计划**:先确定数据库 API path 的唯一规范(带前缀或不带前缀),然后 + 同时修改 sync、init seed、ignore、enterSync 和 Casbin authorize;禁止只 + 修一个接口导致数据库与运行时策略混用两种 path。保留旧数据迁移/回滚 + 策略。 + +**验收测试**:空前缀、`/admin` 前缀、前缀尾斜杠、Swagger、sync diff、 + initdb seed、ignoreApi、setApiRoles/updateCasbin 和真实授权请求。 + +### P1-04:CORS 中间件的启用状态与基准不同 + +**接口/范围**:所有 HTTP 接口,尤其 `OPTIONS` 预检、strict-whitelist 和 + `allow-all` 配置。 + +- GVA 基准 `server/initialize/router.go:64-65` 的 `Cors/CorsByRules` + 都是注释状态,当前快照默认不安装 CORS 中间件;配置文件要求手动启用。 +- Kra `internal/server/gin.go:27` 无条件安装 `CORS(runtime)`。因此默认 + `OPTIONS`、以及配置 `allow-all`/`strict-whitelist` 时的 header、204/403 + 可达性都可能与 GVA 不同;strict 模式会直接拒绝 GVA 基准会继续处理的 + 非白名单请求。 + +**计划**:决定 Kra 是否要保留这一增强。如果目标是严格 parity,应让启用 + 由与 GVA 相同的部署开关控制;如果产品明确需要默认 CORS,则把它作为 + Kra 专有契约并从“GVA 一致”声明中剥离。 + +**验收测试**:无 Origin、allow-all、whitelist 命中/未命中、strict 未命中、 + OPTIONS、`/health` 例外、带 router prefix 的请求。 + +### P2-01:JWT 黑名单存储故障的 fail-open/fail-closed 语义不同 + +**接口/范围**:所有私有接口,以及 `POST /jwt/jsonInBlacklist`、API token +撤销后的访问。 + +- GVA `server/middleware/jwt.go:25-29,81-88` 只检查缓存命中;缓存读取 + 没有错误返回,故障时可能继续解析普通 JWT。 +- Kra `internal/data/api_token.go:128-140` 的黑名单 DB `Count` 出错会被 + `AuthenticateToken` 映射为 `ErrTokenDisabled`,把所有私有请求返回为 + 401“异地登陆或令牌失效”。 + +**计划**:明确安全策略是 fail-closed 还是可用性优先,并在迁移说明中固定; + 若要求 GVA 可用性语义,需区分“明确命中黑名单”和“存储不可用”,不要 + 把后者伪装成 token 已失效。 + +**验收测试**:黑名单命中/未命中、DB/Redis 超时、服务重启后的黑名单加载、 + token 刷新与多点登录。 + +### P2-02:安全限流 key 与作用面不同 + +**接口**:`POST /base/login`、`POST /base/captcha`,以及错误 method/未知 +路径的边界请求。 + +- GVA 只在 `sys_base.go:13-14` 的两个 POST route 上安装 + `SecurityLimit()`,key 为 `GVA_SecLimit`。 +- Kra `internal/server/middleware/rate_limit.go:13-36` 通过全局 URL + suffix 判断,key 为 `KRA_SecLimit`,不检查 HTTP method; + 因此未知路径或错误 method 也可能递增限流计数。 +- 共享 Redis/滚动部署时,前缀不同会使两端计数不连续。 + +**计划**:将限流收窄到精确的 POST route,并在需要滚动兼容时使用 + `GVA_SecLimit` 或双读迁移窗口;保留缓存异常 fail-open 的既有语义。 + +**验收测试**:正确 POST、GET/未知 path、router prefix、窗口边界、共享 + Redis key、缓存故障。 + +### P2-03:版本响应头名称不同 + +**接口/范围**:所有成功、业务失败、鉴权失败和未匹配路由响应。 + +- Kra `internal/server/middleware/access_log.go:35` 写 `X-Kra-Version`。 +- GVA `server/middleware/access_log.go:69-73` 写 `X-Gva-Version`。 + +**计划**:兼容期双写两个 header,或将 GVA header 作为唯一公共契约;增加 + `/health`、`/base/captcha`、一个私有接口和 404 的 header contract test。 + +### 横切回归说明(不新增 parity 缺口) + +以下实现差异目前未判为正常路径缺口,但应纳入同一套回归:Kra 每请求从 +DB 读取 Casbin、持久化 JWT 黑名单、动态 runtime/CORS reload、`ErrorAudit` +对已知 code 7 的过滤、SSE/下载响应捕获。它们都可能在存储或中间件故障时 +改变可观察结果。 + +### P3-01:空 `authorityIds` 的安全输入处理不同 + +**接口**:`POST /user/setUserAuthorities`。 + +GVA 在删除旧关联后直接访问 `authorityIds[0]`,空数组会变成 panic/500; +Kra 在 `internal/biz/user.go:165-170` 返回确定性的业务错误且不丢关联。 +这是更安全的本地行为,不应复制 GVA 的 panic;应统一成“至少一个角色”的 +显式校验,并把差异写入客户端契约。 + +### P3-02:Swagger 空前缀 `basePath` 字面值不同 + +GVA 生成文档的 `docs.SwaggerInfo.BasePath` 默认是空字符串;Kra +`internal/server/swagger.go:59-63` 在无前缀时输出 `basePath:"/"`。通常 URL +语义等价,但严格 JSON snapshot 或生成客户端可能可见。除非需要字面兼容, +可在 P3 阶段修正文档生成器或明确接受差异。 + +## 5. 模块级状态摘要 + +| 模块 | 逐接口结论 | 需要跟踪的横切项 | +| --- | --- | --- | +| Bootstrap/base/init/Swagger | 基础绑定、响应和登录流程对齐 | P1-03、P1-04、P2-03、P3-02 | +| API/Casbin | API CRUD、策略替换、同步正常路径对齐 | P1-01、P1-02、P1-03 | +| User/JWT/session | 用户和 token 正常流程对齐 | P2-01、P2-02、P2-03、P3-01 | +| Authority/menu/button | 角色、菜单、按钮和数据范围正常路径对齐 | P1-03(path 规范) | +| Department/position | 组织树、主部门、岗位关系正常路径对齐 | P1-03(path 规范) | +| Dictionary/parameters | CRUD、树、导入导出和分页对齐 | P2 审计/故障回归 | +| System/security/config/token | 配置、reload、API token 和黑名单正常路径对齐 | P1-04、P2-01、P2-02 | +| Audit/log viewer/error | 非 AI 错误/日志接口逐项对齐 | `getSysErrorSolution` 排除;P2 回归 | +| Export/version | token 下载、模板和版本包流程对齐 | P1-01(静态下载 route table) | +| Timed task/SSE | 调度、副作用、SSE heartbeat/event 对齐 | 中间件超时/捕获回归 | +| Media/chunk upload | 文件引用、哈希、分片和权限对齐 | P1-01、存储故障回归 | +| Announcement/email | CRUD、公开数据源、邮件发送对齐 | CORS/header contract | + +## 6. 实施顺序 + +### 阶段 A:先定兼容契约(不改业务数据) + +1. 决定 API path 的规范(是否保存 router prefix)。形成一份迁移说明, + 明确旧数据库记录、Casbin policy、ignore API 和 `syncApi` 的转换规则。 +2. 决定静态文件是严格注册为 GVA route,还是正式保留 NoRoute 动态路由; + 同时决定 CORS 是部署开关还是 Kra 默认能力。 +3. 决定 JWT 黑名单存储故障的 fail-open/fail-closed 策略,并记录安全评审 + 结论。 + +### 阶段 B:处理 P1 + +按 P1-01 -> P1-02 -> P1-03 -> P1-04 顺序实现,每项都先补失败测试,再改 +实现,最后运行对应模块测试。P1-03 必须和 P1-01 一起验证,因为静态 +route table 会直接进入 sync/init seed。 + +### 阶段 C:处理 P2/P3 兼容项 + +- P2:双写/统一版本 header;统一或双读限流 key;明确黑名单存储故障响应; + 收窄限流 method/path。 +- P3:空角色数组显式校验;Swagger `basePath` 字面值;将所有“安全增强但 + 有差异”的行为写入迁移文档。 + +### 阶段 D:回归与灰度 + +先在单实例 SQLite/内存依赖跑合同测试,再用共享 Redis/数据库做双版本滚动 +测试;最后执行前端登录、切换角色、菜单/API/按钮授权、文件上传下载、版本 +导入导出、任务 SSE 和公告/邮件 smoke 流程。 + +## 7. 回归测试清单 + +| 测试层 | 必测内容 | +| --- | --- | +| Route contract | 178 条注册路由;静态 GET/HEAD;空前缀与 `/admin`;重复 method/path;startup route log | +| Binding/envelope | 每个矩阵接口的缺字段、类型错误、空数组、分页边界;HTTP 200 + code 7、401、409、403、500 | +| API sync/init | `syncApi`、`enterSyncApi`、`initdb`、ignore seed、静态 route、router prefix | +| Auth/session | 登录/验证码限流、JWT 过期刷新、黑名单、API token 撤销、多点登录、MustChangePwd allow-list | +| Authorization | Casbin deny/allow、freshCasbin 失败、DataScope 1/2/4/5、角色/菜单/按钮/部门关系事务 | +| Cross-cutting | CORS modes/OPTIONS、版本 header、request/trace metadata、operation/error audit、panic/broken-pipe | +| Storage/media | GET/HEAD upload headers、对象引用计数、URL/分片 MD5、owner check、原子 complete、token 下载一次性消费 | +| Task/content | scheduler create/update/toggle/trigger、SSE heartbeat/event、字典层级、版本 staged errors、公告公开端点、邮件发送 | + +## 8. 验收门槛与当前限制 + +完成标准: + +- P1 项要么修复并有集成测试,要么由产品/运维明确签字接受为 Kra 专有 + 契约; +- 每个纳入模块的逐接口矩阵仍保持一行一接口,不能用“模块整体 OK”替代; +- 共享 Redis、非空 router prefix、存储故障和数据库不可用均有可重复测试; +- 前端使用的接口和当前无前端调用但已注册的后端接口都完成契约确认。 + +本轮只新增审计文档,没有修改业务代码。已通过 `git diff --check`;全量 +`go test ./...` 未能在当前受限环境中执行,原因是用户级 Go cache 需要沙箱外 +权限且审批服务返回 503,不应把它误写成测试失败。实现阶段必须在可用的构建 +环境补跑全量测试。 diff --git a/docs/audit/local-api-inventory.md b/docs/audit/local-api-inventory.md new file mode 100644 index 0000000..364c91a --- /dev/null +++ b/docs/audit/local-api-inventory.md @@ -0,0 +1,370 @@ +# Local Administration API Inventory + +## Scope and method + +This is the local baseline for the GVA parity audit. It inventories every +route registered by `internal/server/gin.go`, excluding no local route from the +inventory. The current route-contract test asserts **178 registered Gin +routes**, including `GET /health` and the Swagger wildcard. A configured +`routerPrefix` is prepended to every path below at runtime. Local-file +downloads are handled by the `NoRoute -> serveLocalStorage` fallback, so their +`GET`/`HEAD` URL is functional but intentionally does **not** appear in this +registered-route count, `engine.Routes()`, or `/api/syncApi` input. The local +dynamic Swagger is likewise built only from registered routes; the baseline +GVA's pre-generated Swagger also omits the static wildcard. The GVA reference +does register that wildcard in `Routes()`, so the count is not proof that the +two route tables are identical; see `parity-crosscut.md`. + +Source of truth: + +- registration: `internal/server/router/*.go` and `internal/server/gin.go`; +- handler-to-service calls: `internal/server/handler/*.go`; +- dependency assembly: `cmd/kratos-admin/wire_gen.go`; +- frontend comparison: `web/src/api/**/*.js`, `web/src/modules/**/api/*.js`, + and the direct SSE use in `web/src/view/systemTools/timedTask/useAlertStream.js`. + +Unless marked `public`, routes are behind JWT authentication, forced-password +change protection, Casbin/data-scope access control, and the normal error/audit +middleware. `OK` means the standard JSON success envelope; `Page` means +`{ list, total, page, pageSize }`. Download and SSE rows state their special +wire result. The `Call` column gives the exact handler and service entrypoint; +the module pipeline directly below it completes the service/biz/data chain. + +`FE` means a matching frontend request exists. `FE-direct` means the browser +calls it directly rather than via an `api/*.js` function. `--` is a registered +backend route without a current frontend call site. + +## Architecture map + +| Local area | Handler service | Biz usecase | Repository/data implementation | +| --- | --- | --- | --- | +| API, Casbin | `APIService` | `APIUsecase` | `APIRepo` -> `data.apiRepo` | +| User/navigation | `UserService`, `AuthService`, `TokenService` | `UserUsecase`, `AuthenticationUsecase`, `TokenUsecase` | `UserRepo`, `APITokenRepo` -> `data.userRepo`, `data.apiTokenRepo` | +| Authority | `AuthorityService`, `PermissionService` | `AuthorityUsecase`, `PermissionUsecase` | `AuthorityAccessRepo`, `PermissionRepo` -> `data.authorityAccessRepo`, `data.permissionRepo` | +| Menu | `MenuService` | `MenuUsecase` | `MenuRepo` -> `data.menuRepo` | +| Organisation | `DepartmentService`, `PositionService` | `DepartmentUsecase`, `PositionUsecase` | `DepartmentRepo`, `PositionRepo` -> `data.departmentRepo`, `data.positionRepo` | +| Dictionary | `DictionaryService` | `DictionaryUsecase` | `DictionaryRepo` -> `data.dictionaryRepo` | +| Parameters | `ParameterService` | `ParameterUsecase` | `ParameterRepo` -> `data.parameterRepo` | +| Runtime/system/security | `SystemConfigService`, `SecurityService` | `SystemConfigUsecase`, `SecurityUsecase` | `InitializationRepo`, `SecurityRepo` -> `data.initializationRepo`, `data.securityRepo`; runtime settings/config store | +| Audit/logs/errors | `AuditService`, `LogViewerService` | `AuditUsecase`, `LogViewerUsecase` | `AuditQueryRepo`, `LogFileRepo` -> `data.auditRepo`, `data.logFileRepo` | +| Export/version | `ExportService`, `VersionService` | `ExportUsecase`, `VersionUsecase` | `ExportRepo`, `VersionRepo` -> `data.exportRepo`, `data.versionRepo`; export token cache | +| Tasks | `TaskService` | `TaskApplicationUsecase` -> `TaskUsecase` | `TaskRepo` -> `data.taskRepo`; worker scheduler/runtime | +| Media | `MediaService` | `MediaUsecase` | `MediaRepo` -> `data.mediaRepo`; `FileStorage`, upload sessions | +| Announcement/email | `AnnouncementService`, `EmailService` | `AnnouncementUsecase`, `EmailUsecase` | `AnnouncementRepo` -> `data.announcementRepo`; `EmailRepo` -> `data.NewEmailRepo(runtime)` | + +## 1. Bootstrap and public endpoints + +| Method/path | Visibility | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | --- | +| `GET /health` | public | inline Gin handler | none -> `"ok"` | liveness only | -- | +| `GET /swagger/*any` | public | `registerSwagger` | Swagger UI/document -> HTML/JSON | generated from registered Gin routes | -- | +| `POST /base/captcha` | public | `Public.Captcha -> SecurityService.Captcha` | captcha config -> captcha payload | cache-backed captcha creation when enabled | FE | +| `POST /base/login` | public | `Public.Login -> AuthService.Login -> AuthenticationUsecase -> User/Security/Audit repos` | `LoginRequest` -> token/user result | authenticate, issue token, write login audit; apply lockout policy | FE | +| `POST /init/checkdb` | public | `Public.CheckDatabase -> SystemConfigService.IsInitialized -> SystemConfigUsecase -> InitializationRepo` | none -> initialization status | read DB/init state | FE | +| `POST /init/initdb` | public | `Public.InitializeDatabase -> SystemConfigService.InitializeRoutes -> SystemConfigUsecase -> InitializationRepo` | `dto.DatabaseInitRequest` + current routes -> OK | initialize schema/seed data and register current routes | FE | + +## 2. API registry and Casbin policy + +Pipeline: `API handler -> APIService -> APIUsecase -> APIRepo -> data.apiRepo`. +The Casbin endpoints share that pipeline because policy-path associations are +stored and queried through the API repository. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /api/getApiList` | `API.List -> ListAPI` | `APIListRequest(page,pageSize,filters)` -> `Page` | read | FE | +| `POST /api/getAllApis` | `API.All -> AllAPI` | optional body ignored -> `{apis}` | read non-paginated, strict filtering | FE | +| `POST /api/createApi` | `API.Create -> CreateAPIRequest` | `APIRequest(path,method,group,description)` -> `{api}` | create API registry row | FE | +| `POST /api/updateApi` | `API.Update -> UpdateAPIRequest` | `APIRequest` -> OK | update registry row | FE | +| `POST /api/deleteApi` | `API.Delete -> DeleteAPI` | `{id}` -> OK | delete one API and policy association as repo defines | FE | +| `DELETE /api/deleteApisByIds` | `API.DeleteByIDs -> DeleteAPIs` | `{ids}` -> OK | batch delete APIs | FE | +| `POST /api/getApiById` | `API.Find -> FindAPIResponse` | `{id}` -> `{api}` | read | FE | +| `GET /api/getApiGroups` | `API.Groups -> Groups` | none -> `{groups,apiGroupMap}` | read group metadata | FE | +| `GET /api/getApiRoles` | `API.Roles -> APIRoleIDs` | `path,method` -> `authorityIds[]` | read role association | FE | +| `POST /api/setApiRoles` | `API.SetRoles -> SetAPIRoles` | `path,method,authorityIds[]` -> OK | replace API-role association | FE | +| `GET /api/syncApi` | `API.Sync -> SyncAPIResponses` | live `engine.Routes()` -> sync diff | calculate registry/runtime route diff | FE | +| `POST /api/ignoreApi` | `API.Ignore -> SetAPIIgnored` | `path,method,flag` -> OK | mark route ignored for sync | FE | +| `POST /api/enterSyncApi` | `API.ApplySync -> ApplyAPISyncRequest` | selected sync actions -> OK | apply API registry synchronization | FE | +| `GET /api/freshCasbin` | `API.FreshCasbin` | none -> OK | **public** compatibility no-op; policy is read per authorization decision | FE | +| `POST /casbin/updateCasbin` | `API.SetPolicyPaths -> SetPolicyPathsRequest` | `authorityId,paths[]` -> OK | replace policy paths for authority | FE | +| `POST /casbin/getPolicyPathByAuthorityId` | `API.PolicyPaths -> PolicyPathResponses` | `{authorityId}` -> `{paths}` | read policy paths | FE | + +## 3. Users, session, and navigation + +User pipeline: `User/Navigation handler -> UserService -> UserUsecase -> UserRepo -> data.userRepo`. +Authentication paths add `AuthenticationUsecase` with `SecurityUsecase` and +the audit recorder; logout uses `TokenUsecase -> APITokenRepo`. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /user/getUserList` | `User.List -> ListUsersRequest` | `UserListRequest` -> `Page` | read, data-scope aware | FE | +| `POST /user/admin_register` | `User.Create -> CreateUserRequest` | `UserRequest` -> `{user}` | create user; password-policy validation | FE | +| `PUT /user/setUserInfo` | `User.Update -> UpdateUserRequest` | `UserRequest(id,profile)` -> OK | update selected user | FE | +| `PUT /user/setSelfInfo` | `User.UpdateSelf -> UpdateSelfUser` | authenticated `SelfUserRequest` -> OK | update current user's profile | FE | +| `DELETE /user/deleteUser` | `User.Delete -> DeleteUser` | `{id}` -> OK | delete user | FE | +| `POST /user/resetPassword` | `User.ResetPassword -> ResetPassword` | `id,password` -> OK | set another user's password | FE | +| `POST /user/changePassword` | `User.ChangePassword -> ChangePassword` | current and new passwords -> OK | verify and replace current password | FE | +| `PUT /user/setSelfSetting` | `User.SetSelfSetting -> SetUserSetting` | current-user UI settings -> OK | persist user settings | FE | +| `POST /user/setUserAuthorities` | `User.SetAuthorities -> SetUserAuthorities` | `id,authorityIds[]` -> OK | replace user authority groups | FE | +| `POST /user/setUserAuthority` | `User.SwitchAuthority -> AuthService.SwitchAuthority` | current claims + `authorityId` -> new token/user | switch active authority and issue new token | FE | +| `GET /user/getUserInfo` | `User.Get -> UserByUUID` | current claims -> `{user}` | read self/authority info | FE | +| `POST /menu/getMenu` | `Navigation.Menu -> UserService.Menus` | current authority -> menu tree | read authorized navigation | FE | +| `POST /jwt/jsonInBlacklist` | `Session.Logout -> TokenService.BlacklistToken` | bearer token -> OK | blacklist/revoke token | FE | + +## 4. Authorities, menus, and button permissions + +Authority and permissions pipeline: `handler -> AuthorityService/PermissionService -> +AuthorityUsecase/PermissionUsecase -> AuthorityAccessRepo/PermissionRepo -> data`. +Menu pipeline: `Menu handler -> MenuService -> MenuUsecase -> MenuRepo -> data.menuRepo`. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /authority/getAuthorityList` | `Authority.List -> Authorities` | none -> authority tree/list | read | FE | +| `POST /authority/createAuthority` | `Authority.Create -> CreateAuthorityRequest` | authority id/name -> `{authority}` | create authority | FE | +| `POST /authority/copyAuthority` | `Authority.Copy -> CopyAuthorityRequest` | source + new authority -> `{authority}` | duplicate authority permissions/menu relations | FE | +| `PUT /authority/updateAuthority` | `Authority.Update -> UpdateAuthorityRequest` | authority fields -> `{authority}` | update authority | FE | +| `POST /authority/deleteAuthority` | `Authority.Delete -> DeleteAuthority` | `{authorityId}` -> OK | delete authority subject to repo rules | FE | +| `POST /authority/setRoleUsers` | `Authority.SetUsers -> SetAuthorityUsers` | authority/user ids -> OK | replace authority-user relation | FE | +| `GET /authority/getUsersByAuthority` | `Authority.Users -> AuthorityUserIDs` | `authorityId` -> `userIds[]` | read relation | FE | +| `POST /authority/setDataScope` | `Authority.SetDataScope -> SetDataScope` | scope and department ids -> OK | update data-scope and departments | FE | +| `GET /authority/getDataScopeDepts` | `Authority.DataScopeDepartments -> DataScopeDepartmentIDs` | `authorityId` -> `departmentIds[]` | read custom scope departments | FE | +| `POST /menu/getMenuList` | `Menu.List -> Tree` | none -> menu tree | read | FE | +| `POST /menu/getBaseMenuTree` | `Menu.Tree -> Tree` | none -> `{menus}` | read | FE | +| `POST /menu/addBaseMenu` | `Menu.Create -> Create` | `MenuRequest` -> OK | create menu, parameters/buttons | FE | +| `POST /menu/updateBaseMenu` | `Menu.Update -> Update` | `MenuRequest` -> OK | update menu | FE | +| `POST /menu/deleteBaseMenu` | `Menu.Delete -> Delete` | `{id}` -> OK | delete menu and dependent mappings per repo | FE | +| `POST /menu/getBaseMenuById` | `Menu.Find -> Find` | `{id}` -> `{menu}` | read | FE | +| `POST /menu/addMenuAuthority` | `Menu.SetAuthorityMenus -> SetAuthorityMenus` | authority + menu ids -> OK | replace authority-menu mapping | FE | +| `POST /menu/getMenuAuthority` | `Menu.AuthorityMenus -> AuthorityMenus` | `{authorityId}` -> `{menus}` | read mapping | FE | +| `GET /menu/getMenuRoles` | `Menu.RoleIDs + DefaultRouterRoleIDs` | `menuId` -> `{authorityIds,defaultRouterAuthorityIds}` | read role mapping | FE | +| `POST /menu/setMenuRoles` | `Menu.SetRoles -> SetRoles` | menu + authority ids -> OK | replace menu-role mapping | FE | +| `POST /authorityBtn/getAuthorityBtn` | `Permission.Buttons -> SelectedButtons` | authority/menu ids -> selection | read button permission selection | FE | +| `POST /authorityBtn/setAuthorityBtn` | `Permission.SetButtons -> SetSelectedButtons` | authority/menu/selected -> OK | replace button permission selection | FE | +| `POST /authorityBtn/canRemoveAuthorityBtn` | `Permission.CanRemoveButton -> CanRemoveButton` | menu button id -> boolean | dependency/usage check | FE | + +## 5. Departments and positions + +Department pipeline: `Organization handler -> DepartmentService -> DepartmentUsecase -> +DepartmentRepo -> data.departmentRepo`. Position rows substitute the matching +`Position*` components and `data.positionRepo`. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /department/getDepartmentList` | `Organization.ListDepartments -> Departments` | filter body -> department tree/list | read | FE | +| `POST /department/createDepartment` | `CreateDepartment -> DepartmentService.Create` | `DepartmentRequest` -> OK | create department | FE | +| `PUT /department/updateDepartment` | `UpdateDepartment -> Update` | `DepartmentRequest` -> OK | update department | FE | +| `DELETE /department/deleteDepartment` | `DeleteDepartment -> Delete` | `{id}` -> OK | delete department | FE | +| `GET /department/findDepartment` | `FindDepartment -> Department` | `id` -> department | read | FE | +| `GET /department/getDepartmentUsers` | `DepartmentUsers -> UserIDs` | `departmentId` -> `userIds[]` | read membership | FE | +| `POST /department/setDepartmentUsers` | `SetDepartmentUsers -> SetUsers` | department/user ids -> OK | replace department membership | FE | +| `POST /user/setUserDepartments` | `SetUserDepartments -> DepartmentService.SetUserDepartments` | user + department ids -> OK | replace user's departments | FE | +| `POST /position/getPositionList` | `ListPositions -> Positions` | filter body -> position list | read | FE | +| `POST /position/createPosition` | `CreatePosition -> PositionService.Create` | `PositionRequest` -> OK | create position | FE | +| `PUT /position/updatePosition` | `UpdatePosition -> Update` | `PositionRequest` -> OK | update position | FE | +| `DELETE /position/deletePosition` | `DeletePosition -> Delete` | `{id}` -> OK | delete position | FE | +| `GET /position/findPosition` | `FindPosition -> Position` | `id` -> position | read | FE | +| `GET /position/getPositionUsers` | `PositionUsers -> UserIDs` | `positionId` -> `userIds[]` | read membership | FE | +| `POST /position/setPositionUsers` | `SetPositionUsers -> SetUsers` | position/user ids -> OK | replace position membership | FE | +| `POST /user/setUserPositions` | `SetUserPositions -> PositionService.SetUserPositions` | user + position ids -> OK | replace user's positions | FE | + +## 6. Dictionaries + +Pipeline: `Dictionary handler -> DictionaryService -> DictionaryUsecase -> DictionaryRepo -> data.dictionaryRepo`. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /sysDictionary/createSysDictionary` | `Create -> CreateDictionaryRequest` | dictionary DTO -> `{dictionary}` | create dictionary | FE | +| `PUT /sysDictionary/updateSysDictionary` | `Update -> UpdateDictionaryRequest` | dictionary DTO -> OK | update dictionary | FE | +| `DELETE /sysDictionary/deleteSysDictionary` | `Delete -> DeleteDictionary` | `id` -> OK | delete dictionary | FE | +| `GET /sysDictionary/findSysDictionary` | `Find -> Dictionary` | id/type/status -> `{sysDictionary}` | read dictionary | FE | +| `GET /sysDictionary/getSysDictionaryList` | `List(false) -> Dictionaries` | page/name/type -> `Page` | read without details | FE | +| `GET /sysDictionary/getSysDictionaryListWithDetails` | `List(true) -> Dictionaries` | page/name/type -> `Page` | read with details | -- | +| `GET /sysDictionary/exportSysDictionary` | `Export -> ExportDictionary` | `id` -> dictionary export payload | read/export JSON structure | FE | +| `POST /sysDictionary/importSysDictionary` | `Import -> ImportDictionaryJSON` | JSON document -> OK | validate and import dictionary | FE | +| `POST /sysDictionaryDetail/createSysDictionaryDetail` | `CreateDetail -> CreateDictionaryDetailRequest` | detail DTO -> OK | create detail | FE | +| `PUT /sysDictionaryDetail/updateSysDictionaryDetail` | `UpdateDetail -> UpdateDictionaryDetailRequest` | detail DTO -> OK | update detail | FE | +| `DELETE /sysDictionaryDetail/deleteSysDictionaryDetail` | `DeleteDetail -> DeleteDictionaryDetail` | `id` -> OK | delete detail | FE | +| `GET /sysDictionaryDetail/findSysDictionaryDetail` | `FindDetail -> DictionaryDetail` | `id` -> `{sysDictionaryDetail}` | read detail | FE | +| `GET /sysDictionaryDetail/getSysDictionaryDetailList` | `Details -> DictionaryDetails` | page/filter -> `Page` | read | FE | +| `GET /sysDictionaryDetail/getDictionaryTreeList` | `Tree(false) -> DictionaryTree` | `id` -> tree | read tree by id | FE | +| `GET /sysDictionaryDetail/getDictionaryTreeListByType` | `Tree(true) -> DictionaryTree` | `type` -> tree | read tree by type | FE | +| `GET /sysDictionaryDetail/getDictionaryDetailsByParent` | `DetailsByParent -> DictionaryDetailsByParent` | dictionary/parent/includeChildren -> details | read descendants | FE | +| `GET /sysDictionaryDetail/getDictionaryPath` | `Path -> DictionaryPath` | `id` -> ancestor path | read ancestry | FE | + +## 7. Parameters, API tokens, security, and system configuration + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /sysParams/createSysParams` | `Parameter.Create -> CreateParameterRequest -> ParameterUsecase -> ParameterRepo` | parameter DTO -> OK | create parameter | FE | +| `PUT /sysParams/updateSysParams` | `Parameter.Update -> UpdateParameterRequest -> ParameterUsecase -> ParameterRepo` | parameter DTO -> OK | update parameter | FE | +| `DELETE /sysParams/deleteSysParams` | `Parameter.Delete -> DeleteParameters -> ParameterUsecase -> ParameterRepo` | `ID` query -> OK | delete parameter | FE | +| `DELETE /sysParams/deleteSysParamsByIds` | `Parameter.DeleteMany -> DeleteParameters -> ParameterUsecase -> ParameterRepo` | `IDs[]` -> OK | batch delete | FE | +| `GET /sysParams/findSysParams` | `Parameter.Find -> ParameterByID -> ParameterUsecase -> ParameterRepo` | `ID` -> parameter | read | FE | +| `GET /sysParams/getSysParam` | `Parameter.Get -> ParameterByKey -> ParameterUsecase -> ParameterRepo` | `key` -> parameter | read by key | FE | +| `GET /sysParams/getSysParamsList` | `Parameter.List -> ParametersFilter -> ParameterUsecase -> ParameterRepo` | page/name/key/date -> `Page` | read | FE | +| `POST /sysApiToken/createApiToken` | `APIToken.Create -> TokenService.CreateAPIToken -> TokenUsecase -> APITokenRepo` | user/authority/days/remark -> token | issue persisted API token | FE | +| `POST /sysApiToken/getApiTokenList` | `APIToken.List -> APITokens -> TokenUsecase -> APITokenRepo` | page/user/status -> `Page` | read | FE | +| `POST /sysApiToken/deleteApiToken` | `APIToken.Delete -> DisableAPIToken -> TokenUsecase -> APITokenRepo` | `id` -> OK | disable token | FE | +| `GET /securityConfig/getSecurityConfig` | `SystemConfig.GetSecurity -> SecurityService.Security -> SecurityUsecase -> SecurityRepo` | none -> security config | read | FE | +| `POST /securityConfig/setSecurityConfig` | `SystemConfig.SetSecurity -> SaveSecurityRequest -> SecurityUsecase -> SecurityRepo` | security DTO -> config | update security/rate/password policies | FE | +| `POST /system/getSystemConfig` | `SystemConfig.Get -> SystemConfigService.Get -> SystemConfigUsecase -> InitializationRepo` | none -> config document | read runtime config | FE | +| `POST /system/setSystemConfig` | `SystemConfig.Set -> Set -> SystemConfigUsecase -> InitializationRepo` | config document -> OK | persist runtime config | FE | +| `POST /system/reloadSystem` | `SystemConfig.Reload -> Reload -> SystemConfigUsecase + TaskRuntime` | none -> OK | reload runtime config/task schedule | FE | +| `POST /system/getServerInfo` | `SystemConfig.ServerInfo -> SystemInfo` | none -> host/process metrics | read system state | FE | + +## 8. Operation, login, data-access, file-log, and error audit + +Audit pipeline: `Audit handler -> AuditService -> AuditUsecase -> AuditQueryRepo -> data.auditRepo`. +Log viewer uses `LogViewerService -> LogViewerUsecase -> LogFileRepo -> data.logFileRepo`. +Error-record rows use the audit repository's error-record implementation. The +public error creation route is intentionally callable by the frontend error handler. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `GET /sysOperationRecord/getSysOperationRecordList` | `Audit.Operations -> OperationsFilter` | page/path/method/status -> `Page` | read | FE | +| `GET /sysOperationRecord/findSysOperationRecord` | `Audit.Operation -> Operation` | `id` -> operation | read | -- | +| `DELETE /sysOperationRecord/deleteSysOperationRecord` | `Audit.DeleteOperation -> DeleteOperations` | `id` -> OK | delete record | FE | +| `DELETE /sysOperationRecord/deleteSysOperationRecordByIds` | `Audit.DeleteOperations -> DeleteOperations` | `ids[]` -> OK | batch delete | FE | +| `GET /sysLoginLog/getLoginLogList` | `Audit.Logins -> LoginsFilter` | page/username/status -> `Page` | read | FE | +| `GET /sysLoginLog/findLoginLog` | `Audit.Login -> Login` | `id` -> login log | read | FE | +| `DELETE /sysLoginLog/deleteLoginLog` | `Audit.DeleteLogin -> DeleteLogins` | `id` -> OK | delete login log | FE | +| `DELETE /sysLoginLog/deleteLoginLogByIds` | `Audit.DeleteLogins -> DeleteLogins` | `ids[]` -> OK | batch delete | FE | +| `POST /dataAccessLog/getDataAccessLogList` | `Audit.DataAccess -> DataAccessRequest` | filter/page -> `Page` | read | FE | +| `DELETE /dataAccessLog/deleteDataAccessLogByIds` | `Audit.DeleteDataAccess -> DeleteDataAccess` | `ids[]` -> OK | batch delete | FE | +| `GET /logViewer/dates` | `Audit.LogDates -> LogViewerService.LogDates` | `month` -> dates | read filesystem log index | FE | +| `GET /logViewer/files` | `Audit.LogFiles -> LogViewerService.LogFiles` | `date` -> files | read filesystem log index | FE | +| `GET /logViewer/content` | `Audit.LogContent -> LogViewerService.LogContent` | date/path/cursor -> chunk | read log content, cursor pagination | FE | +| `POST /sysError/createSysError` | `Audit.CreateError -> AuditRecorder.RecordError` | frontend error payload -> OK | **public** append error record | FE | +| `GET /sysError/getSysErrorList` | `Audit.Errors -> ErrorsFilter` | page/form/info/date -> `Page` | read | FE | +| `GET /sysError/findSysError` | `Audit.Error -> Error` | `id` -> error record | read | FE | +| `PUT /sysError/updateSysError` | `Audit.UpdateError -> UpdateErrorRequest` | error mutation -> OK | update error record | FE | +| `DELETE /sysError/deleteSysError` | `Audit.DeleteError -> DeleteErrors` | `id` -> OK | delete error record | FE | +| `DELETE /sysError/deleteSysErrorByIds` | `Audit.DeleteErrors -> DeleteErrors` | `ids[]` -> OK | batch delete | FE | + +## 9. Export templates and versions + +Export pipeline: `Export handler -> ExportService -> ExportUsecase -> ExportRepo -> data.exportRepo`, +with short-lived download tokens in the cache. Version pipeline: `Version handler +-> VersionService -> VersionUsecase -> VersionRepo -> data.versionRepo`; export and +import also read/write menus, APIs, and dictionaries through `VersionUsecase`. + +| Method/path | Call | Key input -> output | Primary effect | FE | +| --- | --- | --- | --- | --- | +| `POST /sysExportTemplate/createSysExportTemplate` | `Export.Create -> CreateRequest` | template DTO -> OK | create template | FE | +| `PUT /sysExportTemplate/updateSysExportTemplate` | `Export.Update -> UpdateRequest` | template DTO -> OK | update template | FE | +| `DELETE /sysExportTemplate/deleteSysExportTemplate` | `Export.Delete -> Delete` | `id` -> OK | delete template | FE | +| `DELETE /sysExportTemplate/deleteSysExportTemplateByIds` | `Export.DeleteMany -> Delete` | `ids[]` -> OK | batch delete | FE | +| `GET /sysExportTemplate/findSysExportTemplate` | `Export.Find -> Template` | id/templateID -> template | read | FE | +| `GET /sysExportTemplate/getSysExportTemplateList` | `Export.List -> TemplatesFilter` | page/name/table/template/date -> `Page