# 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/.md` 并清空活动 plan)。 ## Next Actions (D2+) 1. (已完成)执行计划门禁与联调验收项(T13/T14/T16/T17),见 Evidence K。 2. (已完成)进行前端页面流验收(creator audit 查询/筛选/分页)并补充录屏或截图证据(见 Evidence J)。 3. (可选增强)将 superadmin demo 路由按构建开关完全剔除,而非仅运行时拦截。