feat(api): 添加帮助中心反馈系统和VIP服务功能

- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、
  siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法
- 添加帮助文章和站点资讯的参数验证逻辑
- 更新测试文件添加新的API方法测试用例
- 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用
- 更新加入家谱页面为完整的申请流程界面
- 修改资讯详情页面为站点资讯展示页面
- 更新AxiosRequestUtil中认证处理逻辑
- 添加世系树渲染的HTML生成函数用于页面复用
- 更新文档中的API契约说明和页面规划
This commit is contained in:
fizzleaf
2026-07-30 16:04:48 +08:00
parent fb1743aa2a
commit 309598bfa6
53 changed files with 6740 additions and 282 deletions
+318 -4
View File
@@ -1090,7 +1090,7 @@ PC `GenealogyMemberVo` 响应字段及页面用途:
### 7.20 家谱创建与加入申请契约
页面:`profile-create-family.html`;加入申请和审核页面留到下一批开放
页面:`profile-create-family.html``join-genealogy.html``profile-join-family.html``profile-join-review.html`
`AppGenealogyCreateBody`
@@ -1109,9 +1109,323 @@ PC `GenealogyMemberVo` 响应字段及页面用途:
- 创建只调用 `POST /genealogy/pc/genealogies`,提交前读取 `GET /genealogy/pc/genealogies/quota`
- 地区选项复用 `/genealogy/pc/region/children?parentCode=...`,不手写地区编码;
- 创建成功后使用响应中的稳定 `genealogyId` 重读同一家谱,匹配后进入家谱主页;
- ApiClient 已同时冻结加入申请、我的申请、待审核、审核和撤销的 PC path/body 白名单,但本批未提前开放加入 UI
- 申请页先从 `GET /genealogy/pc/genealogies/options` 选择响应中的家谱,再调用 `POST /genealogy/pc/genealogies/{genealogyId}/join-applies`
- 我的申请页调用 `GET /genealogy/pc/genealogies/join-applies/mine`;只有 `status=0` 的响应项可以调用 `DELETE /genealogy/pc/genealogies/join-applies/{applyId}`
- 审核页只从当前家谱上下文取得 `genealogyId`,先读取家谱详情并要求 `canManage=true`,再读取 `GET /genealogy/pc/genealogies/{genealogyId}/join-applies/pending`
- 审核只调用 `PUT /genealogy/pc/genealogies/{genealogyId}/join-applies/{applyId}/audit``status=1` 通过、`status=2` 拒绝;
- `inviterUserId` 没有安全 PC 邀请来源,本轮加入申请契约不发送该字段;
- 创建页面无家谱 ID、申请 ID、用户 ID 或 OSS ID 文本输入,所有写操作防重复。
- 创建/申请/审核页面无家谱 ID、申请 ID、用户 ID 或 OSS ID 文本输入,所有写操作防重复并在成功后重读对应资源
`AppGenealogyJoinApplyBody`
| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `applicantName` | string,最长 50 | 否 | U | 申请页姓名;非空时提交 |
| `phone` | string,最长 30 | 否 | U | 申请页联系电话;非空时提交 |
| `relationDesc` | string,最长 100 | 否 | U | 申请页关系说明;非空时提交 |
| `applyReason` | string,最长 500 | 否 | U | 申请页申请理由;非空时提交 |
| `inviterUserId` | int64/string | 邀请模式条件必填 | I | PC 无安全来源,不展示、不发送;邀请模式继续阻断 |
`GenealogyJoinApplyVo` 与审核:
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `applyId``genealogyId` | int64/string | R/I | 响应按钮属性和写入 path;不显示、不手填 |
| `genealogyName``surname` | string | R | 我的申请列表只读展示 |
| `applicantName``phone``relationDesc``applyReason` | string | R | 管理员待审核列表;只展示申请人主动提交的 `phone` |
| `auditTime``auditRemark` | datetime/string | R | 我的申请审核结果展示 |
| `status` | string `0/1/2/3` | R | 待审核/通过/拒绝/撤销;仅 `0` 可撤销 |
| `appUserId``inviterUserId``auditUserId` | int64/string | I | 普通列表和审核列表均不展示 |
| `appUserPhone``inviterPhone``auditPhone` | string | I | 账号/邀请/审核人员隐私字段均不展示 |
| 审核 `status` | string `1/2` | S | 管理员点击通过或拒绝时提交 |
| 审核 `auditRemark` | string,最长 500 | U | 拒绝时可选填写,非空时提交 |
### 7.21 意见反馈与工单契约
页面:`profile-feedback.html``submit-ticket.html``my-tickets.html``ticket-detail.html`
后端只提供统一反馈集合:
- `POST /genealogy/pc/feedback`:当前业务用户提交反馈;
- `GET /genealogy/pc/feedback`:读取当前业务用户自己的反馈列表;
- 没有独立 ticket path;“工单”只是帮助中心对同一反馈记录的页面名称;
- 没有用户侧回复、追问、关闭或删除接口,页面不制造这些操作。
`AppFeedbackBody`
| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `feedbackType` | string enum `advice/bug/complaint/other` | 否 | S | 两个提交页选择;空值省略并由后端默认 `advice` |
| `feedbackContent` | string,非空 | 是 | U | 问题或建议正文;提交时发送 |
| `contactInfo` | string | 否 | U | 用户主动提供的联系方式;非空时发送 |
| `feedbackTitle` | — | — | V | 旧页面字段不属于 DTO,已从表单移除且不发送 |
`FeedbackVo`
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `feedbackId` | int64/string | R/I | 提交后重读匹配、列表详情链接和 URL;不展示、不手填 |
| `feedbackType``feedbackContent``contactInfo` | string | R | 列表或详情只读展示 |
| `handleStatus` | string `0/1/2/3` | R | 待处理/处理中/已处理/已关闭 |
| `handleResult``handleTime``remark` | string/datetime | R | 后端存在时在详情展示 |
| `status` | string `0/1` | I | 响应有效性校验,不提供用户修改入口 |
| `appUserId``handlerId` | int64/string | I | 内部用户/处理人编号,不展示 |
| `appUserNickName``appUserPhone` | string | I | 后台 enrichment,不在用户页面展示 |
实现边界:
- ApiClient 是 GET/POST path 和三字段 body 白名单唯一 owner
- 提交使用防重复锁;成功响应必须包含稳定字符串 `feedbackId`
- 提交后重读我的反馈列表并精确找到同一 `feedbackId`,否则不宣称成功;
- 工单详情从 URL 读取列表响应产生的 `feedbackId`,只在我的列表中精确匹配,不回退第一条;
- 401 清理登录态;403 保留登录态并显示后端权限错误;页面不展示原始 JSON。
### 7.22 VIP 套餐与会员订单契约
页面:`profile-services.html`
PC 端只开放三条业务用户接口:
- `GET /genealogy/pc/vip/packages`:读取可购买套餐,成功响应 `List<VipPackageVo>`,无分页结构;
- `POST /genealogy/pc/vip/orders`:创建当前业务用户的会员订单,body 为 `AppVipOrderBody`,成功响应 `VipOrderVo`
- `GET /genealogy/pc/vip/orders`:读取当前业务用户的订单,成功响应 `List<VipOrderVo>`,无分页结构;
- 三条接口均要求 APP_USER 登录态;创建接口有后端防重复提交;
- PC Controller 没有支付发起、支付回调、取消、关闭或退款接口,页面不得制造这些动作。
`AppVipOrderBody`
| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `packageId` | int64,稳定字符串 ID | 是 | S/I | 用户点击套餐响应生成的卡片时选择;创建订单时发送,禁止手填 |
| `genealogyId` | int64,稳定字符串 ID | 否 | S/I | 用户从“我的家谱”响应生成的下拉项中选择;非空时创建订单发送,禁止手填 |
| `payType` | string;后端空值默认 `wechat` | 否 | A | PC 页面不提供支付方式选择且默认省略;等待后端开放真实支付能力后再确认枚举 |
`VipPackageVo`
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `packageId` | int64/string | R/I | 套餐卡片选择和订单 body;不展示、不手填 |
| `packageName``packageDesc` | string | R | 套餐名称和说明 |
| `packageType` | string `vip/storage` | R | 显示为会员套餐/存储扩容;未知值整条过滤 |
| `price``originalPrice` | decimal,非负 | R | 价格展示;不参与前端金额计算 |
| `durationValue` | integer,非负,可空 | R | 与期限单位组合展示 |
| `durationUnit` | string `permanent/day/month/year` | R | 永久/天/月/年 |
| `genealogyLimit``memberLimit``storageLimitMb` | integer,非负,可空 | R | 套餐额度只读展示 |
| `featureJson` | string/JSON | I | 契约未说明用户侧展示结构,当前不解析、不展示 |
| `sortOrder` | integer | I | 后端列表顺序 owner,前端不重新排序 |
| `status` | string `0/1` | I | 只接受正常 `0` 套餐;不提供修改入口 |
| `remark` | string | R | 非空时作为套餐补充说明展示 |
`VipOrderVo`
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `orderId` | int64/string | R/I | 创建后重读精确匹配;不展示、不手填 |
| `orderNo` | string | R | 订单列表展示 |
| `packageId` | int64/string | R/I | 响应有效性校验,不展示 |
| `packageName` | string | R | 订单套餐名称 |
| `genealogyId` | int64/string,可空 | R/I | 响应有效性校验,不展示 |
| `genealogyNo``genealogyName` | string,可空 | R | 关联家谱信息展示 |
| `orderAmount``payAmount` | decimal,非负 | R | 订单金额和应付金额展示;以后端值为准 |
| `payType` | string,可空 | R | 后端返回时只读展示,不作为支付入口 |
| `payStatus` | string `0/1/2/3` | R | 待支付/已支付/已关闭/已退款 |
| `payTime``expireTime` | datetime,可空 | R | 支付和有效期信息展示 |
| `status` | string `0/1` | I | 响应有效性校验,不提供修改入口 |
| `remark` | string | R | 非空时展示 |
| `appUserId``appUserNickName``appUserPhone` | int64/string | I | 当前账号及 enrichment 字段,不在页面展示 |
实现边界:
- `ApiClient` 是三条 path 与三字段请求白名单唯一 owner;
- 页面启动时并行读取套餐、我的家谱和订单;所有业务 ID 仅来自响应;
- 创建使用前端防重复锁;创建响应必须是完整有效 `VipOrderVo`,随后重读订单列表并精确找到同一 `orderId` 才宣称成功;
- 页面省略 `payType`,由后端使用已确认的默认值;不展示后端尚未提供的支付、取消或退款按钮;
- 401 清理登录态;403 保留登录态并展示后端错误;非法套餐、订单、金额、枚举或不安全数字 ID 整条过滤。
### 7.23 家谱主页只读概览契约
页面:`profile-family-home.html`
只使用两条现有 PC 读取接口:
- `GET /genealogy/pc/genealogies/{genealogyId}/overview`:返回当前家谱 `AppGenealogyVo`
- `GET /genealogy/pc/genealogies/{genealogyId}/lineage/tree`:返回 `List<LineagePersonTreeView>`
- `genealogyId` 只从 `profile-common.js` 的当前家谱上下文取得,两条请求使用同一个稳定字符串 ID;
- 后端 `overview` 当前实际复用家谱详情服务,不包含文章、相册、视频、祭祀或动态聚合统计。
`AppGenealogyVo` 主页字段:
| 字段 | 类型/约束 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `genealogyId` | int64/string | I/A/R | 当前上下文、两条请求 path、响应一致性校验;不展示、不手填 |
| `genealogyNo` | string | R | 家谱编号只读展示 |
| `genealogyName` | string,非空 | R | 页面标题和概览名称 |
| `surname` | string | R | 姓氏只读展示 |
| `ancestralHall` | string,可空 | R | 堂号非空时展示 |
| `originPlace` | string,可空 | R | 祖籍非空时展示 |
| `regionFullName` | string,可空 | R | 完整地区非空时展示 |
| `memberCount``personCount` | integer,非负 | R | 家谱成员数和世系人物数;不在前端计算 |
| `status` | string `0/1` | I | 只接受正常 `0` 响应 |
| `canManage` | boolean | I | 严格为 `true` 时显示家谱管理入口 |
| `canEditContent` | boolean | I | 保留为内容权限上下文,主页不据此制造写操作 |
| `ownerUserId``coverOssId` | int64/string | I | 当前主页不展示、不手填 |
| 其余地址、角色、成员状态字段 | 对应 DTO 类型 | I | 当前主页不消费,不输出原始 JSON |
`LineagePersonTreeView`
| 字段 | 类型/结构 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `personId``genealogyId` | int64/string | R/I | 树节点稳定标识和响应校验;不直接展示 |
| `name``generationName`、人物摘要字段 | string | R | 复用世系页的安全节点渲染 |
| `relationType` | string `father/mother/spouse/child/adoptive` | R | 关系数据;主页不提供关系写操作 |
| `relationName` | string | R | 非空时作为关系说明 |
| `spouses``children` | `LineagePersonTreeView[]` | R | 递归世系预览 |
实现边界:
- `lineage-pages.js` 是世系节点规范化和树 HTML 的唯一 owner;主页不得复制一套树字段解释;
- 页面并行读取概览和世系树,只展示基础信息、后端已有计数和真实树;
- `/genealogy/dashboard/overview` 是后台权限接口,不属于 PC 家谱主页契约,前端不调用;
- PC 没有成员邀请创建或分享接口,“邀请家人”只显示阻断说明,不提供可点击操作;
- 主页没有写请求;401 清理登录态,403 保留登录态并显示读取错误。
### 7.24 帮助中心文章契约
页面:`help.html`。契约 owner`utils/ApiClient.js``helpArticles` / `helpArticleDetail``public/js/help-pages.js`
接口:
| method | 完整 path | 目录 | 鉴权 | 页面时机 |
| --- | --- | --- | --- | --- |
| GET | `/genealogy/pc/help-articles` | 内容文章 / PC 帮助文章列表 | APP_USER 登录态 | 帮助中心初始化或用户点击刷新 |
| GET | `/genealogy/pc/help-articles/{helpId}` | 内容文章 / PC 帮助文章详情 | APP_USER 登录态 | 用户从列表点击“查看完整解答” |
请求字段:
| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 |
| --- | --- | --- | --- | --- | --- |
| Query | `helpCategory` | 否 | string;后端按完全相等过滤 | S | 当前页面不提供猜测分类选项,因此初始化时省略;后端提供正式分类字典后才能开放选择 |
| Path | `helpId` | 是 | int64;前端始终按非零十进制字符串处理 | I | 只能来自列表响应;用户点击该文章详情时进入 path |
| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 |
| Header | `Authorization` | 是 | `Bearer <access_token>` | A | `AxiosRequestUtil` 从登录态自动填写;页面不读取、不展示 token |
| Body | — | — | 两个接口均无 Body | — | — |
`HelpArticleVo` 响应字段:
| 字段 | 类型 | 分类 | 页面使用 |
| --- | --- | --- | --- |
| `helpId` | int64/string | I | 校验稳定 ID、绑定列表项并精确重读详情;不向用户显示 |
| `helpCategory` | string | R | 列表和详情分类文字,可空 |
| `helpTitle` | string | R | 列表标题,空值使整条响应失败 |
| `helpContent` | string | R | 详情正文;转义并保留换行,不执行响应 HTML |
| `coverOssId` | int64/string | I | 当前响应没有文件 URL,页面不显示、不拼接、不允许手填 |
| `sortOrder` | int64 | I | 仅后端排序,不展示 |
| `viewCount` | int64/string | R | 非负十进制计数;详情 GET 会由后端累计浏览量 |
| `status` | string | I | 只接受正常值 `0`;停用文章整条拒绝 |
| `remark` | string | I | 后台备注,不展示 |
响应与错误边界:
- 列表响应必须是直接数组,不接受分页 `rows` 猜测;任一元素缺少稳定 ID、标题、正文或正常状态时整批失败;
- 详情必须返回与 path 中相同的 `helpId`,不回退到列表第一条;
- YAML 把两条接口标为 `security: []`,但 `PcHelpArticleController` 没有 `@SaIgnore`,部署环境无 token 实测返回“认证失败”;按用户指定的后端项目为准,当前客户端必须携带登录 token,此项作为 Apifox/后端冲突保留;
- 后端只声明统一成功包装,未提供帮助文章专属失败码;详情不存在或停用时按业务错误展示,不制造 404 枚举;
- 无登录态或 401 清理登录状态并进入登录页;403 保留登录态并展示读取错误;
- 页面只有读取动作,不提供编辑、发布、分类管理、封面 OSS ID 或成员邀请操作。
### 7.25 应用推广契约
页面:`app.html` 的“应用推广”区域。契约 owner:`utils/ApiClient.js``promotions``public/js/app-promotion-pages.js`
接口:
| method | 完整 path | 目录 | 鉴权 | 页面时机 |
| --- | --- | --- | --- | --- |
| GET | `/genealogy/pc/promotions` | 应用推广 / PC 应用推广列表 | APP_USER 登录态 | 应用下载页初始化;仅在本地已有登录 token 时请求一次 |
请求字段:
| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 |
| --- | --- | --- | --- | --- | --- |
| Query | `platform` | 否 | stringSQL 字典 `gen_promotion_platform``all` 全部(默认)、`app` APP、`pc` PC、`wechat` 小程序 | A | 应用下载页初始化时固定发送 `pc`;服务端返回 `platform=pc``platform=all` 的正常记录,不让用户选择或手填 |
| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 |
| Header | `Authorization` | 是 | `Bearer <access_token>` | A | `AxiosRequestUtil` 从登录态自动填写;页面不读取、不展示 token |
| Path | — | — | 无 Path 参数 | — | — |
| Body | — | — | 无 Body | — | — |
`AppPromotionVo` 列表元素字段:
| 字段 | 类型 | 必填/约束 | 分类 | 页面使用 |
| --- | --- | --- | --- | --- |
| `promotionId` | int64/string | SQL `BIGINT NOT NULL` 主键;前端按非零十进制字符串处理 | I | 稳定绑定卡片,不展示、不允许手填 |
| `promotionKey` | string | SQL `varchar(80) NOT NULL DEFAULT 'banner'` | I | 后台识别字段,页面不展示、不提交 |
| `promotionTitle` | string | SQL `varchar(200) NOT NULL`;页面要求非空 | R | 推广卡片标题,HTML 转义后展示 |
| `promotionDesc` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 推广卡片说明,HTML 转义后展示 |
| `coverOssId` | int64/string | 可空;响应没有可访问文件 URL | I | 不显示、不拼接 URL、不允许用户手填 OSS ID |
| `targetUrl` | string | SQL `varchar(500) DEFAULT ''`,可空;页面只接受绝对 `http`/`https` URL | R | 合法时作为新窗口外链;危险、相对或无效地址降级为不可点击卡片 |
| `platform` | string | SQL `varchar(30) NOT NULL DEFAULT 'all'`;字典枚举 `all/app/pc/wechat` | I | 仅后端筛选,当前页面不展示 |
| `sortOrder` | int64 | SQL `BIGINT NOT NULL DEFAULT 0`;后端按升序排序,同序按 `promotionId` 降序 | I | 仅后端排序,页面不二次解释或展示 |
| `status` | string | SQL `char(1) NOT NULL DEFAULT '0'``0` 正常、`1` 停用,页面只接受 `0` | I | 停用或缺失状态的记录使整批响应失败 |
| `remark` | string | SQL `varchar(500) DEFAULT NULL`,可空 | I | 后台备注,不展示 |
响应、权限与错误边界:
- 成功响应经统一请求层解包后必须是直接 `AppPromotionVo[]`,不是分页结构;任一元素缺少稳定 ID、标题或正常状态时整批失败;
- 空数组显示“当前暂无应用推广”;未登录时页面本身仍可浏览,但推广区域只显示登录入口且不发请求;
- YAML 把该接口标为 `security: []`,并且未导出真实 Query `platform`;后端 `PcPromotionApiController` 没有 `@SaIgnore`,全局拦截器要求 APP_USER 登录态。SQL 字典已确认 `all/app/pc/wechat`,服务层按“请求平台或 `all`”过滤,因此当前实现携带登录 token 并固定发送 `platform=pc`
- SQL `gen_app_promotion` 另有 `(tenant_id, platform, status, del_flag)``(tenant_id, sort_order)` 索引;`del_flag`、租户及审计字段不属于 `AppPromotionVo`,页面不接收、不展示;
- 401 清理失效 token 并把推广区域切回登录提示;403、网络失败或畸形响应只在推广区域显示读取失败,不跳转、不清理有效登录态;
- 后端只声明统一成功包装,未提供推广专属失败响应和错误码;字段长度、默认值及平台枚举已由后端 SQL 冻结,不再作为阻断项;
- 页面没有推广新增、编辑、上下架、排序、封面上传能力,不调用 APP 或后台管理接口,也不向用户暴露推广 ID、推广键或 OSS ID。
### 7.26 官网资讯契约
页面:`news.html``article-detail.html`。契约 owner`utils/ApiClient.js``siteArticles``utils/AxiosRequestUtil.js` 的公开请求鉴权语义和 `public/js/site-news-pages.js`
接口:
| method | 完整 path | 目录 | 鉴权 | 页面时机 |
| --- | --- | --- | --- | --- |
| GET | `/genealogy/pc/site/articles` | 站点内容 / PC 站点文章列表 | 公开,`auth: false` | 资讯列表初始化、分类切换后的页面初始化,以及详情页按列表响应 ID 精确重读 |
请求字段:
| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 |
| --- | --- | --- | --- | --- | --- |
| Query | `articleType` | 否 | stringSQL 字典 `gen_site_article_type``news` 新闻(默认)、`notice` 公告、`download` 下载 | S | 用户点击固定分类链接后从 URL Query 取得;“全部”省略该字段,非法值不发请求 |
| Query | `limit` | 否 | integer,客户端只允许 1–100;服务端正数最大取 100 | A | 列表和详情重读均由页面固定提交 `100` |
| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 |
| Header | `Authorization` | 不发送 | — | — | 方法设置 `auth: false`;即使本地已有 token 也不发送,公开请求 401/403 不清理既有登录态 |
| Path | — | — | 无 Path 参数 | — | — |
| Body | — | — | 无 Body | — | — |
`SiteArticleVo` 列表元素字段:
| 字段 | 类型 | 必填/约束 | 分类 | 页面使用 |
| --- | --- | --- | --- | --- |
| `articleId` | int64/string | SQL `BIGINT NOT NULL` 主键;前端按非零十进制字符串处理 | I | 只用于生成详情链接和精确匹配,不展示、不允许手填 |
| `articleType` | string | SQL `varchar(50) NOT NULL DEFAULT 'news'`;枚举 `news/notice/download` | R | 转换为新闻、公告、下载标签;枚举外记录使整批失败 |
| `articleTitle` | string | SQL `varchar(200) NOT NULL`;页面要求非空 | R | 列表和详情标题,HTML 转义 |
| `articleSummary` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 列表摘要和详情导语,HTML 转义 |
| `articleContent` | string | SQL `text`,可空 | R | 详情正文;HTML 转义并只保留换行,不执行响应 HTML |
| `coverOssId` | int64/string | SQL `BIGINT`,可空;没有文件 URL 契约 | I | 不显示、不拼接 URL、不允许手填 |
| `externalUrl` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 只接受绝对 HTTP/HTTPS;合法时显示安全新窗口外链 |
| `publishTime` | string/date | SQL `datetime`,可空 | R | 列表和详情发布时间文本,HTML 转义 |
| `sortOrder` | int64 | SQL `BIGINT NOT NULL DEFAULT 0` | I | 仅后端排序,不展示 |
| `status` | string | SQL `char(1) NOT NULL DEFAULT '0'``0` 正常、`1` 停用 | I | 页面只接受 `0`,其他值使整批失败 |
| `remark` | string | SQL `varchar(500) DEFAULT NULL`,可空 | I | 后台备注,不展示 |
响应、详情与错误边界:
- 成功响应经统一请求层解包后必须是直接 `SiteArticleVo[]`;不接受分页 `rows`、旧字段别名或任一非法元素;
- 服务端只查正常状态,按 `sortOrder ASC``publishTime DESC``articleId DESC` 排序;页面保持服务端顺序;
- 后端没有站点文章单条详情接口;详情 ID 只能来自列表链接,详情页重读 `{ limit: 100 }` 后精确匹配同一字符串 ID,不存在时不得回退第一条;
- 标题、摘要、正文、类型和时间全部转义;正文只保留换行,危险或相对外链不渲染;
- 空数组显示“当前暂无资讯”;非法分类/详情 ID 不发请求;网络、业务或畸形响应只更新内容区域,不跳转登录;
- YAML 已导出 method/path、`articleType``limit``security: []`,但响应只引用通用列表结果,未导出 `SiteArticleVo` 11 个字段;完整字段、长度、默认值和枚举由后端 VO、SQL 与字典补齐;
- 2026-07-30 使用本地页面对当前部署环境做匿名实测时,接口返回“认证失败,无法访问系统资源”;页面保持在资讯页且不清理已有 token。该行为与 YAML 的 `security: []` 冲突,后端需确认部署环境是否遗漏公开放行;前端不通过附带登录 token 绕过公开契约;
- `coverOssId` 的文件 URL 和站点文章单条详情接口继续作为后端契约阻断;页面不猜 URL、不制造详情路径;
- 本批不修改 `about/culture/privacy/terms/surname`,也不复用登录后的家谱谱文 CRUD 脚本。
## 8. 分阶段执行顺序
@@ -1247,7 +1561,7 @@ PC `GenealogyMemberVo` 响应字段及页面用途:
### 阶段 7:剩余 PC 页面分批开放
首批已完成家谱生命周期 ApiClient 契约和创建家谱页面:额度、三级地区、封面上传派生、严格 DTO 校验、防重复与创建后重读均已接入。加入申请/审核、反馈/工单、VIP 和家谱主页按独立后续批次实施;成员邀请、分享和资料完善提醒没有 PC Controller,继续阻断。
首批已完成家谱生命周期 ApiClient 契约和创建家谱页面:额度、三级地区、封面上传派生、严格 DTO 校验、防重复与创建后重读均已接入。第二批已完成申请加入、我的申请、撤销和管理员审核页面;家谱/申请 ID 均来自响应或当前上下文,写后重读确认。第三批已完成意见反馈工单展示:四页共用 PC 反馈集合、提交后三字段白名单重读、详情精确匹配和隐私过滤。第四批已完成 VIP 套餐、我的家谱选项和会员订单:页面不手填业务 ID,不伪造支付能力,创建后重读同一订单确认。第五批已完成家谱主页只读概览:复用 PC overview 与世系树,权限控制管理入口,不制造聚合统计或邀请能力。第六批已完成帮助中心:登录后读取 PC 帮助文章列表,列表响应产生详情 ID,详情精确重读并安全展示正文;Apifox 的匿名标记与后端实际鉴权冲突已记录。第七批已完成应用下载页的 PC 推广列表:未登录不发请求,登录后固定发送 SQL 字典确认的 `platform=pc`,只读取服务端返回的 `pc + all` 正常记录,外链仅允许绝对 HTTP/HTTPS;推广 ID、后台键和 OSS ID 均不向用户开放。第八批已完成官网资讯列表与详情:公开请求不发送或清理 token,分类使用 SQL 字典 `news/notice/download`,详情 ID 只来自列表响应并通过重读列表精确匹配;YAML 通用响应缺失完整 `SiteArticleVo`,且当前部署环境匿名访问仍返回认证失败,两项差异均已记录。成员邀请、分享和资料完善提醒没有 PC Controller,继续阻断。
## 9. 文件级施工建议