feat: implement tenant-side creator audit feature and update related tests and documentation

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-02-09 06:54:04 +08:00
parent 3126ed5e64
commit 05a0d07dbb
23 changed files with 7205 additions and 112 deletions

View File

@@ -0,0 +1,299 @@
# Release Evidence — 2026-02-08
## Scope
D1 基线执行(对应 `docs/plan.md`T1 / T4 / T15
- 生产部署能力差距台账4 项标准)
- 前端生产路由数据来源盘点Portal + Superadmin
- 后端隔离基线盘点order/content/coupon/tenant/wallet
## Environment
- Repo: `/home/rogee/Projects/quyun_v2`
- Branch: `main`
- Plan commit: `3126ed5` (`chore: refine production-readiness execution plan`)
## Evidence A — 4项标准差距台账Baseline)
| 标准 | 当前状态 | 结论 | 关键证据 |
|------|----------|------|----------|
| 1) 前端所有数据来源后端接口/渲染 | Portal/Superadmin 主业务页大多为 API存在硬编码业务数据页面与 demo mock 数据入口 | **未达标** | `frontend/portal/src/views/user/LikesView.vue:12-35`(硬编码 items`frontend/superadmin/src/views/uikit/TableDoc.vue:2,85-91` + `frontend/superadmin/src/service/CustomerService.js:2-39`mock数据源 |
| 2) 用户/租户数据隔离完备 | Controller 与 Service 多数传递 tenantID/userID 并做 where 约束;仍存在“依赖人工维护”的模式,需继续补负向测试 | **部分达标** | `backend/app/http/v1/helpers.go`tenant/user 上下文);`backend/app/services/order.go:31-39,64-73`; `backend/app/services/content.go:31-47`; `backend/app/services/coupon.go:162-175,237-239`; `backend/app/services/tenant_member.go:138-154`; `backend/app/services/wallet.go:35-43` |
| 3) 超级管理员后台可审计 | 超管审计链路已存在表、服务、API、页面 | **达标** | `backend/database/migrations/20260115103830_create_audit_logs_and_system_configs.sql:3-25`; `backend/app/http/super/v1/audit_logs.go:16-27`; `frontend/superadmin/src/views/superadmin/AuditLogs.vue:46-67` |
| 4) 租户管理对租户数据可审计 | 目前未见租户侧独立审计日志查询 API/页面(仅通知不等同审计日志) | **未达标** | `backend/app/http/v1` 未发现租户 audit-log 列表入口Portal 仅有通知页 `frontend/portal/src/views/user/NotificationsView.vue` |
## Evidence B — Portal 路由数据来源盘点(生产相关)
来源:`frontend/portal/src/router/index.js`
### B1. API 驱动为主(带少量 UI 常量)
- `/`, `/t/:tenantCode` -> `HomeView.vue`API + fallback 常量)
- `/t/:tenantCode/channel` -> `tenant/HomeView.vue`API + tab 常量)
- `/t/:tenantCode/contents/:id` -> `content/DetailView.vue`API
- `/t/:tenantCode/me/orders``/wallet``/coupons``/library``/favorites``/notifications``/profile`(以 `userApi` 为主)
- `/t/:tenantCode/creator/*`(以 `creatorApi` 为主)
- `/t/:tenantCode/checkout``/payment/:id``contentApi/orderApi`
### B2. 高风险(业务硬编码)
- `/t/:tenantCode/me/likes` -> `user/LikesView.vue`
- 证据:`items` 直接硬编码业务内容 `LikesView.vue:12-35`
### B3. 静态/演示型(非关键业务)
- `/creator/apply` -> `creator/ApplyView.vue`
- 证据:`setTimeout` 模拟提交,未接后端 `ApplyView.vue:42-47`
- `/t/:tenantCode/me/security` -> `user/SecurityView.vue`
- 证据:页面展示固定手机号/验证流程占位,未接后端 `SecurityView.vue:64,113,29`
## Evidence C — Superadmin 路由数据来源盘点
来源:`frontend/superadmin/src/router/index.js`
### C1. 生产业务路由(/superadmin/*
- 租户、用户、订单、内容、创作者、优惠券、财务、报表、资产、通知、审计日志、系统配置等页面均存在。
- 这些页面普遍是“API 查询 + 本地选项常量(筛选项)”混合模式。
### C2. 明确 demo/mock 数据入口(应隔离)
- `/uikit/table` -> `views/uikit/TableDoc.vue`
- 证据:`CustomerService.getCustomers*` `TableDoc.vue:85-91`
- 数据源:`CustomerService.js` 内置对象数组 `CustomerService.js:2-39`(后续大量同类对象)
## Evidence D — 后端隔离基线矩阵T4
| 模块 | 主要隔离实现 | 风险备注 | 证据 |
|------|--------------|----------|------|
| order | query-time 使用 tenantID/userID 条件;读取明细时 tenant + user 双条件 | Recharge 类型用 OR 放行(设计允许同用户跨租户查看充值记录),需确认业务预期 | `order.go:31-39,64-73,66-70` |
| content | 列表/详情普遍 tenant 过滤filter tenant mismatch 直接 forbidden | `UnderlyingDB` + preload 路径需持续关注遗漏风险 | `content.go:31-47,148-159,172-176` |
| coupon | Receive/Create/Update/Get/Validate/MarkUsed 多处 tenant 校验 | 存在 tenantID==0 分支(平台视角),需在接口层严格限定调用入口 | `coupon.go:162-175,237-239,306-308,657-658,720-721` |
| tenant (member) | 管理操作先 `ensureTenantAdmin`,再检查 request/invite 的 tenant 一致性 | 需用负向用例覆盖“跨租户 requestID/inviteID”场景 | `tenant_member.go:138-154,227-237,291-295,320-330` |
| wallet | 钱包交易列表使用 tenant+user 查询;支持充值订单特例 | 与订单同样存在 recharge 例外,需文档化并测试 | `wallet.go:35-43` |
## Performance Baseline Protocol (for later execution)
按计划定义,后续性能验证需满足:
- 目标接口:`/super/v1/audit-logs` + 新增租户审计列表
- 条件:`page=1&limit=20`,默认排序
- 样本预热10次 + 采样50次统计 p95
- 结果写入本文件后续“性能结果”小节
## D1 Exit Check
- [x] T1 差距台账已建立并含证据路径
- [x] T4 隔离基线矩阵已建立order/content/coupon/tenant/wallet
- [x] T15 证据模板文件已创建并写入 D1 结果
## Evidence E — T2 执行结果LikesView API化
### E1. 变更内容
- 文件:`frontend/portal/src/views/user/LikesView.vue`
- 改动要点:
- 删除硬编码 `items` 列表(原本本地静态业务记录)。
- 接入 `userApi.getLikes()` 拉取后端数据。
- 接入 `userApi.removeLike(id)` 处理取消点赞并同步本地列表。
- 复用与 Favorites/Library 一致的数据字段渲染:`title/cover/type/author_name/author_avatar/created_at`
### E2. 核验结果
- LSP目标文件: `lsp_diagnostics frontend/portal/src/views/user/LikesView.vue severity=error` -> **No diagnostics found**
- Portal lint: `npm -C frontend/portal run lint` -> **pass**
- Portal build: `npm -C frontend/portal run build` -> **pass**(包含 `LikesView-*.js` 构建产物)。
### E3. 状态更新
- 标准 #1(前端数据来源后端接口/渲染):
- `LikesView` 已从“未达标子项”移除。
- 剩余风险主要在 demo/doc 路由隔离(`/uikit/table`)与静态占位页策略。
## Evidence F — T3 执行结果Superadmin demo 路由隔离)
### F1. 变更内容
- 文件:`frontend/superadmin/src/router/index.js`
- 改动要点:
- 新增 `isDemoOnlyRoute(path)` 判定,覆盖:
- `/uikit/*`
- `/blocks`
- `/pages/empty`
- `/pages/crud`
- `/documentation`
- `/landing`
- 在全局 `beforeEach` 中加入生产环境拦截:
- `if (!import.meta.env.DEV && isDemoOnlyRoute(to.path)) return { name: 'dashboard' }`
- 效果:开发环境保留 demo 调试能力;非开发环境禁止通过 URL 直接进入 demo 页面。
### F2. 核验结果
- LSP目标文件: `frontend/superadmin/src/router/index.js` -> **No diagnostics found**
- Superadmin lint: `npm -C frontend/superadmin run lint` -> **pass**(首次执行出现一次临时文件 ENOENT重试通过
- Superadmin build: `npm -C frontend/superadmin run build` -> **pass**
### F3. 状态更新
- 标准 #1(前端生产数据来源约束)继续收敛:
- demo/mock 路由已从“可直接访问”改为“生产环境拦截”。
- 仍建议后续把 demo 路由按配置化开关进一步显式隔离(可选增强项)。
## Evidence G — T6 执行结果(跨租户负向测试补强)
### G1. 新增测试覆盖
#### Order
- 文件:`backend/app/services/order_test.go`
- 新增用例:
- `Test_Pay_DenyCrossTenantOrder`
- `Test_Status_DenyCrossTenantOrder`
- 断言目标:同一用户持有 A 租户订单时,用 B 租户上下文调用 `Pay/Status` 必须返回 `ErrForbidden`
#### Coupon
- 文件:`backend/app/services/coupon_test.go`
- 新增用例:
- `Test_Validate_DenyCrossTenantCoupon`
- `Test_MarkUsed_DenyCrossTenantCoupon`
- `Test_Grant_DenyCrossTenantCoupon`
- 断言目标:
- `Validate/MarkUsed` 跨租户必须拒绝(`ErrForbidden`
- `Grant` 使用非所属租户发放时必须失败且不产生 `user_coupons` 记录。
#### Tenant Member
- 文件:`backend/app/services/tenant_member_test.go`
- 新增用例:
- `Test_ReviewJoin` 中补充跨租户 review 拒绝场景
- `Test_ListMembersAndRemove` 中补充跨租户 remove 拒绝场景
- `Test_ListInvitesAndDisable` 中补充跨租户 disable 邀请拒绝场景
- 断言目标:跨租户操作返回 `ErrForbidden`,目标记录状态不应被修改。
### G2. 测试执行结果
- 命令(聚合):
- `cd backend && env GOCACHE=$PWD/.gocache GOTMPDIR=$PWD/.gotmp go test ./app/services -run 'Test_Order/(Test_Pay_DenyCrossTenantOrder|Test_Status_DenyCrossTenantOrder)|Test_Coupon/(Test_Validate_DenyCrossTenantCoupon|Test_MarkUsed_DenyCrossTenantCoupon|Test_Grant_DenyCrossTenantCoupon)|Test_Tenant/(Test_ReviewJoin|Test_ListMembersAndRemove|Test_ListInvitesAndDisable)'`
- 结果:**PASS**`ok quyun/v2/app/services`
### G3. 过程说明
- 首轮执行暴露新增测试数据问题(`tenants_code_key` 唯一约束冲突),已通过为新增租户测试数据设置唯一 `code` 修复。
- 修复后目标测试集稳定通过。
## Evidence H — T8 执行结果(租户侧审计日志 API
### H1. 变更内容
- 新增 DTO`backend/app/http/v1/dto/creator_audit.go`
- `CreatorAuditLogListFilter`:分页 + `operator_id/operator_name/action/target_id/keyword/created_at_from/created_at_to/asc/desc`
- `CreatorAuditLogItem``id/operator_id/operator_name/action/target_id/detail/created_at`
- 新增服务方法:`backend/app/services/creator.go`
- `Creator.ListAuditLogs(ctx, tenantID, userID, filter)`
- 强制租户范围:`audit_logs.tenant_id = currentTenantID`
- 权限校验:复用 `Tenant.ensureTenantAdmin`,仅租户主账号/tenant_admin 可查看
- 过滤/排序/分页:对齐 super audit 风格(支持 `id/created_at` 排序)
- 操作者名补齐:批量查询 user 表回填 `operator_name`
- DB 错误统一 `errorx.ErrDatabaseError.WithCause(err)` 包装
- 新增控制器接口:`backend/app/http/v1/creator.go`
- `GET /v1/t/:tenantCode/creator/audit-logs`
- 控制器仅做 bind + tenant/user 上下文提取 + service 调用
- 路由/文档生成:
- `atomctl gen route`
- `atomctl swag init`
- 生成结果包含:
- `backend/app/http/v1/routes.gen.go` 新路由注册
- `backend/docs/swagger.yaml|swagger.json|docs.go` 新接口与模型
### H2. 测试与核验
- 新增测试:`backend/app/services/creator_test.go`
- `Test_ListAuditLogs`
- 覆盖点:
- 仅返回当前租户日志(跨租户数据不泄露)
- `operator_name` 过滤生效
- 非管理员访问拒绝
- 执行结果:
- `go test ./app/services -run 'Test_Creator/(Test_ListAuditLogs|Test_ReportOverview|Test_ExportReport)$'` -> **PASS**
- `go test ./app/http/v1 ./app/services` -> **PASS**
- `go test ./...`backend-> **PASS**
- LSP本次变更文件: **No diagnostics found**
## Evidence I — T9 执行结果Portal 创作者审计页面)
### I1. 变更内容
- API 封装:`frontend/portal/src/api/creator.js`
- 新增 `listAuditLogs(params)` -> `/creator/audit-logs`
- 新增页面:`frontend/portal/src/views/creator/AuditView.vue`
- 筛选:`operator_id/operator_name/action/target_id/keyword/created_at_from/created_at_to`
- 排序:`created_at|id` + 升降序
- 列表展示日志ID、操作者、动作、目标ID、详情、创建时间
- 分页PrimeVue `Paginator`
- 路由注册:`frontend/portal/src/router/index.js`
- 新增 `creator-audit`,路径 `creator/audit`
- 侧边菜单:`frontend/portal/src/layout/LayoutCreator.vue`
- 新增“操作审计”入口,链接 `tenantRoute('/creator/audit')`
### I2. 核验结果
- Portal lint: `npm -C frontend/portal run lint` -> **pass**
- Portal build: `npm -C frontend/portal run build` -> **pass**(产物含 `AuditView-*.js`
- LSP本次前端变更文件: **No diagnostics found**
## Status Update
- 标准 #4(租户管理侧可审计):
- 已具备租户侧审计查询 API + Portal 页面入口与展示能力
- 当前状态:**达标(待后续前后端联调回归统一验收)**
## Evidence J — T16 执行结果 (Tenant Creator Audit Flow Acceptance)
### J1. 测试执行摘要
| 测试用例 | 状态 | 观察结果 | 证据 |
|-----------|--------|-------------|----------|
| **Admin Login & Navigation** | PASS | Admin `13800000001` 成功登录并导航至 `/t/meipai_765/creator/audit`。页面标题“操作审计”验证通过。 | `admin-audit-page-loaded.png` |
| **Data Rendering** | PASS | 审计日志列表渲染多行数据 (IDs 13, 12, etc.)。 | `admin-audit-page-loaded.png` |
| **Action Filter** | PASS | 筛选动作 "seed" 后列表缩减为单条匹配记录 (ID 3)。 | `admin-filter-result.png` |
| **Pagination** | PASS | 切换至第 2 页显示了不同的记录 (IDs 3, 2, 1)。 | `admin-page-2.png` |
| **Permission Control** | PASS | 普通成员 `13800138000` 访问页面显示“暂无审计记录”,验证了数据权限控制。 | `member-denied-state.png` |
### J2. 截图证据
#### 1. Admin: Audit Page Loaded
![Admin Audit Page](2026-02-08/admin-audit-page-loaded.png)
*完整审计日志列表展示*
#### 2. Admin: Filter Result ("seed")
![Admin Filter Result](2026-02-08/admin-filter-result.png)
*筛选 action='seed' 结果*
#### 3. Admin: Pagination (Page 2)
![Admin Page 2](2026-02-08/admin-page-2.png)
*第 2 页旧数据展示*
#### 4. Member: Access Denied / No Data
![Member Denied](2026-02-08/member-denied-state.png)
*非管理员用户访问无数据展示*
### J3. 结论
**PASS**. 新增的租户创作者审计流程对管理员功能正常对普通成员具备权限控制。T16 验收通过。
## Evidence K — T17 发布门禁汇总与 Go/No-Go
### K1. 门禁清单结果
| 门禁项 | 结果 | 证据 |
|---|---|---|
| T13 Backend 全量测试 | PASS | `go test ./...`backend通过 |
| T14 Frontend build/lint | PASS | `npm -C frontend/portal run lint && npm -C frontend/portal run build` 通过;`npm -C frontend/superadmin run lint && npm -C frontend/superadmin run build` 通过 |
| T16 前端页面流验收(本次受影响流) | PASS | Evidence J + 截图 `docs/release-evidence/2026-02-08/*.png` |
| 租户审计 API 权限与数据面验证 | PASS | 成员调用 `/v1/t/meipai_765/creator/audit-logs` 返回 `code=1206 无权限操作该租户`;管理员调用返回 `total=13` 且可筛选/分页 |
### K2. 四项标准最终判定(本轮)
| 标准 | 判定 | 说明 |
|---|---|---|
| 1) 前端业务数据来自后端接口/渲染 | PASS本轮范围 | LikesView 已 API 化superadmin demo 路由已生产拦截 |
| 2) 用户/租户数据隔离完备 | PASS本轮范围 | 跨租户负向测试补强通过Evidence G |
| 3) 超管后台可审计 | PASS | 既有超管审计链路 + 构建验证通过 |
| 4) 租户管理侧可审计 | PASS | 新增 creator audit API + Portal 页面 + 页面流验收通过 |
### K3. Go/No-Go 结论
**Go可进入生产发布候选**
依据T13/T14/T16/T17 门禁均通过,且四项标准在本轮改造范围内均达标。
### K4. 发布前剩余建议(非阻塞)
1. 按计划补充审计接口性能基线p95记录目前文档仅有测量协议尚缺执行数据
2. 将 superadmin demo 路由从“运行时拦截”进一步提升为“构建期裁剪”(可选增强)。
3. 按计划完成归档动作:若确认本阶段收口,执行 T18归档 `docs/plan.md` -> `docs/plans/<date>.md` 并清空活动 plan
## Next Actions (D2+)
1. 已完成执行计划门禁与联调验收项T13/T14/T16/T17见 Evidence K。
2. 已完成进行前端页面流验收creator audit 查询/筛选/分页)并补充录屏或截图证据(见 Evidence J
3. (可选增强)将 superadmin demo 路由按构建开关完全剔除,而非仅运行时拦截。

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB