From f53a2dbc879d9e23efa9d73e32324912f20714cc Mon Sep 17 00:00:00 2001 From: rain <982766701@qq.com> Date: Thu, 17 Sep 2026 16:35:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-pipeline/package.json | 3 +- .../tests/runtime-asset-inventory.test.mjs | 142 -------- .../tests/runtime-asset-registry.test.mjs | 96 ------ docs/backend-integration-report-2026-08-22.md | 168 ---------- docs/backend-integration-tasks-2026-08-17.md | 142 -------- .../backend-reference-gap-tasks-2026-08-23.md | 145 -------- docs/click-comparison-audit-2026-08-23.md | 153 --------- .../fifth-pass-click-regression-2026-08-23.md | 54 --- ...security-and-operation-audit-2026-08-23.md | 154 --------- docs/frontend-reference-parity-2026-08-17.md | 309 ------------------ docs/release-blockers.md | 148 --------- ...second-pass-comparison-audit-2026-08-23.md | 110 ------- ...rd-pass-operation-flow-audit-2026-08-23.md | 138 -------- docs/后端待处理事项与验收标准.md | 244 -------------- docs/后端线上联调故障与数据准备清单.md | 239 -------------- docs/项目交接说明.md | 52 +++ package.json | 3 +- 17 files changed, 54 insertions(+), 2246 deletions(-) delete mode 100644 design-pipeline/tests/runtime-asset-inventory.test.mjs delete mode 100644 design-pipeline/tests/runtime-asset-registry.test.mjs delete mode 100644 docs/backend-integration-report-2026-08-22.md delete mode 100644 docs/backend-integration-tasks-2026-08-17.md delete mode 100644 docs/backend-reference-gap-tasks-2026-08-23.md delete mode 100644 docs/click-comparison-audit-2026-08-23.md delete mode 100644 docs/fifth-pass-click-regression-2026-08-23.md delete mode 100644 docs/fourth-pass-security-and-operation-audit-2026-08-23.md delete mode 100644 docs/frontend-reference-parity-2026-08-17.md delete mode 100644 docs/release-blockers.md delete mode 100644 docs/second-pass-comparison-audit-2026-08-23.md delete mode 100644 docs/third-pass-operation-flow-audit-2026-08-23.md delete mode 100644 docs/后端待处理事项与验收标准.md delete mode 100644 docs/后端线上联调故障与数据准备清单.md create mode 100644 docs/项目交接说明.md diff --git a/design-pipeline/package.json b/design-pipeline/package.json index 357596d..2286a24 100644 --- a/design-pipeline/package.json +++ b/design-pipeline/package.json @@ -3,8 +3,7 @@ "private": true, "type": "module", "scripts": { - "test": "node --test tests/*.test.mjs", "validate:runtime-assets": "node scripts/validate-runtime-asset-inventory.mjs design-pipeline/manifests/runtime-assets.json", - "check": "npm test && npm run validate:runtime-assets" + "check": "npm run validate:runtime-assets" } } diff --git a/design-pipeline/tests/runtime-asset-inventory.test.mjs b/design-pipeline/tests/runtime-asset-inventory.test.mjs deleted file mode 100644 index f87e33b..0000000 --- a/design-pipeline/tests/runtime-asset-inventory.test.mjs +++ /dev/null @@ -1,142 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' -import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises' -import os from 'node:os' -import path from 'node:path' - -import { expandRuntimeAssetInventory } from '../scripts/runtime-asset-inventory.mjs' - -const createWorkspace = async () => { - const workspace = await mkdtemp(path.join(os.tmpdir(), 'jiapu-runtime-assets-')) - await mkdir(path.join(workspace, 'design-pipeline', 'manifests'), { recursive: true }) - return workspace -} - -const writeManifest = async (workspace, name, value) => { - const filePath = path.join(workspace, 'design-pipeline', 'manifests', name) - await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8') - return filePath -} - -const directAsset = (id, output) => ({ - id, - output, - width: 96, - height: 96, - alpha: true, - bytes: 100, - sha256: 'a'.repeat(64), - provenance: 'committed-binary', - rebuildable: false, -}) - -const runtimeManifest = (imports = [], assets = []) => ({ - schemaVersion: 1, - kind: 'runtime-asset-inventory', - scope: 'auth', - imports, - assets, -}) - -test('展开直接资产与单一导入且不复制物理规格', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - const imported = await writeManifest( - workspace, - 'shared.json', - runtimeManifest([], [directAsset('shared', 'static/assets/shared.png')]), - ) - const root = await writeManifest( - workspace, - 'auth.json', - runtimeManifest(['design-pipeline/manifests/shared.json'], [directAsset('auth', 'static/assets/auth.png')]), - ) - - const result = await expandRuntimeAssetInventory(root, workspace) - assert.deepEqual(result.assets.map(({ id }) => id).sort(), ['auth', 'shared']) - assert(result.manifests.includes(imported)) -}) - -test('拒绝缺失的导入文件', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - const root = await writeManifest(workspace, 'auth.json', runtimeManifest(['design-pipeline/manifests/missing.json'])) - await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /missing manifest/i) -}) - -test('拒绝循环导入', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - const first = await writeManifest(workspace, 'first.json', runtimeManifest(['design-pipeline/manifests/second.json'])) - await writeManifest(workspace, 'second.json', runtimeManifest(['design-pipeline/manifests/first.json'])) - await assert.rejects(() => expandRuntimeAssetInventory(first, workspace), /import cycle/i) -}) - -test('拒绝同一清单被重复导入', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - await writeManifest(workspace, 'shared.json', runtimeManifest([], [directAsset('shared', 'static/assets/shared.png')])) - const root = await writeManifest( - workspace, - 'auth.json', - runtimeManifest([ - 'design-pipeline/manifests/shared.json', - 'design-pipeline/manifests/shared.json', - ]), - ) - await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /duplicate import/i) -}) - -test('拒绝不同清单拥有同一正式输出', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - await writeManifest(workspace, 'shared.json', runtimeManifest([], [directAsset('shared', 'static/assets/same.png')])) - const root = await writeManifest( - workspace, - 'auth.json', - runtimeManifest( - ['design-pipeline/manifests/shared.json'], - [directAsset('auth', 'static/assets/same.png')], - ), - ) - await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /duplicate output/i) -}) - -test('拒绝展开图中的重复资产 id', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - await writeManifest(workspace, 'shared.json', runtimeManifest([], [directAsset('same-id', 'static/assets/shared.png')])) - const root = await writeManifest( - workspace, - 'auth.json', - runtimeManifest( - ['design-pipeline/manifests/shared.json'], - [directAsset('same-id', 'static/assets/auth.png')], - ), - ) - await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /duplicate asset id/i) -}) - -test('直接资产只接受 committed-binary 且拒绝未知字段', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - - const wrongProvenance = directAsset('wrong-provenance', 'static/assets/wrong.png') - wrongProvenance.provenance = 'manual-copy' - const wrongRoot = await writeManifest(workspace, 'wrong.json', runtimeManifest([], [wrongProvenance])) - await assert.rejects(() => expandRuntimeAssetInventory(wrongRoot, workspace), /committed-binary/i) - - const unknownField = directAsset('unknown-field', 'static/assets/unknown.png') - unknownField.runtimeSelector = '.page' - const unknownRoot = await writeManifest(workspace, 'unknown.json', runtimeManifest([], [unknownField])) - await assert.rejects(() => expandRuntimeAssetInventory(unknownRoot, workspace), /unknown field/i) -}) - -test('运行时清单本身拒绝未知字段', async (t) => { - const workspace = await createWorkspace() - t.after(() => rm(workspace, { recursive: true, force: true })) - const manifest = runtimeManifest() - manifest.consumerList = [] - const root = await writeManifest(workspace, 'auth.json', manifest) - await assert.rejects(() => expandRuntimeAssetInventory(root, workspace), /unknown field/i) -}) diff --git a/design-pipeline/tests/runtime-asset-registry.test.mjs b/design-pipeline/tests/runtime-asset-registry.test.mjs deleted file mode 100644 index 3f16996..0000000 --- a/design-pipeline/tests/runtime-asset-registry.test.mjs +++ /dev/null @@ -1,96 +0,0 @@ -import assert from 'node:assert/strict' -import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises' -import os from 'node:os' -import path from 'node:path' -import test from 'node:test' -import { fileURLToPath } from 'node:url' - -import { validateRuntimeAssetRegistry } from '../scripts/runtime-asset-inventory.mjs' - -const runtimeManifest = (scope, imports = []) => ({ - schemaVersion: 1, - kind: 'runtime-asset-inventory', - scope, - imports, - assets: [], -}) - -const testDirectory = path.dirname(fileURLToPath(import.meta.url)) -const pipelineDirectory = path.resolve(testDirectory, '..') -const workspaceDirectory = path.resolve(pipelineDirectory, '..') -const realManifestsDirectory = path.join(pipelineDirectory, 'manifests') - -test('顶层注册表拒绝未进入导入闭包的正式 owner', async (t) => { - const workspace = await mkdtemp(path.join(os.tmpdir(), 'jiapu-runtime-registry-')) - t.after(() => rm(workspace, { recursive: true, force: true })) - const manifestsDirectory = path.join(workspace, 'design-pipeline', 'manifests') - await mkdir(manifestsDirectory, { recursive: true }) - const write = (name, value) => writeFile(path.join(manifestsDirectory, name), `${JSON.stringify(value)}\n`, 'utf8') - - await write('runtime-assets.json', runtimeManifest('schema-v3')) - await write('orphan.json', runtimeManifest('orphan')) - - await assert.rejects( - () => validateRuntimeAssetRegistry( - path.join(manifestsDirectory, 'runtime-assets.json'), - workspace, - manifestsDirectory, - ), - /unregistered manifest/i, - ) -}) - -test('真实 schema v3 注册表覆盖应用资产与保留生成资产', async () => { - const registry = JSON.parse(await readFile(path.join(realManifestsDirectory, 'runtime-assets.json'), 'utf8')) - const auth = JSON.parse(await readFile(path.join(realManifestsDirectory, 'auth-runtime-assets.json'), 'utf8')) - const retained = JSON.parse(await readFile(path.join(realManifestsDirectory, 'retained-generated-runtime-assets.json'), 'utf8')) - - assert.equal(registry.scope, 'schema-v3') - assert.deepEqual(registry.imports, [ - 'design-pipeline/manifests/auth-runtime-assets.json', - 'design-pipeline/manifests/application-runtime-assets.json', - 'design-pipeline/manifests/retained-generated-runtime-assets.json', - ]) - assert.deepEqual(auth.imports, []) - assert.equal(retained.assets.length, 11) - assert(retained.assets.every((asset) => asset.provenance === 'committed-binary')) - assert(retained.assets.every((asset) => asset.rebuildable === false)) - await validateRuntimeAssetRegistry( - path.join(realManifestsDirectory, 'runtime-assets.json'), - workspaceDirectory, - realManifestsDirectory, - ) -}) - -test('注册表根 scope 不是 schema-v3 时拒绝验证', async (t) => { - const workspace = await mkdtemp(path.join(os.tmpdir(), 'jiapu-runtime-registry-scope-')) - t.after(() => rm(workspace, { recursive: true, force: true })) - const manifestsDirectory = path.join(workspace, 'design-pipeline', 'manifests') - await mkdir(manifestsDirectory, { recursive: true }) - const root = path.join(manifestsDirectory, 'runtime-assets.json') - await writeFile(root, `${JSON.stringify(runtimeManifest('auth'))}\n`, 'utf8') - - await assert.rejects( - () => validateRuntimeAssetRegistry(root, workspace, manifestsDirectory), - /root scope must be schema-v3/i, - ) -}) - -test('未声明的旧格式或未知 kind 清单不能被静默跳过', async (t) => { - const workspace = await mkdtemp(path.join(os.tmpdir(), 'jiapu-runtime-registry-unknown-')) - t.after(() => rm(workspace, { recursive: true, force: true })) - const manifestsDirectory = path.join(workspace, 'design-pipeline', 'manifests') - await mkdir(manifestsDirectory, { recursive: true }) - const root = path.join(manifestsDirectory, 'runtime-assets.json') - await writeFile(root, `${JSON.stringify(runtimeManifest('schema-v3'))}\n`, 'utf8') - await writeFile( - path.join(manifestsDirectory, 'unknown.json'), - `${JSON.stringify({ schemaVersion: 1, kind: 'legacy-owner', outputs: [] })}\n`, - 'utf8', - ) - - await assert.rejects( - () => validateRuntimeAssetRegistry(root, workspace, manifestsDirectory), - /undeclared legacy manifest/i, - ) -}) diff --git a/docs/backend-integration-report-2026-08-22.md b/docs/backend-integration-report-2026-08-22.md deleted file mode 100644 index 6f77ba3..0000000 --- a/docs/backend-integration-report-2026-08-22.md +++ /dev/null @@ -1,168 +0,0 @@ -# APP 前后端联调结果与后端处理单 - -更新时间:2026-08-22 - -收件人:后端开发、接口维护、测试与部署人员 - -## 结论 - -本轮已按后端最新源码 `C:\Users\Rain\Desktop\job\Genealogy`(核对提交 `16600afb57e79569907d673ce6742595a27dfecc`)重新接入前端,并在已登录的 MuMu 模拟器中完成真实点击验证。 - -微信登录/绑定、VIP 多支付契约、推荐关系、动态权限、内容密码找回、家谱永久删除、动态业务字典和族人敏感资料等能力在最新后端源码中已经存在。2026-08-17 文档中将这些能力标为“后端缺失”的描述已过期,不应继续据此重复开发。 - -当前已确认 1 个阻断正常功能的后端源码缺陷、2 个既有 OpenAPI 错误、5 项新增业务契约缺口,以及 1 项宣传视频投放数据待配置。此前联调发现的编译、并发取消、VIP capability 字段读取、家谱总览和宣传视频入口问题已处理;参考项目复核发现的订单展示等纯前端问题不列为后端任务。 - -## MuMu 点击验收表 - -| 用户路径 | 结果 | 实际表现 | 责任/下一步 | -| --- | --- | --- | --- | -| 我的 → 账号与安全 | 通过 | 登录资料、改密、换绑手机、绑定微信入口正常显示 | 正式微信能力仍需正式签名包和开放平台参数验证 | -| 我的 → 应用推广 | 部分通过 | 推荐码、邀请人数、复制推荐码和系统分享文字正常显示 | 参考项目还有注册链接二维码/复制链接;当前后端未返回可信 `shareUrl` | -| 我的 → 意见反馈 | 通过 | 动态类型“建议/故障/投诉/其他”和历史记录正常显示 | 未提交测试数据,避免污染线上数据 | -| 我的 → VIP 服务 | 部分通过 | 套餐和订单正常显示;服务端禁用购买时显示“VIP购买暂未开放” | 购买成功路径需开启渠道后再测;订单号与双时间缺失属于前端展示问题 | -| 家谱 → 我的家谱 | 通过 | 多个家谱可加载、切换,当前家谱和成员数正常显示 | 无 | -| 家谱 → 家谱总览 | 通过(前端绕开缺陷) | 谱名、地区、成员数、世系数、加入日期正常显示 | 当前临时从 `mine` 列表取得总览资料;后端详情缺陷修复后应恢复详情单一来源 | -| 家谱总览 → 申请审核 | 通过 | 空状态正常显示 | 无 | -| 家谱总览 → 世系树 | 通过 | 3 位人物和关系图正常渲染;人物操作面板可打开 | 无 | -| 世系树 → 人物资料/编辑 | 通过 | 详情和编辑页正常打开 | 未保存修改,避免污染线上数据 | -| 编辑成员 → 学历分类 | 通过 | MuMu 中选择器显示“文盲、私塾、幼儿园、小学……”等动态字典项 | 前端已修复并发请求互相取消问题 | -| 家谱首页 → 宣传视频 | 部分通过 | 首页宣传视频区域正常显示,“查看更多”可进入宣传视频页,不再被路由拦截;`home_featured` 和 `video_center` 均为空 | 后端/运营需按下述投放要求配置可用视频和封面后再测播放 | -| 家谱总览 → 家谱设置 | 阻断 | 前端不再无限加载,能进入明确的读取失败状态 | 后端需修复下述 P0 源码缺陷 | -| 家谱首页 → 功德记录图片 | 阻断 | MuMu 选择图片并创建记录成功,但重新打开详情没有图片;测试记录随后已删除 | `AppMeritRecordBody/Vo` 缺媒体字段,后端需补文件关联契约 | - -本轮只执行读取、导航、打开选择器等非破坏性点击;没有提交反馈、修改成员、发验证码、绑定微信、购买 VIP、归档或永久删除。 - -## P0:普通家谱所有者无法读取家谱详情和设置 - -### 复现 - -使用普通生命周期 `NORMAL` 的家谱所有者请求: - -- `GET /genealogy/app/genealogies/{genealogyId}` -- `GET /genealogy/app/genealogies/{genealogyId}/permanent-deletion/capability` - -服务端返回业务错误:`家谱必须先归档`。MuMu 中“家谱设置”因此无法读取。 - -### 源码原因 - -1. `AppGenealogyServiceImpl.detail()` 为谱主拼装永久删除能力时调用 `permanentDeletionService.capability()`。 -2. `GenealogyPermanentDeletionService.capability()` 调用 `requireOwner()`。 -3. `requireOwner()` 不只校验所有者,还强制生命周期必须为 `ARCHIVED`,否则直接抛错。 -4. 但 `GenealogyDeletionEligibilityService` 本身已经能用 `GENEALOGY_NOT_ARCHIVED` 表达“当前不可永久删除”。生命周期不满足应是 capability 的禁用原因,不应让详情和 capability 查询失败。 - -### 后端修复要求 - -- 将“所有者鉴权”和“已归档资格”拆开。 -- capability 查询:谱主 + 普通家谱应成功返回 `canDeletePermanently=false`,`disabledReasons` 包含 `GENEALOGY_NOT_ARCHIVED`。 -- 发码和提交永久删除:继续通过 eligibility 严格拒绝未归档家谱。 -- `AppGenealogyServiceImpl.detail()` 对普通谱主必须成功,不能因附加删除能力投影而失败。 -- 增加自动化测试:普通谱主详情、普通谱主 capability、归档谱主 capability、非谱主 capability、未归档发码/提交拒绝。 - -相关源码: - -- `ruoyi-modules/ruoyi-genealogy/src/main/java/cn/ddxcjp/genealogy/service/impl/AppGenealogyServiceImpl.java:220` -- `ruoyi-modules/ruoyi-genealogy/src/main/java/cn/ddxcjp/genealogy/service/GenealogyPermanentDeletionService.java:29` -- `ruoyi-modules/ruoyi-genealogy/src/main/java/cn/ddxcjp/genealogy/service/GenealogyPermanentDeletionService.java:72` - -## P1:后端 OpenAPI 与 Java 返回对象不一致 - -### VIP capability 字段 - -实际 Java VO `VipPaymentMethodCapabilityVo` 和线上响应均返回: - -```json -{ "method": "WECHAT", "enabled": false, "disabledReason": "VIP购买暂未开放" } -``` - -后端自带 `doc/apifox/genealogy-app-openapi.yaml` 却声明 `paymentMethod`,相关 OpenAPI 契约测试也按 `paymentMethod` 断言。前端已按真实 Java 契约统一使用 `method`,仓库内 OpenAPI 副本也已同步。 - -后端需要把 canonical OpenAPI 和 `FrontendHandoffFinalOpenApiContractTest` 改为 `method`;不要同时返回两个别名。 - -### 谱文分类写接口响应 - -后端 OpenAPI 中以下接口的 `200` 响应误写成“APP 微信支付下单参数”并引用 `PaymentOrderVo`: - -- `POST /genealogy/app/genealogies/{genealogyId}/article-categories` -- `PUT /genealogy/app/genealogies/{genealogyId}/article-categories/{categoryId}` - -应改为真实的谱文分类结果 `AppArticleCategoryResult`,并同步契约测试。前端仓库内 OpenAPI 副本已纠正。 - -## P1:宣传视频投放位当前没有可验收数据 - -### MuMu 实测 - -- `GET /genealogy/app/platform-videos?placement=home_featured` 返回空列表,首页只能显示“暂时没有推荐视频”。 -- `GET /genealogy/app/platform-videos?placement=video_center` 返回空列表,点击“查看更多”后页面显示“暂时没有可观看的平台视频”。 -- 请求成功且不是错误响应,说明前端入口和读取契约已生效,当前缺的是处于有效发布时间范围内的投放数据。 - -### 后端/运营处理要求 - -- 至少配置一条 `home_featured` 和一条 `video_center` 数据;同一视频如需同时出现,应按后端投放模型明确配置,不能要求客户端跨投放位猜测。 -- 每条数据必须返回可访问的 `videoFile`;建议同时提供 `coverFile`,首页和列表会先显示封面,点击后播放指定视频。 -- 确认数据状态、`startAt`、`endAt` 与当前服务器时间满足可见条件,业务文件访问地址可在 App 端读取。 -- 当前 `PlatformVideoVo` 是“视频 + 可选封面”模型,不支持独立的纯图片宣传项。如果产品要求图片也作为可点击宣传内容,需要后端另行定义混合媒体类型、目标行为和唯一响应契约,前端不应把封面伪装成独立图片内容。 - -### 验收 - -1. 首页显示最多两条封面,点任一封面直接打开对应视频。 -2. “查看更多”显示 `video_center` 封面列表,点封面进入纵向播放器。 -3. 视频可播放、上下切换、点赞、评论和返回;过期或停用内容不返回。 - -## P1:第四轮对比新增的后端契约任务 - -| 事项 | 当前源码/契约事实 | 后端处理要求 | 联调通过标准 | -| --- | --- | --- | --- | -| 功德记录图片 | `AppMeritRecordBody`、`AppMeritRecordVo` 没有 `mediaOssIds/mediaFiles`;MuMu 已复现上传后不回显 | 复用业务文件引用,创建/更新接收媒体 ID 集合,列表和详情返回授权文件;明确空数组为清空 | 新增、编辑保留、移除、列表首图、详情预览、回收站权限全部通过 | -| 创建家谱始迁祖 | 前端创建请求已有 `firstAncestorName`,`AppGenealogyCreateBody` 和创建事务没有该字段 | 在创建家谱事务内原子创建第一代人物;失败整体回滚,避免只建家谱未建人物 | 创建完成后世系树立即出现同名第一代人物;重复提交不产生重复人物 | -| 推广注册链接 | `ReferralMeVo` 只有推荐码、人数、标题和文案,没有可用于二维码/复制的可信链接 | 增加由服务端配置并生成的 `shareUrl`,不要要求前端拼接旧 H5 域名或暴露内部用户 ID | App 可复制链接、生成二维码;扫码进入注册后推荐关系只绑定一次 | -| 封面清空语义 | Java 更新服务可把 `coverOssId` 设为 `null` 并替换文件引用,但 OpenAPI 未声明 nullable,前端规范化会丢弃显式空值 | 统一谱文、礼仪、视频更新契约:明确 `null` 表示移除封面并释放旧引用;同步 OpenAPI 和契约测试 | 有封面的记录执行移除后,详情返回 `coverFile=null`,旧文件引用释放,其他字段不变 | -| 列表记录创建时间 | 参考相册、礼仪、功德、成长记录、贺礼簿和家族恩人列表均显示 `create_time`;当前对应 APP VO 没有 `createTime`,现有业务时间字段不是同一语义 | 先为已确认映射的 `AppAlbumVo/AppCeremonyVo/AppMeritRecordVo/AppGrowthRecordVo/AppRelativeRecordVo` 和 OpenAPI 增加只读 `createTime`;家族恩人确认存储方案后,复用 Memo 时再补 `AppMemoVo.createTime`,独立建模时由唯一新 VO 持有;不要要求客户端提交,也不要用业务时间回填 | 列表和详情均返回稳定时间;新增后非空;编辑业务日期不改变创建时间;客户端可同时显示创建时间和业务时间 | - -提现记录已有 `auditRemark/payoutReference/paidAt`,前端会先展示这些现有字段。只有产品明确要求区分“审核时间”和“到账时间”时,后端才需要新增独立 `reviewedAt`;不得把 `paidAt` 改名或冒充审核时间。 - -## 前端本轮已完成 - -- 修复 `ceremony-service.js` 导入不存在导出导致 HBuilderX 编译失败。 -- VIP capability 按真实后端 VO 的 `method` 字段读取,MuMu 已验证套餐和订单恢复显示。 -- 家谱基础列表不再读取由独立永久删除 capability 接口拥有的字段。 -- 家谱总览暂时从 `GET /genealogies/mine` 读取当前家谱,避免被后端详情缺陷连带阻断。 -- 家谱设置的两条并发读取使用独立取消控制器,修复无限加载。 -- 编辑成员的 3 条、添加亲属的 5 条动态字典请求分别使用独立取消控制器,修复选择器空白。 -- 族人资料已使用 `zodiacCode`、`educationCode`、`deathExpressionCode`、`relationVariantCode`,遗传病史等敏感资料使用独立 `/sensitive-profile` 契约。 -- 微信绑定、推荐资料、权限目录、视频分页评论、内容密码找回、VIP 多支付和永久删除页面已接入最新接口。 -- 家谱首页已接入 `home_featured` 两条封面预览,宣传视频列表已改为封面优先展示并支持 `videoId` 直达播放。 - -## 后端回传验收材料 - -修复后请提供: - -1. 上述 P0 场景的自动化测试结果。 -2. 更新后的 canonical APP OpenAPI。 -3. 已部署环境版本号或提交号。 -4. 普通谱主详情和永久删除 capability 的实际响应样例。 -5. `home_featured`、`video_center` 各至少一条可用宣传视频及封面,由测试环境实际接口返回。 -6. 功德图片、始迁祖、推广 `shareUrl`、封面清空语义和五类已确认映射记录 `createTime` 的更新后契约与自动化测试结果;家族恩人契约等待产品选型后另行确认。 - -收到部署确认后,前端只需再次在 MuMu 点击“家谱设置”,并回归详情、归档、恢复和永久删除能力状态;不会再补旧字段兼容。 - -## 2026-08-23 运行时复验补充 - -本轮在用户已登录的 MuMu 中重新实点“家谱总览 → 家谱设置”,首次读取仍进入“家谱设置暂时无法读取”;点击“重新读取”后截图哈希完全相同,说明当前部署环境的 P0 阻断仍然存在。证据见 [当前设置失败](audit-2026-08-23/27-current-settings.png) 和 [重试后状态](audit-2026-08-23/28-current-settings-retry.png)。 - -同时在参考项目浏览器中确认了以下后端交接需求的真实产品用途: - -- 推广 `shareUrl` 用于页面二维码与注册链接分享,不是用推荐码文本可以完全替代的字段。 -- 参考列表的 `createTime` 是记录创建时间,不能用礼仪时间、功德时间、提醒时间等业务发生时间冒充;五类已确认映射记录先补,家族恩人随选定契约补。 -- 当前重要证件查询能力已足够支持家谱级聚合页,不需要为入口另造接口。 -- VIP 的 `orderNo/payTime/expireTime` 均是参考购买记录直接显示的独立字段,前端修复后需要后端继续稳定返回。 - -### 暂不交给后端开发的产品确认项 - -参考项目 `pages/index/memorandum/*` 的真实页面名称是“家族恩人”,不是普通“备忘录”;MuMu 实点确认当前入口和表单都是提醒型“家族备忘”,最新后端主业务代码也只有 `Memo`,没有恩人类型。因此业务语义缺口已确认,但后端实现需先由产品选择以下二选一: - -1. “家族恩人”是独立家族档案:再由前后端共同定义唯一数据契约、权限和迁移方式。 -2. “家族恩人”只是备忘录的一种分类:由 `Memo` 契约增加明确且受校验的业务类型,前端按类型提供入口和文案。 - -确认前请勿仅按路由英文名把两者合并,也不要先增加猜测字段。参考“贺礼簿”则已确认对应当前更结构化的 `RelativeRecord`/“往来记录”,不需要另建一套后端接口。 - -完整截图与前后端责任拆分见 [2026-08-23 点击对比审查](click-comparison-audit-2026-08-23.md)。 diff --git a/docs/backend-integration-tasks-2026-08-17.md b/docs/backend-integration-tasks-2026-08-17.md deleted file mode 100644 index e51a882..0000000 --- a/docs/backend-integration-tasks-2026-08-17.md +++ /dev/null @@ -1,142 +0,0 @@ -# 后端开发对接任务单 - -> 本文件是 2026-08-17 的历史任务单。最新后端源码已实现其中多项当时缺失的能力;当前有效结论和剩余任务以 [《APP 前后端联调结果与后端处理单(2026-08-22)》](./backend-integration-report-2026-08-22.md) 为准。 - -更新时间:2026-08-17 - -收件人:家谱项目后端开发、测试及接口维护人员 - -## 后端执行结论 - -请按本任务单完成缺失接口、数据库字段、权限校验和自动化测试。前端页面及调用逻辑已经完成,不需要后端等待前端再次开发;接口实现后可直接联调。 - -建议执行顺序: - -1. P0:族人档案字段、微信登录、VIP 多支付、内容密码找回、家谱永久注销。 -2. P1:推荐关系、动态权限目录、平台评论契约收紧。 -3. 联调后端已存在的公开家谱搜索、视频评论、平台视频和回收站接口。 - -接口路径、字段、枚举、必填规则及响应 schema 只以随文提供的 `genealogy-app-openapi.yaml` 为准。后端实现与 OpenAPI 不一致时,应同步修正实现或契约,不能要求前端增加旧字段、snake_case 别名或猜测式兼容代码。 - -## 1. 结论与契约归属 - -前端已补齐本轮确认保留的能力。后端尚未提供的能力没有使用假数据或旧接口兼容:页面、入口、表单、提交状态、失败提示、取消请求和严格响应校验均已完成,接口可用后直接进入联调。 - -`genealogy-app-openapi.yaml` 是本项目 APP 接口的唯一契约所有者。本文件只记录前后端差距、责任和验收方式,不重复定义请求或响应结构;实现字段与枚举一律以 OpenAPI 为准。 - -核对基线: - -- 参考前端:`C:\Users\Rain\Desktop\job\Jiapu-App` -- 当前前端:`C:\Users\Rain\Desktop\job\jiapuapp` -- 当前后端源码:`C:\Users\Rain\Desktop\job\Genealogy` -- 参考项目注册了 78 个活动路由;`video2.nvue`、`video3.nvue`、`video4.nvue` 均为有效且可达的视频页,已纳入前端比对。`ancestorsOrder.vue` 是 67 字节空壳,不作为正式功能。 -- 当前项目有 59 个有效路由。旧版多页面流程已按当前产品职责合并,因此验收按业务能力和用户路径,不按旧文件数量一一复制。 - -> “后端源码现状”来自 2026-08-17 静态源码核对。前端侧没有修改、构建或运行后端工程,后端开发完成后需自行执行后端构建与自动化测试。 - -## 2. 总体差距表 - -| 优先级 | 业务能力 | 当前前端状态 | 后端源码现状 | 后端下一步 | 联调通过标准 | -| --- | --- | --- | --- | --- | --- | -| P0 | 微信快捷登录 | 已完成授权码登录入口、取消/失败状态、重复提交保护;前端不接收 AppSecret | `AppAuthController` 只有注册、密码登录和短信登录,没有 `/auth/login/wechat` | 按 OpenAPI 新增授权码交换、账号匹配/绑定与冲突响应;正式开放平台参数只放服务端和打包配置 | Android/iOS 正式包完成首次授权、已有账号登录、取消授权、重复登录和账号冲突路径 | -| P0 | VIP 多支付方式 | 已完成微信、支付宝、余额选项;选项完全由 capability 下发;三类支付结果严格分支校验 | capability 只返回 `enabled/disabledReason`;下单体没有 `paymentMethod`;支付响应仍是微信单一形态 | capability 下发支付方式;下单接收 `paymentMethod`;按渠道返回互斥参数;余额支付在服务端原子扣款并开通会员 | 三种支付各走通成功、取消、失败、超时查询;同一订单不能跨渠道重复支付或重复开通 | -| P0 | 族人完整档案 | 新增、编辑、详情展示及校验均完成;遗传病史按服务端能力字段控制 | `LineagePerson/Bo/Vo` 仅已有 `aliasName`,其余新增字段缺失 | 增加数据库列、实体、请求体、VO、映射与服务校验;敏感字段必须服务端鉴权后才返回 | 新增和编辑可回显全部字段;无权限响应不包含遗传病史;旧数据读取不报错 | -| P0 | 内容密码找回 | 谱文、成长记录、重要证件已接入短信验证找回;有发送倒计时、重置状态和未知结果防重 | 已有内容密码保护/解锁/移除和通用认证验证,但没有 recovery 三个 APP 端点 | 复用服务端短信验证能力,实现 capability、发码、重置;只允许资源所有者或获授权管理员操作 | 三类资源均覆盖发码限频、错码、过期码、无权限、成功重置;全链路有审计记录 | -| P0 | 家谱永久注销 | 设置页已完成归档前置、不可用原因、脱敏手机号、家谱名+短信双确认和非幂等未知结果处理 | 后端已有管理员删除引擎与资格检查,但没有 APP 谱主入口;`AppGenealogyVo` 没有注销能力字段 | 用现有删除引擎增加 owner-only APP 包装;投影 capability;成功提交后撤销所有成员家谱上下文 | 非谱主、未归档、名称不符、错码、资金/任务阻塞均拒绝;成功后不可再进入并异步完成清理 | -| P0 | 创建家谱始迁祖 | 创建表单已增加“始迁祖”,请求字段为 `firstAncestorName` | 当前创建家谱请求体和服务没有该字段,也不会原子创建首位世系人物 | 按 OpenAPI 接收字段,并在创建家谱事务内创建对应第一代人物;失败时家谱和人物一起回滚 | 填写始迁祖创建后,世系树立即出现同名第一代人物;重复提交不产生重复人物 | -| P1 | 功德记录图片 | 功德表单已支持图片上传、逐项移除、编辑保留和详情预览;请求 `mediaOssIds`,响应 `mediaFiles` | 当前功德记录请求体和 APP VO 没有媒体字段 | 按 OpenAPI 复用业务文件关联;更新按传入 ID 集合替换关联,空字符串表示清空;列表和详情返回授权后的 `BusinessFileAccess[]` | 新增、编辑、移除和详情均能正确回显;越权和失效文件不可访问;移入回收站后附件权限同步失效 | -| P1 | 推广关系 | 注册页可填写推荐码;个人中心有“我的推荐”、复制和分享入口;全部使用正式响应,不伪造收益 | 注册体没有 `referralCode`,没有推荐资料接口或推荐关系服务 | 注册支持一次性绑定推荐人;新增 `/referrals/me`;落实防自邀、防重复绑定和收益归属幂等 | 首次绑定、无推荐码、自邀、重复绑定、并发注册和推荐资料查询均有自动化测试 | -| P1 | 动态权限项 | 成员管理可从服务端目录渲染分组权限、读取和保存成员授权;前端不硬编码 `auth_str` | 成员权限 GET/PUT 已存在;缺少 `/permission-catalog` | 基于后端唯一权限定义输出当前家谱可授权目录、名称、分组和禁用原因;保存响应返回最终授权集合 | 后端新增权限无需发版即可显示;越权勾选被服务端拒绝;保存后回显与实际鉴权一致 | -| P1 | 家谱视频评论 | 已完成一级评论、一级回复展示/发表、本人或管理员删除;回复入口只允许根评论 | 根评论、回复列表、发表、删除均已存在,字段与前端契约基本一致 | 按 OpenAPI 联调并补自动化契约测试;保持只允许一层回复 | 根评论和直属回复顺序正确;删除权限可信;弱网重复提交不会静默生成多条 | -| P1 | 平台宣传视频 | 已完成列表、播放、点赞、一级评论和删除;链接统一通过业务文件访问层处理 | 列表/详情/点赞/评论均已存在;评论请求仍允许 `parentCommentId` 并在服务内支持回复 | 本期产品决定为平台视频只保留一级评论:删除 `parentCommentId` 输入并清理/迁移已有回复数据 | APP 位置筛选正确;过期内容不返回;点赞幂等;平台评论响应中不存在子回复 | -| P1 | 公开家谱搜索 | 已完成关键词输入、清空、加载/空/错状态及本地二次过滤 | `/genealogies/public` 已支持可选 `keyword` | 无新增接口,按 OpenAPI 联调并确认匿名/登录策略 | 姓氏、谱名、堂号等后端约定字段可查;空关键词恢复列表;分页/数量限制明确 | -| P1 | 相册批量管理 | 已完成批量选择、全选、逐张移至回收站、部分失败保留选择及结果提示 | 单张删除和回收站机制已存在,资源引用可恢复 | 不阻塞上线;如后续数据量需要,再单独设计服务端批量接口和部分成功语义 | 选中项逐张处理可见;失败项仍被选中;成功项可从回收站恢复 | -| P1 | 内容删除与回收站 | 前端所有相关文案统一为“移至回收站”,不再错误声称立即永久删除 | `ContentRecycleBinService` 及恢复链路已存在 | 按现有接口联调,确认各资源类型映射完整 | 删除后列表移除、回收站出现、恢复后关系与文件引用完整 | - -## 3. 族人档案字段表 - -以下键名已经写入前端契约和 OpenAPI。后端需要把 OpenAPI 作为唯一字段来源,不增加 snake_case 别名或双读兼容分支。 - -| 字段 | 含义 | 前端行为 | 后端要求 | -| --- | --- | --- | --- | -| `courtesyName` | 字 | 新增、编辑、详情显示;文本长度校验 | 新增数据库字段并原样回显 | -| `aliasName` | 别名 | 已接入;空值不显示 | 后端已有,核对映射与长度即可 | -| `zodiac` | 生肖 | 选择并显示 | 校验 OpenAPI 枚举,不接受任意文本 | -| `currentAddress` | 现居住地 | 文本输入和详情显示 | 长度校验,空值保持为空而非虚构默认值 | -| `mobile` | 手机号 | 格式校验,详情显示 | 格式和权限由服务端再次校验 | -| `email` | 邮箱 | 格式校验,详情显示 | 规范化大小写规则并回显 | -| `education` | 学历 | 文本输入和显示 | 按 OpenAPI 长度保存,不自行映射未知字典 | -| `occupation` | 职业 | 文本输入和显示 | 按 OpenAPI长度保存 | -| `deathAge` | 享年 | 数字输入;仅逝者相关资料使用 | 使用明确整数范围;不能以真假判断吞掉 `0` | -| `deathType` | 去世原因/类型 | 文本输入和显示 | 依 OpenAPI长度保存;不要与生存状态混成同一字段 | -| `burialDate` | 安葬日期 | 日期选择和格式化显示 | 使用 OpenAPI 日期格式,避免时区转换导致日期偏移 | -| `hereditaryMedicalHistory` | 遗传病史 | 仅 `canManageSensitiveMedicalHistory=true` 时编辑/显示 | 服务端强制鉴权;无权限时响应不得泄露字段内容;需审计访问与修改 | -| `canManageSensitiveMedicalHistory` | 敏感病史能力 | 决定表单和详情是否出现敏感字段 | 由当前用户、家谱和成员关系实时计算,客户端提交不能覆盖 | - -建议后端改动顺序:数据库迁移 → Entity/Bo/请求 DTO/Vo → Mapper → Service 校验和鉴权 → Controller 契约测试。不能只扩 DTO 而遗漏数据库、详情 VO 或树节点回显。 - -## 4. 后端已存在、直接进入联调的接口 - -| 能力 | OpenAPI 路径 | 源码核对结论 | -| --- | --- | --- | -| 公开家谱搜索 | `GET /genealogy/app/genealogies/public?keyword=` | 已支持可选关键词 | -| 家谱视频根评论 | `GET/POST /genealogy/app/genealogies/{genealogyId}/videos/{videoId}/comments` | 已存在 | -| 家谱视频回复 | `GET /genealogy/app/genealogies/{genealogyId}/videos/{videoId}/comments/{commentId}/replies` | 已存在 | -| 家谱视频评论删除 | `DELETE /genealogy/app/genealogies/{genealogyId}/videos/{videoId}/comments/{commentId}` | 已存在 | -| 平台视频 | `GET /genealogy/app/platform-videos?placement=` | 已存在,当前 Controller 要求 placement | -| 平台视频点赞/评论 | `/genealogy/app/platform-videos/{videoId}/likes`、`/comments` | 已存在;评论需收紧为一级 | -| 成员权限读取/保存 | `GET/PUT /genealogy/app/genealogies/{genealogyId}/members/{memberId}/permissions` | 已存在;保存响应需严格返回最终集合 | -| 内容回收站 | `/genealogy/app/genealogies/{genealogyId}/recycle-bin/...` | 已有查询与恢复服务 | - -## 5. 后端需要新增或调整的契约 - -具体 schema、required、枚举和响应包装见 `genealogy-app-openapi.yaml`,这里仅列责任边界。 - -| 端点/契约 | 类型 | 后端责任 | -| --- | --- | --- | -| `POST /genealogy/app/auth/login/wechat` | 新增 | 只接收微信一次性授权码;服务端换取身份并处理账号冲突 | -| `POST /genealogy/app/auth/register` 的 `referralCode` | 调整 | 注册事务内一次绑定,防自邀、防重复与并发覆盖 | -| `GET /genealogy/app/referrals/me` | 新增 | 返回稳定推荐码和服务端统计;无数据也返回合法空统计 | -| `GET /genealogy/app/vip/capability` 的 `paymentMethods` | 调整 | 返回当前租户、平台、用户可用渠道及禁用原因 | -| `POST /genealogy/app/vip/orders` 的 `paymentMethod` 与响应 | 调整 | 按微信/支付宝/余额返回互斥结果;响应必须带渠道判别字段 | -| `/content-password-recovery/{resourceType}/{resourceId}` | 新增三步接口 | 查询能力、发送验证码、验证并重置;服务端掌握手机号与权限 | -| `GET /genealogy/app/genealogies/{genealogyId}/permission-catalog` | 新增 | 输出动态权限目录,权限编码只由后端唯一权限定义产生 | -| `AppGenealogyVo` 注销能力字段 | 调整 | 返回 `canDeletePermanently`、禁用原因和已验证手机号脱敏值 | -| 家谱永久注销发码与提交 | 新增 | owner-only,校验归档、阻塞任务、精确家谱名、短信码并调用现有删除引擎 | -| 族人档案字段 | 调整 | 数据库到请求/响应全链路一致;敏感病史单独服务端鉴权 | -| 平台视频评论请求 | 收紧 | 去掉 `parentCommentId`,拒绝并清理不符合一级评论契约的数据 | - -## 6. 本轮明确的产品取舍 - -| 参考项目做法 | 当前实现 | 原因 | -| --- | --- | --- | -| 安全问题找回内容密码 | 已验证手机号短信找回 | 安全问题答案弱且容易被猜测,不能作为敏感内容的正式凭据 | -| 直接点击永久删除家谱 | 归档前置 + 家谱名 + 短信双确认 + 后台异步任务 | 家谱关联数据多,必须由服务端做资格检查和可审计的高风险操作 | -| 固定 `auth_str` 权限字符串 | 服务端动态权限目录 + 成员授权集合 | 权限语义必须由服务端唯一拥有,避免客户端版本与鉴权漂移 | -| 删除提示为永久删除 | 内容先进入回收站 | 与当前后端实际生命周期一致,避免误导用户 | -| 宣传视频直接信任旧 URL | 平台视频资源走统一文件访问层 | 避免 HTTP、过期或未授权资源地址绕过现有文件契约 | -| 客户端保存微信密钥或信任用户资料 | 客户端仅提交一次性授权码 | AppSecret 必须只在服务端;展示资料不能作为登录身份凭据 | - -## 7. 后端验收与安全底线 - -- 所有写接口继续在服务端校验家谱成员关系、角色和具体权限,不能以页面按钮是否显示作为安全边界。 -- 微信登录、短信发码、内容密码重置和永久注销必须有限频、过期、一次性消费、失败次数限制和审计记录。 -- 遗传病史属于敏感字段:无权限时不只是禁止修改,也不得从列表、详情、树节点或日志中返回原文。 -- 余额购买 VIP 必须在一个服务端事务中完成余额校验、扣款、订单成功和权益开通,并使用稳定幂等键防重复扣款。 -- 支付宝仅返回 APP 支付所需订单字符串;微信返回完整 APP 预支付签名参数;不同渠道字段不能混合猜测。 -- 永久注销成功提交后应立即让所有成员端失去该家谱操作上下文;异步删除失败要可追踪、可重试,但不能把家谱恢复成可写状态。 -- 运行时响应必须通过 OpenAPI 定义;不要长期保留旧字段、snake_case 别名或“缺字段时客户端猜测”的兼容路径。 - -## 8. 前端验收状态与待联调项 - -前端静态契约、59 个页面注册、导航、隐私审计、设计回归和资源引用均已通过项目检查。以下事项只能在后端完成后验证: - -- 真机微信登录和微信/支付宝支付回跳; -- 余额真实扣款、订单查询和重复支付防护; -- 短信发送、限频、过期和服务端审计; -- 族人字段数据库持久化与敏感病史服务端脱敏; -- 永久注销任务、成员上下文撤销和关联数据清理; -- 推广关系的注册事务、归属和收益统计; -- 权限目录与各业务接口实际判权的一致性。 - -联调时若运行时响应与 OpenAPI 不一致,应优先修正后端实现或 OpenAPI 的唯一契约,不应在前端增加第二套字段读取逻辑。 diff --git a/docs/backend-reference-gap-tasks-2026-08-23.md b/docs/backend-reference-gap-tasks-2026-08-23.md deleted file mode 100644 index 5080941..0000000 --- a/docs/backend-reference-gap-tasks-2026-08-23.md +++ /dev/null @@ -1,145 +0,0 @@ -# 剩余九项后端与产品任务及参考项目证据 - -日期:2026-08-23 -收件人:后端开发、接口维护、测试及产品负责人 - -## 结论 - -前端能够独立完成的十三项已经处理。剩余九项中: - -- 六项在参考项目中有直接页面或字段证据; -- 两项有等价流程或部分实现证据,不能逐字段照搬; -- 一项在参考项目前端中没有实现证据,但属于当前项目必须独立收口的安全要求。 - -后端可以直接排期八项;“家族恩人”必须先由产品确定是独立档案还是备忘录分类,再确定唯一契约。 - -参考项目只用于确认产品行为,不作为接口字段命名、安全设计或数据模型的权威来源。当前项目最终契约仍以 `genealogy-app-openapi.yaml` 和后端实现共同确认的单一版本为准。 - -## 九项核对表 - -| 序号 | 剩余事项 | 参考项目是否存在 | 证据与判断 | 当前责任 | -| --- | --- | --- | --- | --- | -| 1 | 功德记录图片保存和回显 | 是 | `pages/index/meritsVirtues/add.vue` 可上传多图,列表使用 `item.imgs[0]`,详情遍历 `datas.imgs` | 后端直接开发 | -| 2 | 推广注册链接和二维码 | 是 | `pages/mine/fenxiang.vue` 生成带推荐人参数的注册链接二维码 | 后端直接开发;链接必须由服务端生成,不能照抄旧域名或直接暴露用户 ID | -| 3 | 谱文、礼仪、视频封面清空 | 部分存在 | 谱文共用图片组件支持删除,礼仪编辑页明确可清空 `cover`;视频封面删除后的父表单同步不完整,不能作为可靠契约 | 后端按当前模型统一清空语义 | -| 4 | 多类内容的创建时间 | 是 | 参考相册、礼仪、功德、成长记录、贺礼簿列表直接显示 `create_time` | 后端直接开发 | -| 5 | 家族恩人业务语义 | 是 | `pages/index/memorandum/index.vue`、`add.vue`、`details.vue` 均明确使用“家族恩人”名称,并支持图片和创建时间 | 产品先选模型,随后后端开发 | -| 6 | 创建家谱时落库始迁祖 | 是 | `pages/index/createGenealogy.vue` 将 `first_ancestor_name` 作为必填项,与家谱资料一同提交 | 后端直接开发 | -| 7 | 谱主与世系人物绑定闭环 | 有等价流程 | 参考创建请求同时携带当前 `user_id` 和 `first_ancestor_name`,树编辑也提供“绑定账号”;更合理的闭环是创建家谱时原子绑定谱主,而不是开放谱主角色编辑 | 后端直接开发 | -| 8 | 换绑手机号前重新验证当前身份 | 是 | `pages/mine/changemobile.vue` 要求 `oldPassword + newMobile` | 后端直接开发,但建议保留当前新手机号短信验证,形成双重验证 | -| 9 | 改密、换绑后的既有会话失效 | 未发现 | 参考改密和换绑成功后直接返回个人中心,没有清理令牌或重新登录逻辑;仅凭参考前端无法证明服务端是否失效旧令牌 | 当前项目独立安全任务,后端直接开发 | - -## 后端接口任务 - -### 1. 功德记录媒体 - -当前 `AppMeritRecordBody`、`AppMeritRecordVo` 没有媒体请求和响应字段。 - -处理要求: - -- 创建和更新接收 `mediaOssIds`,由后端维护业务文件引用; -- 列表和详情返回授权后的 `mediaFiles`; -- 明确空集合表示清空全部图片; -- 回收站、恢复和越权访问同步处理文件权限。 - -验收:新增、编辑保留、逐项移除、列表首图、详情预览和回收站恢复均通过。 - -### 2. 推广分享链接 - -当前推荐资料只有推荐码、邀请人数和文案,没有可信 `shareUrl`。 - -处理要求: - -- 在推荐资料响应中增加服务端生成的 HTTPS `shareUrl`; -- 链接中的推荐凭据使用可校验、可控生命周期的业务标识,不能直接拼接内部用户 ID; -- 注册时继续执行防自邀、一次性绑定和并发幂等校验。 - -验收:前端可以复制链接、生成二维码和系统分享;扫码注册后推荐关系只绑定一次。 - -### 3. 封面清空契约 - -谱文、礼仪和视频已有 `coverOssId`,但 OpenAPI 没有统一声明显式清空语义。 - -处理要求: - -- 更新请求中的 `coverOssId: null` 统一表示移除封面; -- 字段未出现表示保持原封面不变; -- 同步释放旧业务文件引用; -- Java DTO、更新服务、OpenAPI 和契约测试保持一致,不保留空字符串等第二套清空方式。 - -验收:移除后详情返回 `coverFile=null`,其他字段不变,旧文件不再保留业务引用。 - -### 4. 只读创建时间 - -处理要求: - -- 为 `AppAlbumVo`、`AppCeremonyVo`、`AppMeritRecordVo`、`AppGrowthRecordVo`、`AppRelativeRecordVo` 增加只读 `createTime`; -- 家族恩人选定模型后,由对应唯一 VO 持有 `createTime`; -- 不允许客户端提交或修改创建时间,也不能使用礼仪时间、记录日期、提醒时间等业务字段代替。 - -验收:新增后创建时间非空;编辑业务内容或业务日期不会改变创建时间。 - -### 5. 家族恩人契约 - -产品必须二选一: - -1. 独立家族档案:定义独立实体、身份或类别、说明、图片、创建时间、权限和回收站类型; -2. 备忘录分类:由 `Memo` 的唯一契约增加受校验的业务类型,并明确恩人专属字段、提醒字段是否适用以及旧数据迁移规则。 - -确认前不要仅把“家族备忘”改标题,也不要先加入无法验证的猜测字段。 - -### 6. 始迁祖与谱主绑定 - -这两项应在同一创建事务中完成: - -- `AppGenealogyCreateBody` 接收必填或按产品规则校验的 `firstAncestorName`; -- 创建家谱后创建同名第一代世系人物; -- 将当前谱主成员记录绑定到该人物,或按产品确认的关系建立明确绑定; -- 任一步失败时家谱、人物、成员关系整体回滚; -- 重放同一创建请求不能产生重复人物或重复绑定。 - -如果业务允许谱主后续改绑,应新增只允许谱主修改“本人世系人物绑定”的窄接口。该接口不得同时开放角色修改、谱主移除或任意成员资料编辑。 - -验收:新建家谱后世系树立即出现始迁祖,唯一谱主成员具有明确人物绑定;旧家谱谱主也有受控补绑路径。 - -### 7. 换绑手机号的重新认证 - -当前 `AppPhoneChangeBody` 只有 `phone + smsCode`,只证明操作者控制新手机号。 - -建议唯一流程: - -1. 校验当前登录密码,或校验近期完成的重新认证票据; -2. 校验新手机号短信票据; -3. 在同一服务端操作中更新手机号和密码登录标识; -4. 记录安全审计事件。 - -不要用行为验证码代替当前身份验证。行为验证码只能降低自动化攻击,不能证明操作者仍掌握账号凭据。 - -验收:旧密码错误、重新认证过期、新手机号错码、新手机号已占用均拒绝;全部验证通过后才换绑。 - -### 8. 安全操作后的会话失效 - -当前 `AppAuthServiceImpl.changePassword()` 和 `changePhone()` 中没有发现令牌注销或其他会话踢除逻辑。 - -处理要求: - -- 修改密码后使该用户的其他既有令牌失效; -- 换绑手机号后建议使全部令牌失效,并要求使用新手机号重新登录; -- 如果保留当前设备会话,必须明确区分当前令牌与其他令牌,并通过自动化测试证明; -- 失效必须由服务端执行,不能只让前端删除本地缓存。 - -验收:安全操作前签发的旧令牌再次访问受保护接口时返回未登录;新凭据可以重新登录。 - -## 另行保留的既有 P0 - -普通谱主读取永久注销 capability 时,后端仍会因家谱未归档而报错。前端已经把基础设置读取与永久注销资格读取拆开,避免整个设置页被连带阻断,但后端仍需让普通家谱成功返回 `canDeletePermanently=false` 和明确禁用原因。该问题已经记录在 `backend-integration-report-2026-08-22.md`,不重复计入以上九项。 - -## 后端回传材料 - -完成后请提供: - -1. 更新后的 canonical APP OpenAPI; -2. 对应后端提交号和部署环境版本; -3. 新增或更新的接口自动化测试结果; -4. 功德媒体、分享链接、封面清空、创建时间、始迁祖和谱主绑定的真实响应样例; -5. 换绑重新认证及旧令牌失效的安全测试结果。 diff --git a/docs/click-comparison-audit-2026-08-23.md b/docs/click-comparison-audit-2026-08-23.md deleted file mode 100644 index 58b2675..0000000 --- a/docs/click-comparison-audit-2026-08-23.md +++ /dev/null @@ -1,153 +0,0 @@ -# 当前项目与参考项目点击对比审查 - -审查日期:2026-08-23 - -## 结论 - -本轮同时完成了两类核对: - -- 代码全量核对:参考项目 78 条活动路由逐条映射到当前项目 59 条活动路由,合并页面按入口、操作、字段和状态判断,不按页面数量机械判缺。 -- 运行时点击核对:参考项目使用用户已登录的浏览器,当前项目使用用户已登录的 MuMu;第一轮对首页、家谱总览、家族视频、相册、个人中心、推广、收益提现、重要证件、VIP 和家谱设置进行了实际点击与截图。继续复核时,参考项目又实点礼仪、谱文、功德、家族恩人、贺礼簿、成长记录、字辈谱、世系谱、家族动态、管理员、消息、创建和加入家谱;MuMu 重新登录后,补点了礼仪、功德、贺礼簿、家族备忘、人物详情和成长日志的列表、空状态及新建表单。 - -继续复核没有发现需要推翻既有“融合覆盖”判断的新页面,但纠正了两处业务名称:参考 `favor` 是“贺礼簿”,当前也以“贺礼簿”作为入口并升级为结构化往来记录;参考 `memorandum` 的产品名称是“家族恩人”,当前运行态明确是“家族备忘”,没有恩人身份或分类,不能判定已经融合。当前仍确认 14 项确定缺口,另有实现方案待产品选择和数据不足待验项。最紧急问题是当前项目的家谱设置仍被接口错误整体阻断。 - -## 点击链路结果 - -| 链路 | 参考项目实点结果 | 当前项目实点结果 | 判定 | -| --- | --- | --- | --- | -| 首页 → 家谱总览 | 进入 15 宫格式功能总览 | 进入纵向家谱总览;谱文、相册、视频等内容合并到“家族”主标签 | 已融合,不按页面布局判缺 | -| 家谱总览 → 家族视频 | 卡片先显示封面,点击进入独立播放页;浏览器播放页为黑屏,不能据此确认视频源可播放 | 家族视频入口可进入,但测试家谱没有视频数据;列表源码仍直接渲染播放器 | 确认列表交互缺口;实际播放待有数据再验 | -| 家族 → 相册 → 相册详情 | 列表显示封面、名称、说明、照片数、创建时间;详情可进入 | 列表与详情可进入;当前测试相册为空,列表不显示创建时间 | 创建时间缺口确认;有图预览因数据不对等暂不能下结论 | -| 我的 → 分享变现/应用推广 | 页面直接显示注册链接二维码,可点击链接分享 | 显示推荐码、邀请人数、复制推荐码、系统分享和推广内容,没有页面二维码或注册链接 | 确认前后端缺口 | -| 我的 → 余额/收益与提现 | 始终提供提现记录、申请提现;申请页有金额和收款码上传 | 当前显示收益与提现页,但接口未给最低金额时直接显示“暂未开放提现” | 可用性差异确认;记录字段缺口由代码契约进一步确认 | -| 家谱总览 → 重要证件 | 独立家谱级页面按证件类型集中显示多图,并有管理、上传 | 家谱总览没有聚合入口,仅人物资料内提供证件管理 | 确认前端聚合入口缺口,现有接口可复用 | -| 我的/总览 → VIP 购买记录 | 每条显示订单号、套餐、状态、支付时间、到期时间 | 当前显示套餐、金额和状态,不显示订单号,支付/到期时间也未完整分开展示 | 确认前端字段消费缺口 | -| 家谱总览 → 家谱设置 → 重新读取 | 参考项目基础资料可查看和维护 | 首次进入显示“家谱设置暂时无法读取”;点击重试后仍为完全相同错误画面 | P0 阻断,确认仍未修复 | - -## 继续点击复核(编号步骤) - -1. 点击参考“礼仪”列表并进入详情:列表和详情都显示活动封面,详情另显示分类、时间和地点。当前代码已有 `coverFile`,但列表与详情没有消费,原“礼仪封面缺口”结论成立。 -2. 点击参考“谱文”分类和列表:可进入分类列表;详情被内容密码弹窗拦住,本轮没有绕过密码,因此只保留已有列表与源码证据,不声称详情已验证。 -3. 点击参考“功德”列表并进入详情:列表有首图和创建时间,详情可显示多张图片。当前前端虽有上传与预览代码,但后端媒体契约仍未完整闭环,原缺口成立。 -4. 点击参考 `memorandum` 列表并进入详情:页面实际名称是“家族恩人”,显示标题、说明、多图和创建时间。当前只有通用“家族备忘”,代码与最新后端主业务源码均没有恩人类型;是否用备忘录扩展类型或保留独立模块,必须先由产品确认。 -5. 点击参考 `favor` 列表并进入详情:页面实际名称是“贺礼簿”,显示标题、说明、多图和创建时间。当前“往来记录”已升级为姓名、关系、事项、日期、金额、备注和多图,核心能力已融合,只保留列表首图与创建时间差异。 -6. 依次点击参考“成长记录 → 人物 → 成长阶段 → 具体记录”:人物层不显示时间,但具体记录列表确实显示 `create_time`,因此“六类记录创建时间”仍包含成长记录,不能把业务日期代替创建时间。 -7. 点击参考“字辈谱”和“世系谱”:当前分别有字辈管理和图形化谱系,属于同能力融合/升级,不补重复页面。 -8. 点击参考“家族普”:实际内容是家族动态流,对应当前“家族圈”,不是另一套缺失的家谱模块。 -9. 点击参考“管理员”:当前成员与角色管理覆盖且权限表达更细,判定为升级,不补页面。 -10. 点击参考“VIP 购买”和“消息”:支付方式能力当前已按后端能力动态展示;消息中的申请、文档和联系入口已被当前消息中心、帮助与合规页面拆分覆盖。 -11. 点击参考“创建家谱”和“加入家谱”:当前创建字段覆盖并更完整,仅始迁祖后端落库仍是已确认缺口;当前申请、邀请码和搜索加入属于安全流程升级。 -12. 准备继续点击当前 MuMu 时,应用登录态已失效并停在“登录家谱”。本轮拒绝把登录页截图作为功能证据;礼仪、功德、家族恩人/备忘、贺礼簿/往来记录和成长记录的当前端再次实点,需恢复登录后补测。 - -## MuMu 重新登录后的补点结果 - -13. 从当前“家族动态”进入“礼仪”:列表和空状态正常,新建表单显示活动类型、标题、说明、日期、时间、地点、详细地址和封面图片。测试家谱没有礼仪数据,因此不能用运行态证明列表/详情会显示封面;源码不消费 `coverFile` 的缺口仍成立。 -14. 从当前“家族动态”进入“功德录”:列表和空状态正常,新建表单显示捐赠人、标题、金额、内容、类型、日期、时间及相关图片。最新后端 `AppMeritRecordBody/AppMeritRecordVo` 仍没有媒体字段,所以前端上传入口存在,但保存回传链路未闭环。 -15. 从当前“家族动态”进入“贺礼簿”:当前页面标题就是“贺礼簿”,新建表单将参考自由文本升级为亲友姓名、关系称谓、礼仪事项、日期、时间、礼金金额、备注和图片。确认属于融合升级,不新增独立页面;空列表仍无法验证首图和创建时间显示。 -16. 从当前“家族动态”进入“家族备忘”:新建表单只有备忘标题、提醒日期/时间、备忘内容和图片,没有恩人姓名、身份、类别或专属文案。参考“家族恩人”的产品语义在当前端确实不可发现,确认是业务入口/分类缺口;可复用现有 Memo 模块扩类型,但不能只改标题冒充完成。 -17. 从当前“人物录 → 人物详情 → 成长日志”进入成长记录:链路可走通,新建表单支持人物、成长阶段、标题、内容、记录时间、提醒时间、图片和视频。核心能力已融合且更丰富;当前入口比参考多两层,是否提升到家族首页属于信息架构选择。测试人物没有成长数据,仍不能用运行态验证列表创建时间。 - -### MuMu 补点截图 - -| 当前礼仪 | 当前功德 | -| --- | --- | -| ![当前礼仪空状态](audit-2026-08-23/61-current-ceremonies.png)
![当前礼仪新建表单](audit-2026-08-23/62-current-ceremony-create.png) | ![当前功德空状态](audit-2026-08-23/63-current-merits.png)
![当前功德图片字段](audit-2026-08-23/65-current-merit-create-media.png) | - -| 当前贺礼簿 | 当前家族备忘 | 当前成长日志 | -| --- | --- | --- | -| ![当前贺礼簿](audit-2026-08-23/66-current-relative-records.png)
![当前结构化往来表单](audit-2026-08-23/67-current-relative-create.png) | ![当前家族备忘](audit-2026-08-23/68-current-memos.png)
![当前备忘表单](audit-2026-08-23/69-current-memo-create.png) | ![当前人物详情入口](audit-2026-08-23/72-current-person-detail-lower.png)
![当前成长媒体字段](audit-2026-08-23/75-current-growth-create-media.png) | - -### 继续复核截图 - -| 礼仪列表与详情 | 功德列表与详情 | -| --- | --- | -| ![参考礼仪列表](audit-2026-08-23/34-reference-ceremonies.png)
![参考礼仪详情](audit-2026-08-23/36-reference-ceremony-detail.png) | ![参考功德列表](audit-2026-08-23/40-reference-merits.png)
![参考功德详情](audit-2026-08-23/41-reference-merit-detail.png) | - -| 家族恩人 | 贺礼簿 | 成长具体记录 | -| --- | --- | --- | -| ![参考家族恩人详情](audit-2026-08-23/43-reference-favor-detail.png) | ![参考贺礼簿详情](audit-2026-08-23/45-reference-gift-ledger-detail.png) | ![参考成长记录](audit-2026-08-23/48-reference-growth-entries.png) | - -| 字辈谱/世系谱 | 家族动态 | 创建/加入家谱 | -| --- | --- | --- | -| ![参考字辈谱](audit-2026-08-23/50-reference-generation-poems.png)
![参考世系谱](audit-2026-08-23/51-reference-pedigree.png) | ![参考家族动态](audit-2026-08-23/52-reference-family-genealogy.png) | ![参考创建家谱](audit-2026-08-23/57-reference-create-genealogy.png)
![参考加入家谱](audit-2026-08-23/58-reference-join-genealogy.png) | - -## 截图证据 - -### 视频列表与播放入口 - -| 参考项目 | 当前项目 | -| --- | --- | -| ![参考家族视频列表](audit-2026-08-23/06-reference-videos.png) | ![当前家族视频空状态](audit-2026-08-23/09-current-videos.png) | - -参考卡片点击后确实进入独立播放器,但浏览器中显示黑屏,因此本轮只能确认“封面卡片 → 播放页”的交互,不能声称视频成功播放。 - -### 推广邀请 - -| 参考项目 | 当前项目 | -| --- | --- | -| ![参考二维码分享](audit-2026-08-23/18-reference-referral.png) | ![当前推广中心](audit-2026-08-23/19-current-referral.png) | - -### 重要证件与家谱设置 - -| 参考项目重要证件 | 当前项目家谱设置阻断 | -| --- | --- | -| ![参考重要证件汇总](audit-2026-08-23/24-reference-documents.png) | ![当前设置读取失败](audit-2026-08-23/27-current-settings.png) | - -### VIP 订单 - -| 参考项目 | 当前项目 | -| --- | --- | -| ![参考购买记录](audit-2026-08-23/31-reference-vip-orders.png) | ![当前 VIP 与订单](audit-2026-08-23/30-current-vip.png) | - -## 确定需要补的项目 - -| 优先级 | 差异 | 当前是否已有相近能力 | 责任与完成条件 | -| --- | --- | --- | --- | -| P0 | 家谱设置整体读取失败 | 页面和表单均已有,但基础详情与永久删除资格被同一个 `Promise.all` 绑定 | 前端把删除资格改为非关键独立状态;后端让未归档谱主正常读取详情和 capability 的禁用原因 | -| P0 | 家族视频列表仍直接铺 `