新增小程序配置模块对接方案 (Banner/Contact/FAQ/Agreement)

这个提交包含在:
API Changelog Bot 2026-04-18 11:28:12 +08:00
父节点 f0f2cd8fe7
当前提交 471ebd9d6c

查看文件

@ -0,0 +1,410 @@
# 小程序配置模块对接方案
> 模块:小程序配置 (Banner 首页配置 / 我的页面配置 / 协议政策 / FAQ)
> 后端服务hl-user-serviceAdmin + Internal+ hl-mp-service小程序透传
> 文档日期2026-04-18
> 适用dev 分支当前代码PR #773 整改后)
---
## 0. 通用约定(必读)
### 0.1 响应包装 `Result<T>`
```json
{
"code": 200, // 200=成功;非 200=业务异常HTTP 始终 200
"message": "成功",
"data": { ... }
}
```
**所有接口 HTTP 状态码始终是 200**,业务成败看 `code`。401 = 未登录/Token 过期;403 = 无权限;500 = 业务/系统异常。
### 0.2 分页响应 `PageResult<T>`
```json
{ "records": [...], "total": 0, "page": 1, "pageSize": 20 }
```
分页请求统一字段:`page`(默认 1,≥1`pageSize`(默认 20,1~100
### 0.3 Long ID 序列化
所有 Long 类型 ID 通过 `@JsonSerialize(ToStringSerializer)` 序列化为 **字符串**(防 JS 大数精度丢失)。前端接收/回传统一按字符串处理。
### 0.4 富文本 XSS 过滤
`content`/`answer` 字段后端会经 `XssUtils` 过滤:
- 禁用:`<script>` `<iframe>` `<img>` `on*` 事件属性
- 允许:常规标签 + 样式属性
- Agreement.content 单字段最大 100KB;Faq.answer 最大 10KB
### 0.5 幂等保护
管理端 POST/PUT 接口大多带 `@Idempotent(timeout=5)`5 秒内同参数请求会被拦截。前端**无需自行去重**。
### 0.6 小程序端缓存
所有 `/mp/**` 接口使用 `@MpCache` 缓存 1800s30 分钟)。**管理端修改后小程序最多 30 分钟生效**,无主动失效机制。
### 0.7 端口/路由
| 类型 | 路径前缀 | 说明 |
|---|---|---|
| 管理端 | `/admin/...` | 走 gateway → hl-user-service,需 ADMIN Token |
| 小程序 | `/mp/...` | 走 gateway → hl-mp-service → Feign → hl-user-service,需 USER Token部分公开 |
| 内部 | `/internal/...` | 服务间 Feign 专用,**外部访问被网关拦截**403 |
---
## 1. Banner 首页配置(轮播图)
### 1.1 业务说明
首页顶部轮播位(图片或视频),支持时间窗、链接跳转(产品/景点/活动/H5/小程序内页)、排序。
### 1.2 数据库表 `sys_banner`
关键字段:
| 列 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 雪花 ID |
| title / subtitle | VARCHAR | 标题/副标题 |
| media_type | VARCHAR(20) | IMAGE / VIDEO字典 banner_media_type|
| image_url / video_url | VARCHAR(500) | 资源 URL |
| material_id | BIGINT | 素材库关联 ID |
| tags | VARCHAR(200) | 标签,逗号分隔 |
| link_type | VARCHAR(20) | 字典 banner_link_type |
| link_id / link_url / link_target_name | VARCHAR | 跳转参数 |
| sort_order | INT | 越小越前 |
| status | INT | 0=下线 1=上线(**注意:本表 status 是整数,与其他表的字符串不同**|
| start_time / end_time | DATETIME | 上下线时间窗 |
| create_time / update_time / deleted_at | DATETIME | 审计 + 软删 |
### 1.3 管理端接口(前缀 `/admin/banner`
| 方法 | 路径 | 入参 | 返参 |
|---|---|---|---|
| GET | `/admin/banner` | Query: page, pageSize, keyword, status | `Result<PageResult<BannerVO>>` |
| GET | `/admin/banner/{id}` | Path: id | `Result<BannerVO>` |
| POST | `/admin/banner` | Body: BannerRequest | `Result<BannerVO>` |
| PUT | `/admin/banner/{id}` | Path: id + Body: BannerRequest | `Result<BannerVO>` |
| DELETE | `/admin/banner/{id}` | Path: id | `Result<Void>` |
⚠️ Banner 模块的请求体类名是 `BannerRequest`(历史遗留,未按 `XxxSaveReqVO` 规范)。
### 1.4 BannerRequest 字段(创建+更新合一)
| 字段 | 类型 | 必填 | 校验/字典 | 说明 |
|---|---|---|---|---|
| title | String | ✅ | @NotBlank, ≤100 | 标题 |
| subtitle | String | | ≤200 | 副标题 |
| mediaType | String | | banner_media_type, ≤20 | IMAGE / VIDEO |
| imageUrl | String | ✅ | @NotBlank, ≤500 | 图片或视频封面 URL |
| videoUrl | String | | ≤500 | 视频 URLmediaType=VIDEO 时有效)|
| materialId | Long | | | 素材库 ID关联|
| tags | String | | ≤200 | 逗号分隔标签 |
| linkType | String | | banner_link_type, ≤20 | 跳转类型,见字典 |
| linkId | String | | ≤50 | 跳转目标 IDPRODUCT/SCENIC 等用)|
| linkUrl | String | | ≤500 | 外部 URLWEBVIEW/PAGE 用)|
| linkTargetName | String | | ≤100 | 跳转名称(后台展示用)|
| sortOrder | Integer | | | 排序,越小越前 |
| status | Integer | | 0/1 | 0=下线,1=上线 |
| startTime | LocalDateTime | | ISO 8601 | 展示开始时间 |
| endTime | LocalDateTime | | ISO 8601 | 展示结束时间 |
### 1.5 BannerVO 返回字段
含上述全部字段 + `id`(String)、`createdAt``tags`(返回时为 `List<String>` 已拆好)。
### 1.6 小程序端接口(前缀 `/mp/banner`
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/mp/banner/active` | 公开接口(无需登录),返回当前生效的轮播列表,缓存 30 分钟 |
返回:`Result<List<BannerVO>>`(结构同管理端 BannerVO,已按 `sortOrder` 升序,已过滤 `status=1` 且在 `startTime~endTime` 时间窗内的)。
---
## 2. 我的页面配置 - Contact联系我们 / 关于我们)
### 2.1 业务说明
"我的"页面联系方式区块。三种渠道:
- **ABOUT**:关于我们(用 `content` 富文本)
- **ONLINE_CS**:在线客服(用 `value` 存 URL
- **PHONE**:电话咨询(用 `value` 存电话号)
### 2.2 数据库表 `sys_contact`
| 列 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 雪花 ID |
| channel_type | VARCHAR(32) | 字典 contact_channel_type |
| name | VARCHAR(100) | 标签文案 |
| subtitle | VARCHAR(200) | 副标题(保留字段,前端可不展示)|
| icon_url | VARCHAR(500) | 图标 URL来自素材库 SYSTEM 分类)|
| value | VARCHAR(500) | 渠道值URL/电话)|
| content | TEXT | 富文本(仅 ABOUT 用)|
| sort_order | INT | 排序 |
| status | VARCHAR(32) | ACTIVE / INACTIVE |
| create_time/update_time/created_by/updated_by/deleted_at | | 审计 + 软删 |
### 2.3 管理端接口(前缀 `/admin/contact`
| 方法 | 路径 | 入参 | 返参 |
|---|---|---|---|
| GET | `/admin/contact` | Query: ContactPageReqVO | `Result<PageResult<ContactRespVO>>` |
| GET | `/admin/contact/{id}` | Path: id | `Result<ContactRespVO>` |
| POST | `/admin/contact` | Body: ContactSaveReqVO | `Result<Long>`(返回新 ID|
| PUT | `/admin/contact/{id}` | Path: id + Body: ContactSaveReqVO | `Result<Void>` |
| DELETE | `/admin/contact/{id}` | Path: id | `Result<Void>` |
| GET | `/admin/contact/channel-types/enabled` | — | `Result<List<{value,label}>>` 渠道下拉框 |
### 2.4 ContactPageReqVO 查询字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / pageSize | Integer | | 见 0.2 |
| keyword | String | | 模糊匹配 name/value |
| status | String | | ACTIVE/INACTIVE 不传=全部 |
| channelType | String | | ABOUT/ONLINE_CS/PHONE 不传=全部 |
### 2.5 ContactSaveReqVO 字段(创建+更新合一)
| 字段 | 类型 | 必填 | 校验/字典 | 说明 |
|---|---|---|---|---|
| id | Long | | | 不传=创建,传=更新 |
| channelType | String | ✅ | @NotBlank, ≤32, contact_channel_type | ABOUT/ONLINE_CS/PHONE |
| name | String | ✅ | @NotBlank, ≤100 | 标签文案 |
| subtitle | String | | ≤200 | 副标题 |
| iconUrl | String | | ≤500 | 图标 URL |
| value | String | | ≤500 | 渠道值ABOUT 可空)|
| content | String | | ≤50000 | 富文本,仅 ABOUT 用 |
| sortOrder | Integer | | | 排序,默认 0 |
### 2.6 ContactRespVO 返回字段
全字段 + `status`(字典)、`createTime``updateTime`
### 2.7 小程序端接口(前缀 `/mp/common`
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/mp/common/contact` | 返回全部 ACTIVE 联系方式列表,按 sortOrder 升序,缓存 30 分钟 |
返回:`Result<List<MpContactRespVO>>`,字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | |
| channelType | String | ABOUT/ONLINE_CS/PHONE |
| name | String | |
| iconUrl | String | |
| value | String | |
| content | String | 富文本ABOUT 才有)|
| sortOrder | Integer | |
> **小程序端不返回** subtitle/status/创建时间,瘦身处理。
---
## 3. 我的页面配置 - FAQ常见问题
### 3.1 业务说明
两级结构:分类 → 条目。小程序"帮助中心"按分类聚合展示。
### 3.2 数据库表
- `sys_faq_category`分类id / name / sort_order / status / 审计)
- `sys_faq_item`条目id / category_id / question / answer / sort_order / status / 审计)
### 3.3 管理端接口 - 分类(前缀 `/admin/faq/categories`
| 方法 | 路径 | 入参 | 返参 |
|---|---|---|---|
| GET | `/admin/faq/categories` | — | `Result<List<FaqCategoryRespVO>>` 含全部(含停用),带 itemCount |
| GET | `/admin/faq/categories/enabled` | — | `Result<List<{value,label}>>` 仅启用,下拉框用 |
| GET | `/admin/faq/categories/{id}` | Path: id | `Result<FaqCategoryRespVO>` |
| POST | `/admin/faq/categories` | Body: FaqCategorySaveReqVO | `Result<FaqCategoryRespVO>` |
| PUT | `/admin/faq/categories/{id}` | Path + Body | `Result<FaqCategoryRespVO>` |
| PUT | `/admin/faq/categories/{id}/status` | Path + Query: status=ACTIVE/INACTIVE | `Result<Void>` |
| DELETE | `/admin/faq/categories/{id}` | Path: id | `Result<Void>` 软删,**前置校验:分类下不能有条目** |
### 3.4 管理端接口 - 条目(前缀 `/admin/faq/items`
| 方法 | 路径 | 入参 | 返参 |
|---|---|---|---|
| GET | `/admin/faq/items` | Query: FaqItemPageReqVO | `Result<PageResult<FaqItemRespVO>>` |
| GET | `/admin/faq/items/{id}` | Path: id | `Result<FaqItemRespVO>` |
| POST | `/admin/faq/items` | Body: FaqItemSaveReqVO | `Result<FaqItemRespVO>` |
| PUT | `/admin/faq/items/{id}` | Path + Body | `Result<FaqItemRespVO>` |
| PUT | `/admin/faq/items/{id}/status` | Path + Query: status=ACTIVE/INACTIVE | `Result<Void>` |
| DELETE | `/admin/faq/items/{id}` | Path: id | `Result<Void>` 软删 |
### 3.5 VO 字段
**FaqCategorySaveReqVO**
| 字段 | 类型 | 必填 | 校验 |
|---|---|---|---|
| id | Long | | 不传=创建 |
| name | String | ✅ | @NotBlank, ≤50 |
| sortOrder | Integer | | |
**FaqCategoryRespVO**id(String) / name / sortOrder / status / statusLabel / itemCount / createTime / updateTime
**FaqItemPageReqVO**
| 字段 | 类型 | 说明 |
|---|---|---|
| page / pageSize | Integer | |
| categoryId | Long | 可选,按分类筛 |
| keyword | String | 模糊匹配 question |
| status | String | ACTIVE/INACTIVE |
**FaqItemSaveReqVO**
| 字段 | 类型 | 必填 | 校验 |
|---|---|---|---|
| id | Long | | 不传=创建 |
| categoryId | Long | ✅ | @NotNull |
| question | String | ✅ | @NotBlank, ≤200 |
| answer | String | ✅ | @NotBlank, ≤10000,富文本 XSS 过滤 |
| sortOrder | Integer | | |
**FaqItemRespVO**id(String) / categoryId(String) / categoryName / question / answer / sortOrder / status / statusLabel / createTime / updateTime
### 3.6 小程序端接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/mp/common/faq` | 返回全部启用 FAQ按分类嵌套,缓存 30 分钟 |
返回:`Result<List<MpFaqCategoryRespVO>>`
- `MpFaqCategoryRespVO``id`(String) / `name` / `items`(条目列表)
- `MpFaqItemRespVO``id`(String) / `question` / `answer`
> **小程序端只返回启用的分类和条目**,按 sortOrder 升序。
---
## 4. 协议政策 - Agreement
### 4.1 业务说明
法律协议,按 `type` 唯一(每类型最多一条记录)。前端"用户协议"、"隐私政策"链接打开。
**UPSERT 模式**:管理端只有保存接口(按 type 自动判断创建/更新),无独立 DELETE。
### 4.2 数据库表 `sys_agreement`
| 列 | 类型 | 说明 |
|---|---|---|
| agreement_id | BIGINT | 雪花 ID |
| type | VARCHAR(64) | **UNIQUE**,字典 mp_agreement_type |
| title | VARCHAR(100) | 标题 |
| content | MEDIUMTEXT | 正文,HTML 富文本 |
| update_date | DATE | 显式更新日期,C 端副标题用 |
| version | VARCHAR | 版本号(保留字段,默认 1.0|
| status | VARCHAR(32) | ACTIVE/INACTIVE |
| sort_order | INT | 排序 |
| create_time/update_time/created_by/updated_by/deleted_at | | 审计 + 软删 |
### 4.3 管理端接口(前缀 `/admin/agreement`
| 方法 | 路径 | 入参 | 返参 |
|---|---|---|---|
| GET | `/admin/agreement/page` | Query: AgreementPageReqVO | `Result<IPage<AgreementRespVO>>` |
| GET | `/admin/agreement/{id}` | Path: id | `Result<AgreementRespVO>` |
| GET | `/admin/agreement/type/{type}` | Path: type | `Result<AgreementRespVO>` 按 type 取,编辑页用 |
| POST | `/admin/agreement/save-by-type` | Body: AgreementSaveReqVO | `Result<Void>` UPSERT,幂等 |
> **没有 DELETE 接口**。下线请改 status=INACTIVE。
### 4.4 AgreementSaveReqVO 字段
| 字段 | 类型 | 必填 | 校验/字典 |
|---|---|---|---|
| type | String | ✅ | @NotBlank, @Pattern `^(USER_AGREEMENT|PRIVACY_POLICY)$`, mp_agreement_type |
| title | String | ✅ | @NotBlank, ≤100 |
| content | String | ✅ | @NotBlank, ≤100000100KB,HTML 富文本,XSS 白名单过滤 |
| updateDate | LocalDate | | 不传默认今天 |
| sortOrder | Integer | | 默认 0 |
### 4.5 AgreementRespVO 返回字段
id(String) / type / typeLabel / title / content / version / updateDate / sortOrder / status / statusLabel / createTime / updateTime / createdBy(String) / updatedBy(String)
### 4.6 小程序端接口(前缀 `/mp/common`
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/mp/common/agreement/{type}` | 取指定类型协议详情(含正文),缓存 30 分钟 |
| GET | `/mp/common/agreement/list` | 取全部 ACTIVE 协议列表(**不含 content**,轻量),缓存 30 分钟 |
**MpAgreementDetailVO**详情接口type / title / content / version / updateDate / updateTime
**MpAgreementSimpleVO**列表接口type / title / version / updateDate / updateTime
---
## 5. 字典完整清单
### 5.1 `banner_media_type`
| value | label |
|---|---|
| IMAGE | 图片 |
| VIDEO | 视频 |
### 5.2 `banner_link_type`
| value | label |
|---|---|
| NONE | 无跳转 |
| PRODUCT | 产品详情 |
| SCENIC | 景点详情 |
| ACTIVITY | 活动详情 |
| HOTEL | 酒店详情 |
| PAGE | 小程序页面 |
| WEBVIEW | H5 外链 |
> 说明linkType=PRODUCT/SCENIC/ACTIVITY/HOTEL 时,`linkId` 必填(资源 ID。linkType=WEBVIEW/PAGE 时,`linkUrl` 必填。
### 5.3 `contact_channel_type`
| value | label | 用法 |
|---|---|---|
| ABOUT | 关于我们 | `content` 存富文本 |
| ONLINE_CS | 在线客服 | `value` 存 URL |
| PHONE | 电话咨询 | `value` 存电话号 |
### 5.4 `mp_agreement_type`
| value | label |
|---|---|
| USER_AGREEMENT | 用户协议 |
| PRIVACY_POLICY | 隐私政策 |
### 5.5 `common_status`
| value | label |
|---|---|
| ACTIVE | 启用 |
| INACTIVE | 停用 |
> 适用sys_contact / sys_faq_category / sys_faq_item / sys_agreement 的 status 字段。
> **特例**`sys_banner.status` 是整数 0/10=下线,1=上线),与本字典不同,不要混淆。
---
## 6. 前端对接 Checklist
### 管理端
- [ ] 列表/详情页直接调 `/admin/...`,使用 ADMIN Token
- [ ] 表单字段按本文档 SaveReqVO 严格对齐(必填项、长度限制)
- [ ] 字典下拉用各模块的 `/enabled` 接口(不要硬编码)
- [ ] Long 类型 ID 接收/回传统一按字符串处理
- [ ] 富文本编辑器输出 HTML,后端会过滤危险标签
- [ ] 5 秒内同参数提交会被幂等拦截(无需自行去重)
- [ ] 软删除后该数据列表自动消失(无需特殊处理)
### 小程序端
- [ ] 调 `/mp/...` 接口,使用 USER Token部分公开如 `/mp/banner/active` 可不传)
- [ ] **缓存 30 分钟**:管理端改完后小程序最多 30 分钟生效,前端体验需告知用户
- [ ] Banner/FAQ 已按 sortOrder 排好,前端按返回顺序展示即可
- [ ] FAQ 返回结构是嵌套的(分类→条目数组),可直接渲染分组列表
- [ ] Agreement 详情页用 `/mp/common/agreement/{type}` 取 content;列表如登录页同意勾选`/list` 节省流量
---
## 7. 已知坑/注意事项
1. **status 字段两种格式**sys_banner 是 0/1 整数;其他三表contact/faq/agreement是 ACTIVE/INACTIVE 字符串。前端列表筛选时勿混。
2. **Banner 用 BannerRequest 类名**(历史遗留),不是 BannerSaveReqVO。
3. **FAQ 删除分类有前置校验**:分类下有条目时返回业务异常 `code=500`,需先清空条目。
4. **Agreement 不能删**:只能 status=INACTIVE 软下线。
5. **小程序端 30 分钟缓存**无主动失效机制,灰度调试期建议用临时账号清测试用户的 Redis Key。
6. **OperatorHolder 已修复**PR #777):所有写操作的 `created_by/updated_by` 现在能正确填入操作人 ID之前是 NULL
---
## 8. 联系/反馈
后端代码位置:
- Admin Controller`hl-user-service/src/main/java/com/hulalv/user/controller/Admin{Banner,Contact,Faq,Agreement}Controller.java`
- Internal Controller`hl-user-service/src/main/java/com/hulalv/user/controller/Internal{Banner,Contact,Faq,MpCommon}Controller.java`
- MP Controller`hl-mp-service/src/main/java/com/hulalv/mp/controller/Mp{Banner,Common}Controller.java`
- VO`hl-user-service/src/main/java/com/hulalv/user/vo/{admin,mp}/`
如有字段缺失或行为不符,**直接反馈到本仓库 Issue**(带接口路径 + 实际响应 + 期望响应)。