hl-api-changelog/changelogs/2026-04/2026-04-17_mp-config-refactor_contact-faq-agreement.md

10 KiB

小程序配置模块重构:联系我们 / 常见问题 / 协议政策

服务: hl-user-service (端口 8081) PR: wx/HL#773 Issue: wx/HL#766 日期: 2026-04-17 影响范围: 管理端三大配置模块(联系我们 / 常见问题 / 协议政策)的所有写接口和大部分读接口


背景

小程序配置模块历史遗留问题较多:

  • Status 字段混用 Integer(0/1) 与 String"启用/停用"
  • VO 命名不统一:FaqCategoryVO / AgreementRequest 等不符合团队规范
  • 协议政策每类型应只有一条记录,但历史接口允许多记录 CRUD,数据可能出现同类型多条
  • 没有字典校验 / 条件必填 / 幂等 / 富文本 XSS 过滤,业务错常见 500

本次整改按团队规范重构三个模块,统一 VO 命名、字段类型、错误码、安全策略。

字段统一变更(全三模块)

Status 字段Integer → String

旧值 新值
1 "ACTIVE"
0 "INACTIVE"

影响所有请求体 / 响应体中的 status / enabled 字段,前端请求与展示都需切换成大写英文常量。

数据库迁移由 DDL 完成(幂等 SQL,已在 PR 中附带)。

业务错误码500 → 400

字典值非法、条件校验失败、资源不存在等业务错从 code=500 改为 code=400,并附中文消息。HTTP 始终 200,前端按 code 区分。

写接口全部加 @Idempotent

9 个写接口启用幂等控制3~5 秒窗口),前端连点 / 重试会被返:

{
  "code": 429,
  "success": false,
  "message": "请勿重复提交"
}

改 body 后可再次提交。

富文本 XSS 过滤(后端自动处理)

以下字段提交时后端自动清洗 <script> / <iframe> / on* 事件属性等危险标签:

  • Contact.content
  • FaqItem.answer
  • Agreement.content

前端展示时可直接 v-html 渲染,无需额外过滤。


Contact联系我们

字段重命名

旧字段 新字段 说明
title name 渠道名称
icon iconUrl 图标 URL

字典校验 channelType

说明 必填的关联字段
ABOUT 关于我们 content(富文本介绍)必填
ONLINE_CS 在线客服 value(链接/账号)必填
PHONE 客服电话 value(电话号码)必填

非上述 3 值 → code=400 + "渠道类型不支持"。

接口清单

管理端

方法 路径 说明 变更
POST /admin/contact 创建 Body 结构变更:原 ContactRequest 废弃,改为 ContactSaveReqVO(字段见下)
PUT /admin/contact/{id} 修改 同上,Body 用 ContactSaveReqVOid 由路径传)
DELETE /admin/contact/{id} 删除 签名不变
GET /admin/contact/{id} 详情 响应用 ContactRespVO(字段重命名)
GET /admin/contact/page 分页 响应项为 ContactRespVO
GET /admin/contact/channel-types/enabled 新增:渠道类型下拉 List<Map<value,label>>

ContactSaveReqVO(请求)

{
  "channelType": "ABOUT",         // 必填。ABOUT / ONLINE_CS / PHONE
  "name": "关于呼籁",              // 必填。旧名 title
  "iconUrl": "https://...",       // 选填。旧名 icon
  "value": null,                  // ABOUT 可空;ONLINE_CS/PHONE 必填
  "content": "<p>...</p>",        // ABOUT 必填;其他可空。富文本 XSS 过滤
  "sortOrder": 1,                 // 选填
  "status": "ACTIVE"              // 选填,默认 ACTIVE
}

ContactRespVO(响应)

{
  "id": 1,
  "channelType": "ABOUT",
  "name": "关于呼籁",               // 旧名 title
  "iconUrl": "https://...",        // 旧名 icon
  "value": null,
  "content": "<p>...</p>",
  "sortOrder": 1,
  "status": "ACTIVE",              // 旧为 Integer 0/1
  "createTime": "2026-04-17 10:00:00",
  "updateTime": "2026-04-17 10:00:00"
}

Faq常见问题

分类Category

方法 路径 说明 变更
POST /admin/faq/categories 创建分类 Body 改为 FaqCategorySaveReqVO(原 FaqCategoryRequest 废弃)
PUT /admin/faq/categories/{id} 修改分类 同上
DELETE /admin/faq/categories/{id} 删除分类 新增业务校验:分类下有条目时 code=400 + "该分类下仍有常见问题,无法删除"
GET /admin/faq/categories/{id} 详情 响应 FaqCategoryRespVO
GET /admin/faq/categories/page 分页 响应 FaqCategoryRespVO
GET /admin/faq/categories/enabled 新增:分类下拉 List<Map<value,label>>,只返回 ACTIVE

FaqCategorySaveReqVO

{
  "name": "预订须知",
  "sortOrder": 1,
  "status": "ACTIVE"
}

条目Item

方法 路径 说明 变更
POST /admin/faq/items 创建条目 Body 改为 FaqItemSaveReqVO(原 FaqItemRequest 废弃)
PUT /admin/faq/items/{id} 修改条目 同上
DELETE /admin/faq/items/{id} 删除 签名不变
GET /admin/faq/items/{id} 详情 响应 FaqItemRespVO
GET /admin/faq/items/page 分页 响应 FaqItemRespVO

FaqItemSaveReqVO

{
  "categoryId": 1,           // 必填,必须是存在且 ACTIVE 的分类
  "question": "如何退订?",
  "answer": "<p>...</p>",    // 富文本,XSS 过滤
  "sortOrder": 1,
  "status": "ACTIVE"
}

categoryId 指向不存在 / 已停用的分类 → code=400

VO 命名调整(前端 TypeScript 定义需要改)

旧类型 新类型
FaqCategoryVO FaqCategoryRespVO
FaqItemVO FaqItemRespVO
FaqCategoryRequest FaqCategorySaveReqVO
FaqItemRequest FaqItemSaveReqVO

Agreement协议政策

核心变化:每个 type 只有一条记录saveByType 模式),废弃多记录 CRUD。

废弃接口(前端必须下线)

方法 路径 处理
POST /admin/agreement 删除。改用 /save-by-type
PUT /admin/agreement/{id} 删除。改用 /save-by-type
DELETE /admin/agreement/{id} 删除。协议不允许删除,只能停用或覆盖

新接口

方法 路径 说明
POST /admin/agreement/save-by-type 按类型保存:该 type 不存在则创建,存在则覆盖(幂等)
GET /admin/agreement/by-type/{type} 按类型查询,type 参数为字典值
GET /admin/agreement/list 返回所有启用协议(简要列表)

字典 agreement_type

说明
USER_AGREEMENT 用户协议
PRIVACY_POLICY 隐私政策

非上述两值 → code=400 + "协议类型不支持"。

旧类型迁移DDL 完成)

旧值 新值 / 处理
user USER_AGREEMENT
privacy PRIVACY_POLICY
travel 软删(deleted_at 设置)
refund 软删(deleted_at 设置)

AgreementSaveReqVO(请求)

{
  "type": "USER_AGREEMENT",          // 必填。USER_AGREEMENT / PRIVACY_POLICY
  "title": "用户协议",                // 必填
  "content": "<p>...</p>",            // 必填,XSS 过滤
  "updateDate": "2026-04-17",         // 必填,LocalDate
  "sortOrder": 1                      // 选填
}

AgreementRequest 废弃。

AgreementRespVO(响应)

{
  "id": 1,
  "type": "USER_AGREEMENT",
  "title": "用户协议",
  "content": "<p>...</p>",
  "updateDate": "2026-04-17",         // 新增字段LocalDate
  "sortOrder": 1,
  "status": "ACTIVE",
  "createTime": "2026-04-17 10:00:00",
  "updateTime": "2026-04-17 10:00:00"
}

前端适配清单

必须改

  • 所有模块:请求体 / 响应体 status 字段由 Integer(0/1) 换为 String("ACTIVE"/"INACTIVE")
  • Contact:请求体字段 title → nameicon → iconUrl,响应同步
  • Contact:新建/编辑表单中 channelType 切换时,校验关联字段必填逻辑ABOUT→content,ONLINE_CS/PHONE→value
  • Contact:渠道类型改用 /admin/contact/channel-types/enabled 下拉接口,不要写死前端枚举
  • Faq:分类下拉改用 /admin/faq/categories/enabled
  • Faq:删除分类时处理"分类下有条目"的 400 错误提示
  • FaqTypeScript 类型改名 FaqCategoryVO → FaqCategoryRespVO
  • Agreement:三个废弃接口全部下线,统一改用 /save-by-type
  • Agreement:表单类型下拉只保留 USER_AGREEMENT / PRIVACY_POLICY,清除 travel / refund
  • Agreement:响应 VO 新字段 updateDate,列表需要展示(建议列)
  • 全局:业务错处理从 code === 500 改为 code === 400500 留给真正的系统异常)
  • 全局:连点/重试按钮要处理 429 "请勿重复提交"

无需改

  • 列表/详情接口路径不变(除 Agreement 删除的 3 个)
  • 富文本字段不用在前端做 XSS 过滤(后端处理了)

部署步骤(后端已完成,前端配合验证)

由用户/测试服务器管理员在测试环境执行:

  1. 执行 DDLsql/mp_config_refactor_v2.sqlhl_user_service 库,幂等可重跑)
  2. 清 Redis 缓存:redis-cli DEL hl:menu:tree:* hl:dict:*
  3. 重启 hl-user-service
  4. 管理员重新登录加载新菜单

向后兼容性

场景 兼容性
旧前端继续发 Integer status 不兼容。会触发 @Pattern 校验返回 400
旧前端继续用 title/icon 字段名 不兼容。响应 VO 不再返这两个字段
旧前端调 POST/PUT/DELETE /admin/agreement 不兼容。接口已删除,404
旧前端不传 channelType 必填字段 400 + 中文消息

前端必须随本 PR 同步更新,否则管理端会出现大面积报错。