feat(api): 完善家谱系统API客户端契约

- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
This commit is contained in:
fizzleaf
2026-07-29 16:57:27 +08:00
parent 59a72fb22b
commit fb1743aa2a
74 changed files with 13166 additions and 1320 deletions
+498 -51
View File
@@ -156,13 +156,39 @@
| C11 | `completed` 只有 string 类型,无枚举 | 不能决定复选框提交 `0/1`、true/false 或其他值 | Apifox 增加枚举和中文含义 |
| C12 | `feedType``recordType``registerSource` 等无枚举 | 无法判断文本输入还是选择项 | 明确为自由文本,或补全枚举 |
| C13 | 多个日期字段没有 format/时区规则 | 页面值和后端解析可能不一致 | 明确 date 或 date-time,并统一时区 |
| C14 | 只有配额,没有我的家谱/成员入口 | 无法取得 `genealogyId`、成员或受邀用户选项 | 增加 PC 家谱列表/选项和成员选项接口,或明确外部上下文契约 |
| C14 | 已由后端 PC 实链接决 | `GET /genealogy/pc/genealogies/mine``/options` 返回完整 `AppGenealogyVo``genealogyId` 从响应取得 | 前端只从真实列表选择,不允许手填 ID |
| C15 | `bindingMode=SPECIFIED` 需要 `appUserId`,但无业务用户选项接口 | 不能提供合法选择器 | 增加家谱成员/业务用户选项接口 |
| C16 | 邀约的 `inviteeUserIds` 没有选项接口 | 不能让管理员选择受邀人 | 增加同一家谱成员选项接口 |
| C17 | 视频、成长、亲友、备忘、功德、通知响应未展开 | 无法安全展示、编辑或单条操作 | 分别增加资源 View 和列表/详情响应 |
| C18 | 更新接口未说明缺省、null、空串语义 | 可选字段可能无法清空 | Apifox 对每个可清空字段写明更新规则 |
| C19 | 多数 View 没有 `canEdit` / `canDelete` 等权限字段 | 页面无法可靠决定按钮可见性 | 增加能力字段,或提供明确且可实现的角色规则 |
| C20 | 文章、相册、祭祀只有删除操作 | 没有真实 ID 来源和完整资源流程 | 补全 PC 列表/详情/新增/修改后才开放页面 |
| C21 | 家族圈动态只返回 `mediaOssIds`,PC 没有 OSS ID 批量解析/访问 URL 接口 | 已上传图片只能统计数量,不能在列表和详情安全回显缩略图 | PC 增加按 OSS ID 返回授权 URL、文件名和媒体类型的接口,或让动态 View 直接返回媒体对象数组 |
| C22 | 停用动态不会出现在普通列表,普通详情接口也拒绝读取 | 管理员可以停用,但停用后无法从 PC 页面重新读取、恢复或验证最终状态 | 增加包含停用记录的管理列表/详情,或增加独立的状态恢复接口 |
| C23 | `VideoVo` 只返回 `coverOssId` / `videoOssId`,没有 PC 文件访问 URL 或解析接口 | 可以维护视频记录,但不能安全播放视频或回显封面 | 视频 View 直接返回授权 URL,或增加按 OSS ID 获取文件访问地址的 PC 接口 |
| C24 | 视频列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法从页面重新读取和恢复 | 增加视频 management 列表/详情,或增加独立状态恢复接口 |
| C25 | `GrowthRecordVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 成长记录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 |
| C26 | 成长记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加成长记录 management 列表/详情,或增加独立状态恢复接口 |
| C27 | `giftAmount` 只有 `number` / `BigDecimal` 类型,没有精度、范围和正负规则 | 前端不能自行限定两位小数、最大金额或禁止负数 | Apifox 明确 scale、precision、minimum/maximum 和负数业务含义 |
| C28 | `RelativeRecordVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 亲友记录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 |
| C29 | 亲友记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加亲友记录 management 列表/详情,或增加独立状态恢复接口 |
| C30 | `MemoVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 备忘录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 |
| C31 | 备忘录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加备忘录 management 列表/详情,或增加独立状态恢复接口 |
| C32 | 功德金额只有 `number` / `BigDecimal` 类型,没有精度、范围和正负规则 | 前端不能自行限定两位小数、最大金额或禁止负数 | Apifox 明确 scale、precision、minimum/maximum 和负数业务含义 |
| C33 | 功德记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加功德记录 management 列表/详情,或增加独立状态恢复接口 |
| C34 | YAML `NotificationView` 声明了家谱编号/名称、发送人昵称/手机号和业务摘要,但 PC `toVo` 未填充这些字段 | 页面可能长期显示空白,导出契约与真实 PC 响应不一致 | 后端补齐字段,或从 PC Schema 删除不会返回的字段并明确隐私策略 |
| C35 | PC 谱文后端列表支持可选 `categoryId`,YAML 未声明该 query,且没有文章分类选项接口 | 前端无法可靠提供分类筛选或编辑分类 | YAML 补齐 query,并提供当前家谱文章分类选项接口 |
| C36 | `ArticleVo` 只返回 `coverOssId`,没有 PC 文件访问 URL | 谱文可维护封面引用,但不能安全回显封面图片 | Article View 返回封面 URL,或增加 OSS ID 授权解析接口 |
| C37 | 谱文列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读或恢复 | 增加谱文 management 列表/详情,或独立状态恢复接口 |
| C38 | `AlbumVo` / `AlbumPhotoVo` 只返回 `coverOssId` / `ossId`,没有 PC 文件访问 URL | 相册和照片记录可维护,但不能安全回显封面或照片内容 | View 返回授权 URL,或增加 OSS ID 授权解析接口 |
| C39 | 相册和照片列表只返回正常记录,且没有停用记录的 management 入口 | `status=1` 后无法从页面重读、恢复或再次编辑 | 增加相册/照片 management 列表,或独立状态恢复接口 |
| C40 | `CeremonyVo` 只返回 `coverOssId`,没有 PC 文件访问 URL | 活动记录可维护,但不能安全回显封面 | View 返回封面授权 URL,或增加 OSS ID 授权解析接口 |
| C41 | 活动列表和详情只允许读取正常记录 | `status=1` 后无法从页面重读、恢复或再次编辑 | 增加活动 management 列表/详情,或独立状态恢复接口 |
| C42 | `CeremonyGiftBody.giftAmount` 的 YAML 没有精度和最大值,后端实际拒绝负数 | 页面可按后端约束拒绝负数,但不能自行限定小数位或最大金额 | YAML 补充 minimum、scale、precision 和 maximum |
| C43 | YAML 成员更新角色枚举含 `owner/visitor`PC 后端 DTO 只接受 `admin/editor/member` | 按 YAML 展示会产生必然失败的写入 | YAML 收紧枚举;本轮前端按 PC 后端校验冻结为三种可分配角色 |
| C44 | `GenealogyMemberVo` 声明昵称、手机号、家谱和世系人物名称等字段,PC `toVo` 实际只填充 ID、成员名、角色、关系、来源和时间 | 列表部分说明可能长期为空,手机号还有隐私风险 | 后端补齐非敏感展示字段并删除手机号,或从 PC Schema 删除不会返回的字段 |
| C45 | PC 没有世系关系解除接口 | 页面无法合法解除父母、配偶、子女或兄弟姐妹关系 | 后端增加带权限和关系完整性校验的解除接口 |
| C46 | 成员更新只在 `lineagePersonId != null` 时写入,没有解除成员与世系人物绑定的语义 | 下拉留空只能表示“不修改”,不能完成解绑 | 增加明确解绑接口或约定可验证的 null/空值语义 |
当前复核状态:
@@ -175,8 +201,28 @@
| C01/C08 | 前端按后端 PC 实链解决 | 只调用 `/genealogy/pc/region/*`;列表按 `RegionSelectVo``regionCode/regionName/regionLevel` 读取,不保留公共路径 fallback |
| C09 | 前端按后端 PC 实链解决 | `SysOssResumableInitVo` 明确提供 `uploadId/instant/ossId/url/fileName/uploadedChunks`,完成响应使用 `SysOssUploadVo` |
| C10 | 前端边界解决 | 后端初始化 `ossId` 为 Long、完成响应为 string;浏览器统一转十进制字符串,隐藏回填且不转 `Number` |
| C11(备忘录) | 本轮按后端 PC 实链解决 | YAML 仍只有 stringPC `AppMemoServiceImpl` 明确校验 `completed` 只能为 `0/1`,含义为否/是,前端冻结为未完成/已完成 |
| C13 | 后续日期模块仍阻断 | 本阶段上传与区划响应不消费日期;其他模块仍需逐字段核验 Java 日期类型 |
| C17(成长记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcGrowthRecordController` 明确返回 `GrowthRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 |
| C17(亲友记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcRelativeRecordController` 明确返回 `RelativeRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 |
| C17(备忘录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcMemoController` 复用的 PC 支持层明确返回 `MemoVo`,前端只冻结该 PC 响应,不读取 APP 路径 |
| C17(功德记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcMeritRecordController` 复用的 PC 支持层明确返回 `MeritRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 |
| C17(消息通知) | 本轮按 YAML 详情 DTO 和后端 PC 实链解决 | 详情为完整 `NotificationView`;列表导出仍是泛型数组,但 PC 控制器实际返回同一 `NotificationVo`。前端只接受直接数组,不读取 APP 路径 |
| C20(谱文) | 本轮按后端 PC 实链解决 | PC 控制器实际具备列表、详情、新增、修改、删除并返回 `ArticleVo`;页面不再只开放删除 |
| C18 | 阻断 | 未说明可选更新字段的省略、`null`、空串语义 |
| C34 | 阻断非核心展示 | PC 实际未填充 `genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary`;页面兼容空值并始终隐藏手机号,核心通知读取和已读闭环不依赖这些字段 |
| C35 | 阻断分类能力 | 本轮不发送 YAML 未声明的 `categoryId` query,也不提供手填分类 ID |
| C36/C37 | 阻断非核心展示/停用管理 | 本轮不猜封面 URL,并固定提交 `status=0`,避免产生不可重读谱文 |
| C20(相册/照片) | 本轮按后端 PC 实链接决 | PC 控制器具备相册列表、新增、修改、删除和照片列表、新增、删除;页面只从响应取得稳定 ID |
| C38/C39 | 阻断非核心展示/停用管理 | 本轮不猜文件 URL,并固定提交 `status=0`,避免产生不可重读的相册或照片 |
| C20(祭祀活动/祭品) | 本轮按后端 PC 实链接决 | PC 控制器具备活动完整 CRUD 和祭品列表、新增、删除;页面只使用响应中的稳定 ID |
| C16 | 本轮按后端 PC 实链接决 | `members/options` 返回同一家谱成员及真实 `appUserId`;管理员邀约名单只允许选择正常且已绑定账号的成员 |
| C15 | 本轮按后端 PC 实链接决 | `members/options` 是当前家谱真实业务账号的唯一选择源;`SPECIFIED` 只提交正常且已绑定账号成员的 `appUserId` |
| C40/C41 | 阻断非核心展示/停用管理 | 本轮不猜封面 URL,并固定提交 `status=0`,避免产生不可重读活动 |
| C42 | 阻断金额精度/上限 | 前端只执行后端已确认的非负校验,不自行限制小数位和最大金额 |
| C43 | 前端按后端 PC 实链收紧 | 成员角色选择器只允许 `admin/editor/member`;不发送 YAML 中后端拒绝的 `owner/visitor` |
| C44 | 阻断非核心 enrichment | 页面兼容未填充名称,始终隐藏成员、邀请人手机号和内部用户 ID;核心成员管理不依赖这些字段 |
| C45/C46 | 阻断 | 页面不提供世系关系解除,也不把空 `lineagePersonId` 解释为解绑 |
阶段 0 验收标准:
@@ -376,12 +422,13 @@
`-1` 显示为“不限”,不能参与普通减法或显示负数。
配额响应不包含 `genealogyId`,不能替代“我的家谱”。在 C14 完成前
配额响应不包含 `genealogyId`,不能替代“我的家谱”。当前由 `GET /genealogy/pc/genealogies/mine` 返回完整 `AppGenealogyVo`
- 所有家谱内页面缺少真实 `genealogyId` 时必须阻止请求
- 不显示静态家谱卡片作为真实数据
- 不从 APP 接口获取家谱
- 不允许用户手工输入家谱 ID
- `profile-families.html` 从真实列表生成家谱入口
- `genealogyId` 在浏览器边界保持字符串,并由 `profile-common.js` 统一读取和传播
- 所有家谱内页面缺少真实 `genealogyId` 时必须阻止请求并回到家谱选择入口
-显示静态家谱卡片,不从 APP 接口获取家谱,不允许用户手工输入家谱 ID
- 切换家谱通过进入新的带 `genealogyId` URL 完成整页导航,旧页面模块状态不会复用。
### 7.5 行政区划
@@ -455,6 +502,17 @@
4. 点赞、取消、评论、删除成功后以接口返回和重新读取结果校正计数。
5. 删除有回复的评论后保留占位,不从 DOM 直接移除整棵回复。
2026-07-28 家族圈实施状态:
- `ApiClient` 的 14 个家族圈 PC 操作已由契约测试锁定;页面首屏只调用 `/feeds/page`,评论和回复分别调用各自的 `/page`
- 已新增 `profile-feed-detail.html`,以 `genealogyId + feedId` 形成稳定详情深链;列表、详情和编辑链接都保留字符串 ID。
- 列表和详情消费 `AppFamilyFeedVo` 的发布人、点赞状态与计数、评论计数、置顶、状态和时间字段;评论消费 `FamilyFeedCommentVo` 的归属、父评论、层级、删除占位、回复计数和时间字段。
- `GET /genealogy/pc/genealogies/{genealogyId}``canEditContent/canManage` 控制编辑和高级设置,`GET /genealogy/pc/auth/profile``userId` 结合响应中的发布人/评论人 ID 控制本人删除按钮;后端仍执行最终权限校验。
- 发布图片只允许文件选择;`mediaOssIds` 为隐藏的上传派生字段,多文件结果使用英文逗号连接,不允许手填 OSS ID。
- 新增、修改、点赞、取消点赞、评论和删除均有同资源动作锁;成功后重新读取详情、分页列表或评论分页以校正页面状态。
- `status=1` 使用后端定义的“停用”语义,不再显示为“草稿”。C21、C22 未解决前,图片缩略图回显和停用记录恢复仍属于后端阻断,不宣称这两项闭环完成。
- 已在真实 Chrome 登录态验证:当前账号的 `/genealogies/mine` 返回空列表;无家谱上下文时家族圈不发业务请求,所有发布入口统一改写到 `profile-families.html?next=profile-feed-edit.html`。发布表单实测为多文件选择、隐藏 OSS ID、普通用户隐藏且禁用管理字段。由于账号没有家谱,本轮不能进行真实动态列表/详情读取或写操作联调。
### 7.7 字辈谱
`profile-generation.html` 同时提供“单条维护”和“批量维护”。
@@ -486,6 +544,14 @@
普通展示列表使用正常字辈接口;管理页面使用 management 接口以包含停用项。
当前实现状态:
- 页面先读取家谱详情,以 `canEditContent` 决定调用正常列表还是 management 列表;无维护权限时不渲染新增、编辑、停用和批量维护控件,后端继续执行最终权限校验。
- `genealogyId` 只来自家谱上下文,`poemId` 只来自列表响应;所有业务 ID 在浏览器边界保持字符串,不提供手工 ID 输入。
- 单条新增、修改和状态切换成功后重新读取列表;批量维护严格执行 preview → 当前请求体签名确认 → save → 重新读取列表。
- 批量预览逐项校验 `action`、状态、必需的新旧文本和内部 ID,并核对四类汇总计数;勾选 `disableMissing` 时保存确认明确说明只停用后续世代、不删除历史。
- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送字辈业务请求,新增按钮和批量面板隐藏,家谱上下文入口统一返回家谱选择页。当前账号家谱列表为空,因此正常/management 列表、单条写入和批量写入仍缺少真实家谱数据联调证据。
### 7.8 世系人物
全部操作集中在 `profile-tree.html`,详情和编辑可使用抽屉/弹窗,但所有字段都要有明确控件。
@@ -493,7 +559,7 @@
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `bindingMode` | 必填 | S | NONE 不绑定、SELF 当前账号、SPECIFIED 指定账号 |
| `appUserId` | SPECIFIED 时必填 | S/I | 从真实业务用户选项选择;没有接口时禁用 SPECIFIED,禁止文本 ID |
| `appUserId` | SPECIFIED 时必填 | S/I | 从当前家谱 `members/options` 的正常且已绑定账号成员中选择;禁止文本 ID |
| `personNo` | 可选 | U/A | 高级设置可填;留空由服务端生成 |
| `name` | 必填 | U | 姓名 |
| `aliasName` | 可选 | U | 别名/曾用名 |
@@ -543,6 +609,19 @@
`LineagePersonTreeView` 补全前,不得继续依靠多个字段 fallback 猜树结构。
当前实现状态:
- 已接入 PC 世系树、普通列表、分页列表、人物选项、详情、新增、修改、逻辑停用,以及父母、子女、兄弟姐妹、配偶四类关系新增;成功写入后统一重新读取树、列表、分页和选项,返回稳定 `personId` 时再读取详情。
- 页面先读取家谱详情,`canEditContent` 只控制新增、编辑、停用和关系维护;树、列表、详情、筛选和分页保持只读可用,后端继续执行最终查看和编辑权限校验。
- `LineagePersonBody` 的全部字段均有明确来源:世代从正常字辈列表选择并自动回填字辈,父母从世系人物选项选择,头像通过统一上传组件产生隐藏 `avatarOssId`,所有业务 ID 在浏览器边界保持字符串且不可手填。
- 新增和编辑人物开放 `NONE``SELF``SPECIFIED``SPECIFIED` 选择器只消费当前家谱 `members/options`,过滤未绑定账号、停用成员和当前账号,不显示手机号,不允许手填 `appUserId`
- 编辑已绑定其他账号的人物时,用详情响应的 `appUserId` 精确匹配同一成员选项;真实选项不存在时不伪造回填,保存会被前端校验阻止。
- 分页查询已接入 `keyword``generation``personStatus``pageNum/pageSize`;详情展示家谱、绑定账号、父母、配偶、出生、逝世、安葬、人物状态、简介和备注等响应字段。
- DELETE 确认明确使用“停用”语义,并提示存在正常子女时后端会拒绝;页面没有伪造解除关系或物理删除。
- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送世系业务请求,读区域显示选择家谱提示,新增、关系维护和完整表单隐藏,分页与配偶专用字段不误显示,所有家谱链接返回家谱选择页。当前账号家谱列表为空,因此真实树、列表、详情和写操作仍缺少有数据账号联调证据。
- C18 仍适用于本模块:可选字段清空语义未明确,本轮仅提交非空值,不宣称可选字段清空闭环。
- `SPECIFIED` 账号绑定相关聚焦测试 42/42 通过;真实账号页面验证统一留到阶段 6 收尾执行。
### 7.9 视频
计划新增 `profile-video-edit.html``profile-video.html` 负责列表。
@@ -560,7 +639,25 @@
流程:选视频 → 分片上传 → 得到 `videoOssId` → 可选封面上传 → 填标题说明 → 保存 → 重新读取详情。
当前视频列表、详情、新增和修改均返回泛型对象/列表。C17 完成前可以完成静态表单布局和 `ApiClient` 契约测试,但不得把真实 CRUD 页面标记为已完成。
YAML 的视频响应仍使用泛型 `ObjectResult` / `ListResult`,但后端已存在独立 `PcVideoController`,其 PC method/path 与 YAML 一致,并明确返回 `VideoVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `VideoVo` 冻结:
- I`videoId``genealogyId``coverOssId``videoOssId``publisherUserId`
- R`genealogyNo``genealogyName``surname``publisherNickName``publisherPhone``publishTime``viewCount``remark`
- U/R`videoTitle``videoDesc`
- F/A/R`durationSeconds`
- U/S/R`sortOrder`
- A/R`status`,当前固定提交 `0`
当前接入状态:
- `profile-video.html` 已接入正常视频列表、详情读取、权限化编辑/删除入口、空错状态和无家谱上下文分支;
- 新增 `profile-video-edit.html`,新增和修改只提交 `VideoBody` 七个字段;视频与封面都通过统一分片上传产生隐藏 OSS ID,不允许手填;
- 选择视频文件后由浏览器媒体元数据自动读取时长,`durationSeconds` 只读;
- 保存成功后使用稳定 `videoId` 重新读取详情,再返回列表;删除确认明确会释放视频和封面文件引用,删除后重读列表;
- 后端 `BigNumberSerializer` 对超出 JavaScript 安全整数范围的 `Long` 输出字符串,对安全整数输出 number;前端接受两种输入并统一保留为字符串;
- C23 未解决前只显示文件“已上传”,不猜测 OSS URL,不宣称播放或封面预览完成;
- C24 未解决前不开放 `status=1`,避免产生无法重新读取和恢复的停用视频;
- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:视频列表不发业务请求,发布入口及编辑表单保持隐藏,所有上下文链接返回家谱选择页,控制台无错误。当前账号没有家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。
### 7.10 贺礼邀约与祭祀
@@ -583,6 +680,8 @@
“我的邀请”接口有完整响应,可独立实现接受/拒绝流程。管理端替换受邀人依赖 C16,且必须先有真实活动 ID。
当前接入状态:`profile-gift.html` 已使用 `GET /genealogy/pc/genealogies/ceremony-invitations/mine` 展示当前账号的邀请列表和响应内详情;仅 `PENDING` 邀请显示接受/拒绝,并向 `PUT /genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/invitations/me` 提交唯一字段 `inviteStatus`,成功后重读列表。内部 ID 和坐标不直接展示,坐标仅用于生成地图链接。2026-07-29 使用真实账号联调,PC “我的邀请”接口成功返回空数组,页面空状态和鉴权正常;由于当前账号没有待响应邀请,真实接受/拒绝写入仍待有邀请数据时复核。后端仓库中的同组 `AppCeremonyController` 仍以 `/genealogy/app/genealogies` 为基础路径,与冻结 YAML 的 `/genealogy/pc` 不一致,但线上 PC mine 路径已实际可用,前端继续以 YAML 为唯一契约,不回退 APP 路径。
当前没有祭祀活动列表、详情、新增、修改和献礼列表/新增接口。现有 `profile-gift-edit.html` 中的 `ceremonyType``ceremonyTitle``ceremonyTime``location``ceremonyDesc``coverOssId` 不属于任何现有 PC 写入 DTO,必须继续保持禁用,不得向猜测路径提交。
两条删除接口只能在后端补齐列表和详情、产生稳定 ID 后开放:
@@ -605,9 +704,27 @@
| `remindTime` | 可选 | S | 日期时间控件 |
| `mediaOssIds` | 可选 | F/I | 多附件上传 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | S | 0 正常、1 停用 |
| `status` | 可选 | A | 当前固定提交 0 正常;C26 解决前不开放 1 停用 |
列表、详情、新增和修改响应仍是泛型对象。补齐 `GrowthRecordView` 前不得展示原始 JSON,也不得猜 `recordId` 开启编辑和删除。
YAML 的列表、详情、新增和修改响应仍是泛型对象,但后端已存在独立 `PcGrowthRecordController`,其 PC method/path 与 YAML 一致,并明确返回 `GrowthRecordVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `GrowthRecordVo` 冻结:
- I`recordId``genealogyId``appUserId``lineagePersonId``mediaOssIds`
- R`genealogyNo``genealogyName``surname``appUserNickName``appUserPhone``lineagePersonNo``lineagePersonName``remark`
- U/R`recordType``recordTitle``recordContent`
- S/R`recordDate``remindTime`
- U/S/R`sortOrder`
- A/R`status`,当前固定提交 `0`
当前接入状态:
- `profile-growth.html` 已接入当前账号成长记录列表、详情、编辑和删除入口;列表接口不传后端额外的 `all` 参数,只消费当前 YAML 声明的请求;
- `profile-growth-edit.html` 已接入新增和修改;`lineagePersonId` 只从真实世系人物选项接口选择,`recordId` 只来自列表响应或 URL,均按字符串处理且不能手填;
- `recordType` 后端 DTO 和服务均为不带枚举的自由字符串,因此保留可选文本输入;`recordDate` 提交 `yyyy-MM-dd``remindTime``datetime-local` 转为后端 `Date` 接受的 `yyyy-MM-dd HH:mm:ss`
- 多附件通过统一分片上传产生隐藏 `mediaOssIds`,支持保留、追加和清除附件选择,不显示 OSS ID;
- 新增和修改成功后必须使用稳定 `recordId` 重读同一条详情并校验身份,再返回列表;删除成功后重读列表,确认文案明确逻辑删除且会释放附件引用;
- C18 未解决前,标题以外可选文本、人物和日期字段清空不宣称闭环;前端对空可选字段保持省略,附件清除使用后端已明确的空引用处理;
- C25 未解决前只显示附件数量,不猜测 OSS URL、文件名或下载地址;C26 未解决前固定 `status=0`,不产生无法重新读取和恢复的停用记录;
- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情不发成长记录业务请求,编辑表单及页头保存按钮隐藏,入口统一返回家谱选择页。当前账号没有家谱,真实列表、详情、世系选项、上传和 CRUD 写入仍待有数据账号验证。
### 7.12 亲友记录
@@ -619,13 +736,33 @@
| `relationName` | 可选 | U | 关系名称 |
| `eventName` | 可选 | U | 事件名称 |
| `eventTime` | 可选 | S | 日期时间选择器 |
| `giftAmount` | 可选 | U | 金额输入,明确精度和非负规则后校验 |
| `giftAmount` | 可选 | U | 金额输入;只提交 JSON 序列化后数值不变的有限数字,C27 解决前不猜精度、范围或非负规则 |
| `recordContent` | 可选 | U | 内容 |
| `mediaOssIds` | 可选 | F/I | 多附件上传 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | S | 0 正常、1 停用 |
| `status` | 可选 | A | 当前固定提交 0 正常;C29 解决前不开放 1 停用 |
补齐 `RelativeRecordView` 前只完成页面字段布局和请求契约,不开放真实列表、编辑和删除。
YAML 的列表、详情、新增和修改响应仍是泛型对象,但后端已存在独立 `PcRelativeRecordController`,其 PC method/path 与 YAML 一致,并明确返回 `RelativeRecordVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `RelativeRecordVo` 冻结:
- I`relativeId``genealogyId``appUserId``mediaOssIds`
- R`genealogyNo``genealogyName``surname``appUserNickName``appUserPhone``remark`
- U/R`relativeName``relationName``eventName``recordContent`
- S/R`eventTime`
- U/R`giftAmount`,后端 `BigDecimal` 响应按字符串保留;
- U/S/R`sortOrder`
- A/R`status`,当前固定提交 `0`
当前接入状态:
- `profile-relative.html` 已接入当前账号亲友记录列表、详情、编辑和删除入口;列表接口不传后端额外的 `all` 参数,只消费当前 YAML 声明的请求;
- `profile-relative-edit.html` 已接入新增和修改;`relativeId` 只来自列表响应或 URL,按字符串处理且不能手填;
- `eventTime` 使用 `datetime-local`,提交前转为后端 `Date` 接受的 `yyyy-MM-dd HH:mm:ss`,并校验真实月日和时分秒;
- `giftAmount` 请求按 YAML 的 `number` 提交;提交前比较用户十进制输入与 `JSON.stringify(Number(value))` 的精确数值,序列化会改值的超大或高精度金额直接拒绝。后端 `BigDecimal` 响应按原字符串展示,不转回 `Number`
- 多附件通过统一分片上传产生隐藏 `mediaOssIds`,支持保留、追加和清除附件选择,不显示 OSS ID;
- 新增和修改成功后必须使用稳定 `relativeId` 重读同一条详情并校验身份,再返回列表;删除成功后重读列表,确认文案明确逻辑删除且会释放附件引用;
- C18 未解决前,可选文本、时间和金额字段的清空不宣称闭环;附件清除使用后端已明确的空引用处理;
- C27 未解决前不增加两位小数、最大金额或非负限制;C28 未解决前只显示附件数量;C29 未解决前固定 `status=0`,并拒绝编辑停用响应,避免意外恢复;
- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情不发亲友记录业务请求,编辑表单及页头保存按钮隐藏,入口统一返回家谱选择页。当前账号没有家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。
### 7.13 备忘录
@@ -635,13 +772,31 @@
| --- | --- | --- | --- |
| `memoTitle` | 必填 | U | 标题 |
| `memoContent` | 可选 | U | 内容 |
| `remindTime` | 可选 | S | 日期时间 |
| `completed` | 可选 | S | 应为开关;C11 明确提交值前保持禁用 |
| `mediaOssIds` | 可选 | F/I | 多附件上传 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | S | 0 正常、1 停用 |
| `remindTime` | 可选 | S | `datetime-local` 选择,提交为 `yyyy-MM-dd HH:mm:ss` 并校验真实日期 |
| `completed` | 可选 | S | PC 后端已核验:`0` 未完成、`1` 已完成;创建默认 `0` |
| `mediaOssIds` | 可选 | F/I | 多附件上传自动产生;隐藏保存,允许追加和清除,不允许手填 |
| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` |
| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 |
`completed` 表示业务完成状态,`status` 表示记录正常/停用,两者不能合并。补齐 `MemoView` 前不得猜 `memoId` 响应字段
PC `MemoVo` 响应字段及页面用途:
| 字段 | 类型 | 标记 | 页面处理 |
| --- | --- | --- | --- |
| `memoId``genealogyId``appUserId` | Long | I | 仅从 PC 响应取得并按十进制字符串保存;用于详情、编辑、删除和写后重读,不显示、不手填 |
| `genealogyNo``genealogyName``surname` | string | R | 家谱上下文只读信息;当前备忘录页不重复展示 |
| `appUserNickName` | string | R | 详情显示记录人 |
| `appUserPhone` | string | I | 隐私字段,不渲染 |
| `memoTitle``memoContent``remindTime``completed` | string | R | 列表摘要、详情展示和编辑回填 |
| `mediaOssIds` | string | I | 只统计附件数量并回填隐藏上传字段,不暴露原始 OSS ID |
| `sortOrder` | Long | I | 编辑回填;列表顺序由后端负责 |
| `status` | string | I | 仅接受正常记录 `0`;停用响应拒绝进入编辑器 |
| `remark` | string | R | 详情只读展示,MemoBody 不含此字段,因此不提交 |
`completed` 表示业务完成状态,`status` 表示记录正常/停用,两者不能合并。列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/memos` 及其 `/{memoId}` 子路径;列表不发送后端管理专用的 `all` 查询。新增/修改成功后使用稳定 `memoId` 重读同一详情,删除后重读列表。
- C18 未解决前,可选内容和提醒时间的清空不宣称闭环;附件清除使用后端已明确的空引用处理;
- C30 未解决前只显示附件数量;C31 未解决前固定 `status=0`,拒绝编辑停用响应,避免意外恢复;
- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情显示明确提示,编辑表单及页头保存按钮隐藏,控制台无错误。当前账号没有可用家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。
### 7.14 功德记录
@@ -653,12 +808,33 @@
| `meritType` | 可选 | S | donation 捐赠、repair 修祠、public 公益、other 其他 |
| `meritTitle` | 必填 | U | 标题 |
| `meritContent` | 可选 | U | 内容 |
| `amount` | 可选 | U | 金额 |
| `meritTime` | 可选 | S | date-time |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | S | 0 正常、1 停用 |
| `amount` | 可选 | U | YAML `number`;提交前拒绝 JSON 序列化会改变精确数值的输入,不自行限制小数位、范围或正负 |
| `meritTime` | 可选 | S | `datetime-local` 选择,提交为后端示例和 `Date` 接受的 `yyyy-MM-dd HH:mm:ss` |
| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` |
| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 |
现有页面错误地使用了 `service`,但契约枚举中不存在该值;必须删除 `service`,增加 `repair``public`。现有页面还缺少 `sortOrder``status`
PC `MeritRecordVo` 响应字段及页面用途:
| 字段 | 类型 | 标记 | 页面处理 |
| --- | --- | --- | --- |
| `meritId``genealogyId``appUserId` | Long | I | 仅从 PC 响应取得并按十进制字符串保存;用于详情、编辑、删除和写后重读,不显示、不手填 |
| `genealogyNo``genealogyName``surname` | string | R | 家谱上下文只读信息;当前功德页不重复展示 |
| `appUserNickName` | string | R | 详情显示记录人 |
| `appUserPhone` | string | I | 隐私字段,不渲染 |
| `donorName``meritType``meritTitle``meritContent` | string | R | 列表摘要、详情展示和编辑回填;HTML 内容转义后展示 |
| `amount` | BigDecimal | R | 按响应原始字符串展示,不转回 `Number` |
| `meritTime` | Date | R | 列表、详情展示和 `datetime-local` 编辑回填 |
| `sortOrder` | Long | I | 编辑回填;列表顺序由后端负责 |
| `status` | string | I | 仅接受正常记录 `0`;停用响应拒绝进入编辑器 |
| `remark` | string | R | 详情只读展示,`MeritRecordBody` 不含此字段,因此不提交 |
现有页面中的契约外 `service` 已删除,补齐 `repair``public`;由于 `MeritRecordBody` 没有附件字段,功德页不借用其他模块的 `mediaOssIds` 或上传控件。ApiClient 的新增和修改方法再次按 8 个 YAML 字段白名单过滤,调用者额外传入 `appUserId``mediaOssIds` 等字段也不会发送。
- 列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/merit-records` 及其 `/{meritId}` 子路径;
- 新增/修改成功后使用稳定 `meritId` 重读同一详情,删除成功后重读列表;
- C18 未解决前,可选内容、金额和时间的清空不宣称闭环;C32 未解决前不增加两位小数、最大金额或非负限制;C33 未解决前固定 `status=0` 并拒绝编辑停用响应;
- PC 写操作最终由 `assertCanEditContent` 鉴权;当前 View 没有 `canEdit/canDelete`,页面保留操作入口并明确处理 403,不猜角色字段;
- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情显示明确提示,编辑表单及页头保存按钮隐藏,契约外类型和附件控件不存在,控制台无错误。当前账号没有可用家谱,真实列表、详情和 CRUD 写入仍待有数据账号验证。
列表、详情、新增和修改仍是泛型响应。补齐 `MeritRecordView` 后再开放完整 CRUD。
@@ -666,28 +842,276 @@
页面:`profile-messages.html`
| 字段 | 必填性 | 类型 | 页面处理 |
接口冻结:
- `GET /genealogy/pc/notifications`:可选 query `readStatus=0|1`,返回非分页直接数组;全部通知时省略 query。
- `GET /genealogy/pc/notifications/unread-count`:返回非负 int64 未读数量。
- `GET /genealogy/pc/notifications/{notificationId}`:读取当前账号可见的正常通知详情。
- `POST /genealogy/pc/notifications/{notificationId}/read`:将当前账号的一条正常未读通知标记已读。
- `POST /genealogy/pc/notifications/read-all`:将当前账号全部未读通知标记已读。
- 五个接口均由 `ApiClient` 自动携带 Bearer 和 `clientid`;页面不采集 Header。YAML 未声明业务错误码;前端统一处理未登录/401、403、404、业务失败和网络失败。
| 字段 | 必填性 / 类型 | 标记 | 页面用途、来源与提交时机 |
| --- | --- | --- | --- |
| `readStatus` | 可选 query | S | 全部=省略、未读=0、已读=1 |
| `notificationId` | 单条已读必填 path | I/A | 从被点击通知记录带入 |
| 未读数量 `data` | 响应 | R | 顶部徽标,int64 按安全数值/字符串处理 |
| query `readStatus` | 可选 string;枚举 `0/1` | S | 消息页筛选选择;全部=省略、未读=0、已读=1;切换筛选时提交 |
| path `notificationId` | 详情/单条已读必填 int64 | I/A | 从列表响应的稳定十进制字符串取得;点击详情或“标记已读”时自动进入 path,不允许手填或转 `Number` |
| `notificationId` | 响应 int64 | I | 列表行内部关联详情和已读动作;不在可见页面展示 |
| `genealogyId` | 响应 int64,可空 | I | 仅保留为响应内部上下文;不展示、不提交、不允许手填 |
| `genealogyNo` | 响应 string | R | 详情只读候选字段;当前 PC 未填充,不作为功能依赖 |
| `genealogyName` | 响应 string | R | 详情只读展示;当前 PC 可能为空 |
| `senderUserId` | 响应 int64,可空 | I | 内部关联字段;不展示、不提交 |
| `senderNickName` | 响应 string | R | 详情只读展示;当前 PC 可能为空 |
| `senderPhone` | 响应 string | I | 隐私字段,页面始终隐藏 |
| `noticeType` | 响应 string,无枚举/默认值 | R | 列表和详情只读展示;不据此猜测跳转 |
| `noticeTitle` | 响应 string | R | 列表和详情标题,转义后展示 |
| `noticeContent` | 响应 string | R | 列表摘要和详情正文,转义后展示 |
| `bizType` | 响应 string,无枚举/默认值 | R | 详情只读展示;不据此拼业务 URL |
| `bizId` | 响应 int64,可空 | I | 内部业务关联字段;PC 未提供目标路由契约前不展示、不跳转 |
| `bizSummary` | 响应 string | R | 详情只读展示;当前 PC 可能为空 |
| `publishTime` | 响应 string `date-time` | R | 列表和详情只读展示,不由页面提交 |
| `readStatus` | 响应 string;枚举 `0/1` | R | 0=未读、1=已读;仅正常未读记录显示单条已读按钮 |
| `readTime` | 响应 string `date-time`,可空 | R | 详情只读展示,未读时为空 |
| `status` | 响应 string;枚举 `0/1` | I | 仅用于确认正常状态 `0` 才能执行单条已读,不提供页面编辑 |
| `remark` | 响应 string | R | 详情只读展示 |
| 未读数量 `data` | 响应 int64 | R | 顶部计数;只接受非负安全整数 |
通知列表当前是 `ListResult<object>`,没有通知 ID、标题、正文、类型、时间、已读状态和目标深链。补齐 `NotificationView`
响应和权限边界
- 可以调用未读数量和全部已读;
- 不得展示原始对象;
- 不得猜 `notificationId` 开放单条已读;
- 不得根据未声明字段拼详情跳转
- YAML 的详情对象没有声明字段级 `required`、长度、范围或默认值;页面不补造限制,并兼容可选展示字段为空。
- PC 后端按当前 `APP_USER` 登录账号隔离通知,只返回 `status=0` 的正常通知;列表按接收记录倒序,非分页。
- 详情和单条已读必须命中当前账号的接收记录;重复单条已读保持已读状态,页面仍做写操作防重复。
- C34 未解决前,`genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary` 不能被视为稳定可用;手机号即使后端补齐也不展示
- 页面不展示原始 JSON,不使用 APP 接口,不开放删除,不根据 `bizType/bizId` 猜业务深链。
- 2026-07-29 已在真实登录 Chrome 验证当前账号:未读数为 0,全部和仅未读筛选均成功同步,空列表/空详情状态正确。账号暂无通知,因此有数据详情和单条/全部已读真实写入仍待有数据时复核。
### 7.16 当前只有删除能力的模块
### 7.16 谱文
| 模块 | 当前操作 | 页面策略 |
页面:`profile-article.html``profile-article-edit.html`
| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `categoryId` | 可选 int64 | I/S | C35 未解决前省略;没有真实分类选项时不提供输入框 |
| `articleTitle` | 必填 string | U | 编辑页标题;保存时提交 |
| `articleSummary` | 可选 string | U | 编辑页摘要;非空时提交 |
| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 |
| `articleContent` | 必填 string | U | wangEditor 正文;同步到 textarea 后提交 |
| `authorName` | 可选 string | U | 编辑页落款;非空时提交 |
| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 |
| `status` | 可选 string `0/1` | A/I | C37 未解决前固定隐藏提交 `0` |
| `genealogyId``articleId` | path int64 | I/A | 家谱上下文和列表/详情响应;按十进制字符串处理,不显示、不手填 |
PC `ArticleVo` 响应字段及页面用途:
| 字段 | 类型 | 标记 | 页面处理 |
| --- | --- | --- | --- |
| `articleId``genealogyId` | Long | I | 列表、详情、编辑、删除和写后重读的稳定 ID |
| `genealogyNo``genealogyName``surname` | string | R | 家谱只读上下文 |
| `categoryId` | Long,可空 | I | 仅内部保留;无分类选项源时不回填为可编辑 ID |
| `categoryName``categoryCode` | string | R | 详情分类只读展示 |
| `articleTitle``articleSummary``articleContent``authorName` | string | R/U | 列表、详情展示和编辑回填;可见内容均转义 |
| `coverOssId` | Long,可空 | I | 编辑隐藏回填;C36 未解决前不拼封面 URL |
| `publishTime``viewCount` | Date/Long | R | 详情发布时间和浏览量 |
| `sortOrder` | Long | I/U | 编辑回填;列表顺序由后端负责 |
| `status` | string `0/1` | I | 只允许正常记录进入编辑器 |
| `remark` | string | R | 详情只读展示,不进入 ArticleBody |
接入边界:
- 列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/articles` 及其 `/{articleId}` 子路径;
- YAML 响应仍为泛型包装,但 PC 控制器明确返回 `ArticleVo`,前端严格校验该 VO,不读取 APP 路径;
- 新增/修改成功后使用稳定 `articleId` 重读同一详情;删除后重读列表;所有写操作防重复;
- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示新增、编辑和删除;后端继续执行最终权限校验;
- C35 未解决前不开放分类筛选/编辑;C36 未解决前不猜封面地址;C37 未解决前不开放停用;
- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。
### 7.17 相册与照片
页面:`profile-album.html``profile-album-edit.html``profile-album-detail.html`
相册请求字段:
| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `albumName` | 必填 string | U | 编辑页相册名称;保存时提交 |
| `albumDesc` | 可选 string | U | 编辑页描述;非空时提交 |
| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 |
| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 |
| `status` | 可选 string `0/1` | A/I | C39 未解决前固定隐藏提交 `0` |
| `genealogyId``albumId` | path int64 | I/A | 家谱上下文及相册响应;按十进制字符串处理,不显示、不手填 |
照片请求字段:
| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `ossId` | 必填 string/int64 | F/I | 选择照片并完成统一上传后自动产生;隐藏保存,禁止手填 |
| `photoTitle``photoDesc``photographer` | 可选 string | U | 照片表单;非空时提交 |
| `shootTime` | 可选 date | S | 日期选择器;非空时提交 `YYYY-MM-DD` |
| `sortOrder` | 可选 int64 | U/S | 照片表单安全整数;非空时提交 |
| `status` | 可选 string `0/1` | A/I | C39 未解决前固定隐藏提交 `0` |
| `albumId``photoId` | path int64 | I/A | 相册和照片响应;按十进制字符串处理,不显示、不手填 |
PC 响应字段及页面用途:
| 对象 | 字段 | 标记 | 页面处理 |
| --- | --- | --- | --- |
| `AlbumVo` | `albumId``genealogyId` | I | 列表、编辑、详情、删除和写后重读的稳定 ID |
| `AlbumVo` | `genealogyNo``genealogyName``surname` | R | 家谱只读上下文 |
| `AlbumVo` | `albumName``albumDesc` | R/U | 列表/详情展示和编辑回填;可见内容均转义 |
| `AlbumVo` | `coverOssId` | F/I | 编辑隐藏回填;C38 未解决前不拼封面 URL |
| `AlbumVo` | `photoCount` | R | 列表和详情照片数量 |
| `AlbumVo` | `sortOrder``status` | I/U | 编辑回填;只允许正常记录进入编辑器 |
| `AlbumVo` | `remark` | R | 详情只读展示,不进入请求 Body |
| `AlbumPhotoVo` | `photoId``genealogyId``albumId` | I | 照片删除和写后重读的稳定 ID |
| `AlbumPhotoVo` | `genealogyNo``genealogyName``surname``albumName` | R | 家谱与相册只读上下文 |
| `AlbumPhotoVo` | `ossId` | F/I | 文件引用;C38 未解决前不拼照片 URL |
| `AlbumPhotoVo` | `photoTitle``photoDesc``photographer``shootTime` | R/U | 照片列表展示;可见内容均转义 |
| `AlbumPhotoVo` | `sortOrder``status` | I | 列表顺序和正常状态校验 |
| `AlbumPhotoVo` | `remark` | R | 只读展示,不进入请求 Body |
接入边界:
- 相册只调用 `/genealogy/pc/genealogies/{genealogyId}/albums` 及其 `/{albumId}` 子路径;照片只调用该相册下的 `/photos``/{photoId}`
- 后端没有单独的相册详情接口,详情和编辑页必须重读相册列表并用响应中的稳定 `albumId` 精确匹配;
- YAML 响应仍为泛型包装,前端只接受后端 PC 控制器实际返回的 `AlbumVo` / `AlbumPhotoVo` 直接数组,不读取 APP 路径;
- 新增/修改相册后重读相册列表;新增/删除照片后重读照片列表;所有写操作防重复;
- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示新增、编辑、删除和照片维护;
- C38 未解决前不猜文件地址;C39 未解决前不开放停用;
- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。
### 7.18 祭祀活动、祭品与管理员受邀名单
页面:`profile-ceremony.html``profile-gift-edit.html``profile-ceremony-detail.html`;当前账号收到的邀请继续由 `profile-gift.html` 负责。
活动请求字段:
| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `ceremonyType` | 必填 string,无枚举 | U | 编辑页活动类型;保存时提交,不借用其他端枚举 |
| `ceremonyTitle` | 必填 string | U | 编辑页标题;保存时提交 |
| `ceremonyDesc` | 可选 string | U | 编辑页说明;非空时提交 |
| `ceremonyTime` | 可选 date-time | S | 日期时间选择器;转换为后端 Date 可解析格式后提交 |
| `location` | 可选 string | U | 编辑页地点名称;非空时提交 |
| `locationAddress` | 可选 string,最长 300 | U | 编辑页详细地址;非空时提交 |
| `longitude` | 可选 number`[-180, 180]` | U/S | 与纬度同时填写并保存 |
| `latitude` | 可选 number`[-90, 90]` | U/S | 与经度同时填写并保存 |
| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 |
| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 |
| `status` | 可选 string `0/1` | A/I | C41 未解决前固定隐藏提交 `0` |
| `genealogyId``ceremonyId` | path int64 | I/A | 家谱上下文和活动响应;按十进制字符串处理,不显示、不手填 |
祭品与受邀名单请求字段:
| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `giverName` | 可选 string | U | 祭品表单赠送人姓名;非空时提交 |
| `giftAmount` | 必填 number,后端要求非负 | U | 祭品表单金额;添加祭品时提交;C42 未解决前不限制精度和最大值 |
| `giftMessage` | 可选 string | U | 祭品留言;非空时提交 |
| `inviteeUserIds` | 必填、唯一 int64 数组,可为空 | S/I | 从 `members/options` 的正常且已绑定账号成员多选产生;更新名单时提交完整数组,禁止手填 |
| `giftId``invitationId``inviteeUserId` | path/response int64 | I/A | 只从 PC 响应取得;用于删除、名单匹配和写后重读,不显示原值 |
PC 响应字段及页面用途:
| 对象 | 字段 | 标记 | 页面处理 |
| --- | --- | --- | --- |
| `CeremonyVo` | `ceremonyId``genealogyId``sponsorUserId` | I | 活动 CRUD、详情和写后重读的稳定 ID |
| `CeremonyVo` | `genealogyNo``genealogyName``surname` | R | 家谱只读上下文 |
| `CeremonyVo` | `sponsorNickName` | R | 发起人只读展示 |
| `CeremonyVo` | `sponsorPhone` | I | 隐私字段,始终不展示 |
| `CeremonyVo` | `ceremonyType``ceremonyTitle``ceremonyDesc``ceremonyTime``location``locationAddress` | R/U | 列表/详情展示和编辑回填;可见内容均转义 |
| `CeremonyVo` | `longitude``latitude` | I/S | 仅用于生成安全地图链接,不直接展示数值 |
| `CeremonyVo` | `coverOssId` | F/I | 编辑隐藏回填;C40 未解决前不拼封面 URL |
| `CeremonyVo` | `giftCount``giftAmount` | R | 详情祭品数量与礼金合计 |
| `CeremonyVo` | `sortOrder``status` | I/U | 编辑回填;只允许正常记录进入编辑器 |
| `CeremonyVo` | `remark` | R | 详情只读展示,不进入请求 Body |
| `CeremonyGiftVo` | `giftId``genealogyId``ceremonyId``giverUserId` | I | 祭品删除、关联和写后重读的稳定 ID |
| `CeremonyGiftVo` | `genealogyNo``genealogyName``surname``ceremonyTitle` | R | 家谱与活动只读上下文 |
| `CeremonyGiftVo` | `giverNickName``giverName``giftAmount``giftMessage``giftTime` | R | 祭品列表展示;可见内容均转义 |
| `CeremonyGiftVo` | `giverPhone` | I | 隐私字段,始终不展示 |
| `CeremonyGiftVo` | `status``remark` | R/I | 正常状态校验和只读备注 |
| `CeremonyInvitationVo` | 全部 ID、版本 | I | 邀请状态匹配和更新后的重读校验,不显示原值 |
| `CeremonyInvitationVo` | `inviteStatus`、投递/阅读/响应时间 | R | 管理员邀请列表只读展示 |
| `GenealogyMemberVo` 选项 | `appUserId` | I/S | 受邀人的唯一真实业务用户 ID 来源 |
| `GenealogyMemberVo` 选项 | `memberName``appUserNickName` | R/S | 受邀人选择器标签;手机号不展示 |
接入边界:
- 活动只调用 `/genealogy/pc/genealogies/{genealogyId}/ceremonies``/{ceremonyId}`;祭品只调用其 `/gifts` 子路径;
- 管理员邀请列表和替换只调用同一活动下的 `/invitations``/invitees`,受邀人来源只调用当前家谱 `/members/options`
- 新增/修改活动后重读同一详情,新增/删除祭品后重读祭品列表,更新受邀人后重读邀请列表;所有写操作防重复;
- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示活动、祭品和受邀名单维护;
- 空数组允许取消全部尚未响应邀请;响应过的历史邀请由后端保留状态,前端不伪造删除;
- C40 未解决前不猜封面地址;C41 未解决前不开放停用;C42 未解决前不猜金额精度和上限;
- 相关聚焦测试 56/56 通过;真实账号页面验证统一留到阶段 6 收尾执行。
### 7.19 家谱成员管理、退出与谱主转移
页面:`profile-family-admin.html`
成员修改请求字段:
| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `memberName` | 可选 string,最长 50 | U | 管理员编辑表单;非空时提交 |
| `relationName` | 可选 string,最长 100 | U | 管理员编辑表单;后端明确接收空串时可清空 |
| `roleType` | 可选 string`admin/editor/member` | S | 按当前管理者和目标角色过滤;保存时提交,不发送 `owner/visitor` |
| `lineagePersonId` | 可选 int64 | S/I | 从当前家谱世系人物选项选择;非空时提交,留空表示不修改而不是解绑 |
| `genealogyId``memberId` | path int64 | I/A | 家谱上下文和成员列表响应;按字符串处理,不显示、不手填 |
退出与谱主转移:
| 字段/操作 | 必填性 / 类型 | 标记 | 页面来源与提交时机 |
| --- | --- | --- | --- |
| `DELETE /members/me` | 无 Body | S/A | 当前正常成员二次确认后提交;谱主不显示退出按钮 |
| `targetMemberId` | 必填 int64 | S/I | 谱主从当前正常成员列表选择新谱主;禁止选择自己和手填 ID |
| `DELETE /members/{memberId}` | path int64 | S/I | 管理员从真实成员行执行“移出家谱”;谱主不可被移出 |
PC `GenealogyMemberVo` 响应字段及页面用途:
| 字段 | 标记 | 页面处理 |
| --- | --- | --- |
| 内容文章 | 删除文章 | `profile-article*` 保持设计;没有列表/详情时不开放删除 |
| 相册 | 删除相册、删除照片 | `profile-album.html` 保持设计;不使用静态 ID 调删除 |
| 祭祀 | 删除活动、删除祭品 | 活动 CRUD 和礼物列表补齐前保持禁用 |
| `memberId``genealogyId``appUserId``lineagePersonId``inviterUserId` | I | 当前成员匹配、修改、移出、转让和世系绑定的稳定 ID;不显示原值 |
| `genealogyNo``genealogyName``surname` | R | C44 未解决前允许为空的家谱只读上下文 |
| `appUserNickName``lineagePersonNo``lineagePersonName``memberName` | R/U | 成员列表标签和编辑回填;可见内容均转义 |
| `appUserPhone``inviterPhone` | I | 隐私字段,始终不展示 |
| `roleType` | R/S | 响应可含 `owner/admin/editor/member/visitor`;修改只提交三种后端可分配角色 |
| `relationName` | R/U | 列表展示和编辑回填 |
| `joinSource``inviterNickName``joinTime` | R | 加入来源只读说明;C44 未填充时允许为空 |
| `status` | I | 列表只接受后端返回的正常状态 `0` |
删除按钮必须由真实响应中的 ID 和权限字段产生。禁止用 DOM 序号、示例 ID 或 URL 猜测资源 ID。
权限与状态边界:
- 所有账号可按家谱查看权限读取正常成员;只有家谱详情 `canManage=true` 时显示修改和移出;
- 谱主不能直接修改或移出;只有谱主可管理管理员、分配 `admin` 或转让谱主;管理员只能管理非管理员成员并分配 `editor/member`
- 普通成员、编辑和管理员可退出家谱;谱主必须先转让谱主。退出和移出只停用成员关系,不删除世系人物;
- 更新后重读成员列表并核对同一 `memberId`;移出后核对该成员不再出现在正常列表;谱主转移后重读家谱详情和成员列表;
- 页面没有直接新增成员接口;新增成员必须通过后端已有的加入/邀请业务闭环;
- C44 未解决前不依赖 enrichment 字段并隐藏手机号;C45/C46 未解决前不伪造关系解除或成员-世系人物解绑;
- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。
### 7.20 家谱创建与加入申请契约
页面:`profile-create-family.html`;加入申请和审核页面留到下一批开放。
`AppGenealogyCreateBody`
| 字段 | 类型 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `genealogyName` | string | 是 | U | 创建页谱名;提交时发送 |
| `surname` | string | 是 | U | 创建页姓氏;提交时发送 |
| `regionCode` | string | 是 | S | 省市区选择器自动取最后一级;提交时发送 |
| `ancestralHall``originPlace``addressDetail``intro` | string | 否 | U | 创建页可选输入;非空时发送 |
| `coverOssId` | int64/string | 否 | F | 封面上传成功后自动产生;不允许手填 |
| `visibility` | string enum `0/1/2` | 否 | S | 私密/公开/成员可见 |
| `joinMode` | string enum `0/1/2` | 否 | S | 关闭/审核/邀请模式 |
实现边界:
- 创建只调用 `POST /genealogy/pc/genealogies`,提交前读取 `GET /genealogy/pc/genealogies/quota`
- 地区选项复用 `/genealogy/pc/region/children?parentCode=...`,不手写地区编码;
- 创建成功后使用响应中的稳定 `genealogyId` 重读同一家谱,匹配后进入家谱主页;
- ApiClient 已同时冻结加入申请、我的申请、待审核、审核和撤销的 PC path/body 白名单,但本批未提前开放加入 UI;
- `inviterUserId` 没有安全 PC 邀请来源,本轮加入申请契约不发送该字段;
- 创建页面无家谱 ID、申请 ID、用户 ID 或 OSS ID 文本输入,所有写操作防重复。
## 8. 分阶段执行顺序
@@ -769,6 +1193,8 @@
3. 世系人物;
4. 我的贺礼邀请。
当前进度:阶段 4 已按顺序完成。家族圈的列表、正常状态详情、新增、修改、删除、点赞、评论、回复、权限显隐、空/错状态、防重复和写后重读已接入,C21 图片 URL 回显和 C22 停用记录管理继续阻断;字辈谱的正常/management 权限分流、单条新增修改和状态切换、批量预览保存、响应校验、防重复、写后重读及无家谱上下文分支已接入;世系人物的树、列表、分页、详情、完整字段表单、权限显隐、四类关系新增、逻辑停用、防重复和写后重读已接入,C15 继续阻断新增 `SPECIFIED` 账号绑定,C18 继续阻断可选字段清空闭环;我的贺礼邀请已接入当前账号列表、响应内详情、PENDING 接受/拒绝、长 ID 校验、权限/空错状态、防重复和写后重读,C16 继续阻断管理员替换受邀人。真实账号已验证邀请读取和空状态;该账号没有待响应邀请,接受/拒绝真实写入仍待有数据时复核。下一阶段为阶段 5,按顺序先核对视频 DTO 是否已完整。
每个模块采用同一闭环:
1. `ApiClient` 方法和契约测试;
@@ -792,19 +1218,36 @@
任何模块的 View/List DTO 未补齐时,只完成表单布局和契约测试,不宣称真实功能完成。
### 阶段 6:后端补齐后再开放的模块
当前进度:阶段 5 已按顺序完成。视频、成长记录、亲友记录、备忘录、功德记录和消息通知均已按对应 PC 契约开放;各模块的文件 URL、停用记录、可选字段清空和金额业务边界仍按 C18、C23–C34 保持阻断,不通过 APP 接口或猜测字段补齐。
范围:
### 阶段 6:当前 PC 契约可闭环模块
- 文章;
- 相册/照片;
- 祭祀活动/祭品;
- 管理员邀约名单;
- 指定账号绑定世系人物;
- 家谱和成员管理;
- 世系关系解除。
已完成:
这些模块不允许通过 APP 接口或猜测路径提前实现。
- 谱文列表、详情、新增、修改和删除;
- 相册列表、新增、修改、删除,以及照片列表、新增和删除;
- 祭祀活动列表、详情、新增、修改和删除,祭品列表、新增和删除;
- 管理员受邀名单读取和完整替换,当前账号邀请读取与响应;
- 从同家谱正常成员选项中选择账号完成 `SPECIFIED` 世系人物绑定;
- 家谱成员列表、修改、移出、当前成员退出和谱主转移;
- 个人中心、家谱首页和家谱内容页的稳定入口。
契约收口:
- `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)` 只接收真实成员选项产生的 ID 数组,`utils/ApiClient.js` 唯一封装 `{ inviteeUserIds }`
- `/members/options` 严格按 YAML 不发送 Query;后端存在但 YAML 未定义的 `keyword` 不进入前端契约;
- 成员角色构造边界只接受 `admin/editor/member``owner/visitor` 在请求构造前失败;
- 所有业务 ID 来自 URL、上下文或响应选项,页面不提供手填 ID/OSS ID。
仍阻断:
- C35–C46 中记录的分类选项、文件 URL、停用记录、金额业务精度/上限、响应 enrichment 与解绑契约;
- 世系关系解除没有 PC 接口;成员移出/退出不会伪造成世系关系解除或成员与世系人物解绑;
- 真实账号 `19181970173` 已验证登录和无家谱上下文分支:阶段 6 页面均阻止业务请求、隐藏管理操作并返回家谱选择页,浏览器控制台无错误。该账号当前无家谱数据,因此列表有数据态、详情以及真实写操作仍待有数据账号复核,未制造家谱或业务记录。
### 阶段 7:剩余 PC 页面分批开放
首批已完成家谱生命周期 ApiClient 契约和创建家谱页面:额度、三级地区、封面上传派生、严格 DTO 校验、防重复与创建后重读均已接入。加入申请/审核、反馈/工单、VIP 和家谱主页按独立后续批次实施;成员邀请、分享和资料完善提醒没有 PC Controller,继续阻断。
## 9. 文件级施工建议
@@ -822,9 +1265,13 @@
| `public/js/auth-pages.js` / `security-pages.js` | 认证业务流程 |
| `public/js/feed-pages.js` | 家族圈 |
| `public/js/generation-pages.js` | 字辈 |
| `public/js/lineage-pages.js` | 世系 |
| `public/js/lineage-pages.js` | 世系与指定账号绑定 |
| `public/js/video-pages.js` | 视频列表、详情、编辑、上传元数据和 CRUD |
| `public/js/growth-pages.js``relative-pages.js``memo-pages.js` | 对应族务记录 |
| 新增专用视频/功德/邀约脚本 | 仅在 DTO 完整后新增 |
| `public/js/ceremony-pages.js` | 当前用户贺礼邀请列表、响应内详情和接受/拒绝 |
| `public/js/article-pages.js``album-pages.js` | 谱文、相册和照片 |
| `public/js/ceremony-admin-pages.js` | 祭祀活动、祭品与管理员邀约 |
| `public/js/member-admin-pages.js` | 成员修改、移出、退出和谱主转移 |
不得创建一个包揽所有业务的巨型页面脚本,也不得为了单一页面引入通用框架。