feat(api): 实现三端统一的JSON API响应契约
- 新增`api_response.py`统一响应封装工具类,提供标准成功/错误响应构造方法 - 重构WonderQ-Admin全局异常处理器,将所有异常转换为标准响应格式 - 修改所有公共和管理端接口的返回逻辑,统一使用`code`(与HTTP状态码一致)、`msg`和`data`的三层结构 - 新增`api-response-contract.md`文档,定义完整的三端统一JSON响应规范 - 更新所有领域API文档,明确业务数据需位于`data`字段内,补充响应格式说明 - 为WonderQ-MiniAPP和WonderQ-Admin-UI新增响应解析逻辑和类型定义,自动完成协议校验和错误处理 - 更新所有测试用例,适配新的响应结构确保接口符合契约要求 - 新增`module-config-api.md`模块配置API文档,补充站点模块配置的接口约定 - 更新项目README文档,调整文档分类顺序将响应契约置于首位
This commit is contained in:
@@ -6,38 +6,42 @@
|
||||
|
||||
后端开发:
|
||||
|
||||
1. `admin-api-requirements.md`
|
||||
2. `home-api.md`
|
||||
3. `wanfa-api.md`
|
||||
4. `concierge-api.md`
|
||||
5. `detail-api.md`
|
||||
6. `team-building-api.md`
|
||||
7. `public-api.md`
|
||||
|
||||
管理前端开发:
|
||||
|
||||
1. `integration-workflow.md`
|
||||
1. `api-response-contract.md`
|
||||
2. `admin-api-requirements.md`
|
||||
3. `home-api.md`
|
||||
4. `wanfa-api.md`
|
||||
5. `concierge-api.md`
|
||||
6. `detail-api.md`
|
||||
7. `team-building-api.md`
|
||||
8. `module-config-api.md`
|
||||
8. `public-api.md`
|
||||
|
||||
管理前端开发:
|
||||
|
||||
1. `api-response-contract.md`
|
||||
2. `integration-workflow.md`
|
||||
3. `admin-api-requirements.md`
|
||||
4. `home-api.md`
|
||||
5. `wanfa-api.md`
|
||||
6. `concierge-api.md`
|
||||
7. `detail-api.md`
|
||||
8. `team-building-api.md`
|
||||
9. `module-config-api.md`
|
||||
|
||||
MiniAPP 前台开发:
|
||||
|
||||
1. `integration-workflow.md`
|
||||
2. `wanfa-api.md`
|
||||
3. `detail-api.md`
|
||||
4. `team-building-api.md`
|
||||
5. `public-api.md`
|
||||
6. `development-status.md`
|
||||
1. `api-response-contract.md`
|
||||
2. `integration-workflow.md`
|
||||
3. `wanfa-api.md`
|
||||
4. `detail-api.md`
|
||||
5. `team-building-api.md`
|
||||
6. `public-api.md`
|
||||
7. `development-status.md`
|
||||
|
||||
## 文档清单
|
||||
|
||||
| 文档 | 作用 | 主要读者 |
|
||||
| --------------------------- | --------------------------------------------------------- | -------------- |
|
||||
| `api-response-contract.md` | 三端统一 JSON 响应包裹、错误和客户端解包规则 | 全部 |
|
||||
| `integration-workflow.md` | 三端本地启动、联调顺序、接口变更流程和验证命令 | 全部 |
|
||||
| `development-status.md` | 三端能力对接状态矩阵和优先联调路径 | 全部 |
|
||||
| `decisions.md` | 当前有效技术和文档决策 | 全部 |
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
|
||||
首页体验、团队共创和极境视界内容的字段、图片、排序与商品领域隔离约束见 [home-api.md](./home-api.md)。该文档是本主契约的首页内容补充,适用端为 `WonderQ-Admin` 和 `WonderQ-Admin-UI`。
|
||||
|
||||
所有 Admin JSON 接口遵循 [三端统一 API 响应契约](./api-response-contract.md)。
|
||||
|
||||
## 通用约定
|
||||
|
||||
- API 前缀:`/api/admin`。
|
||||
- 除登录接口外均需 `Authorization: Bearer <admin-jwt>`。
|
||||
- JSON 请求统一使用 camelCase 字段。
|
||||
- 变更接口写入审计日志后再提交事务。
|
||||
- 失败响应统一包含 `message`、`code` 和可选 `details`。
|
||||
- 成功业务结果统一放在 `data`;创建成功为 HTTP/code `201`。
|
||||
- 失败统一返回数字 `code`、用户可读 `msg`、`data: null`,业务错误码放在可选的 `errorCode`。
|
||||
|
||||
## 接口清单
|
||||
|
||||
@@ -74,7 +77,7 @@ type SiteModule =
|
||||
{ "email": "admin@example.test", "password": "<password>" }
|
||||
```
|
||||
|
||||
成功响应包含 `token` 和 `{ id, email, name, role }`。
|
||||
成功响应包裹为 `data: { token, user: { id, email, name, role } }`;具体字段结构保持现有登录接口约定。
|
||||
|
||||
## 兼容边界
|
||||
|
||||
|
||||
116
docs/api-response-contract.md
Normal file
116
docs/api-response-contract.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 三端统一 API 响应契约
|
||||
|
||||
本文档是 `WonderQ-Admin`、`WonderQ-Admin-UI`、`WonderQ-MiniAPP` 的统一 JSON 响应规范。适用于 `/health`、`/api/public/**` 和 `/api/admin/**`,新接口必须直接遵守本契约。
|
||||
|
||||
## 基本结构
|
||||
|
||||
所有 JSON 响应都必须包含 `code`、`msg` 和 `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `code` | `number` | 与 HTTP 状态码一致;成功通常为 `200`,创建成功为 `201`。 |
|
||||
| `msg` | `string` | 成功固定为 `success`;失败为用户可读的错误信息。 |
|
||||
| `data` | `object \| array \| string \| number \| boolean \| null` | 成功时承载原接口业务结果;失败时必须为 `null`。 |
|
||||
| `errorCode` | `string`,可选 | 稳定的业务错误码,使用大写蛇形命名。 |
|
||||
| `details` | `unknown`,可选 | 面向客户端的结构化错误详情,不得包含 SQL、堆栈、Token 或环境变量。 |
|
||||
|
||||
`data` 内的业务字段保持各领域文档原有结构不变。也就是说,列表的 `items`、首页的 `experiences`、玩法的 `categories` 等字段都位于响应的 `data` 内,而不是与 `code` 同级。
|
||||
|
||||
## 成功响应
|
||||
|
||||
### 查询、更新、排序
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 创建
|
||||
|
||||
创建接口返回 HTTP `201`,响应中的 `code` 也必须为 `201`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 201,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "example-id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 删除
|
||||
|
||||
删除接口仍保留原有业务结果,只放入 `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "example-id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 失败响应
|
||||
|
||||
失败响应的 HTTP 状态码和 `code` 必须相同,`data` 必须为 `null`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "未找到对应内容",
|
||||
"data": null,
|
||||
"errorCode": "RESOURCE_NOT_FOUND",
|
||||
"details": {
|
||||
"resource": "example"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`errorCode` 和 `details` 没有值时可以省略,但不能用空对象替代 `data: null`。常见错误码包括:
|
||||
|
||||
| HTTP/code | 场景 | 推荐 `errorCode` |
|
||||
| --- | --- | --- |
|
||||
| `400` | 请求参数、排序列表或字段格式错误 | `VALIDATION_ERROR` 或领域错误码 |
|
||||
| `401` | 未登录或 Token 无效 | `AUTH_REQUIRED` 或 `AUTH_INVALID` |
|
||||
| `404` | 资源不存在、停用或未配置 | 领域 `*_NOT_FOUND` |
|
||||
| `409` | 重复键、删除冲突或并发冲突 | 领域 `*_CONFLICT` |
|
||||
| `422` | 仍由业务层使用的不可处理实体 | 领域错误码 |
|
||||
| `500` | 未知服务端异常 | `INTERNAL_SERVER_ERROR` |
|
||||
|
||||
参数校验错误由后端统一转换为 `400 + VALIDATION_ERROR`;如果某个既有业务场景仍返回 `422`,也必须按本契约包装,且 `code` 必须为数字 `422`。
|
||||
|
||||
## 客户端处理
|
||||
|
||||
- `WonderQ-Admin` 负责所有路由和全局异常处理,不能把内部异常、SQL、堆栈、Token 或环境配置写入 `msg`、`details` 或响应日志。
|
||||
- `WonderQ-Admin-UI` 的公共 `request<T>` 校验包裹结构,成功只返回 `data`;失败使用 `msg` 提示,并保留 `errorCode`、`details`。
|
||||
- `WonderQ-MiniAPP` 的公共请求层执行同样校验,页面、Store 和归一化函数继续只接收业务类型,不重复读取 `data`。
|
||||
- HTTP 错误是服务端返回了合法错误包;网络错误是请求未获得 HTTP 响应;协议错误是响应缺少必填字段或字段类型不正确,三者应分别进入现有错误、重试和 fallback 流程。
|
||||
- 未包裹的旧响应不再兼容,客户端必须将其识别为协议错误。
|
||||
|
||||
## 联调检查
|
||||
|
||||
每次新增或修改接口时,至少检查:
|
||||
|
||||
1. 成功查询返回 `200`,创建返回 `201`。
|
||||
2. `code` 为数字且等于 HTTP 状态码。
|
||||
3. 成功 `msg` 为 `success`,失败 `data` 为 `null`。
|
||||
4. 列表、详情、删除和排序的原业务字段只出现在 `data` 内。
|
||||
5. `400`、`401`、`404`、`409`、`422`、`500`(适用时)都保持相同包裹结构。
|
||||
|
||||
相关领域字段和路径以 [public-api.md](./public-api.md)、[admin-api-requirements.md](./admin-api-requirements.md)、[home-api.md](./home-api.md)、[wanfa-api.md](./wanfa-api.md)、[detail-api.md](./detail-api.md)、[concierge-api.md](./concierge-api.md)、[team-building-api.md](./team-building-api.md) 和 [wild-archives-api.md](./wild-archives-api.md) 为准。
|
||||
@@ -4,6 +4,8 @@
|
||||
>
|
||||
> 状态:已实现。本文档约定 `WonderQ-Admin`、`WonderQ-Admin-UI` 的管家管理接口,并记录 MiniAPP 使用的对应 Public API。
|
||||
|
||||
所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||||
|
||||
## 领域边界
|
||||
|
||||
管家管理只维护管家顾问卡片资料:
|
||||
@@ -48,7 +50,7 @@ MiniAPP Public API:
|
||||
- 变更接口返回最新顾问对象;排序接口返回排序后的 `items`。
|
||||
- ID 由后端生成并作为非空字符串返回。
|
||||
- 空列表返回 `[]`,不能返回 `null` 或省略字段。
|
||||
- 失败响应沿用 Admin API 约定,包含 `message`、`code` 和可选的 `details`。
|
||||
- 成功结果放入 `data`;失败返回数字 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。
|
||||
|
||||
## 数据类型
|
||||
|
||||
@@ -129,23 +131,27 @@ Authorization: Bearer <admin-jwt>
|
||||
|
||||
```json
|
||||
{
|
||||
"advisors": [
|
||||
{
|
||||
"id": "advisor-001",
|
||||
"avatar": "https://example.test/assets/advisor-avatar.jpg",
|
||||
"name": "示例顾问",
|
||||
"role": "SENIOR TRAVEL ADVISOR",
|
||||
"details": [
|
||||
{ "icon": "calendar", "label": "服务经验:8年" },
|
||||
{ "icon": "navigate", "label": "擅长领域:自然探索" }
|
||||
],
|
||||
"qrImage": "https://example.test/assets/advisor-qr.png",
|
||||
"isActive": true,
|
||||
"sortOrder": 0,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"updatedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"advisors": [
|
||||
{
|
||||
"id": "advisor-001",
|
||||
"avatar": "https://example.test/assets/advisor-avatar.jpg",
|
||||
"name": "示例顾问",
|
||||
"role": "SENIOR TRAVEL ADVISOR",
|
||||
"details": [
|
||||
{ "icon": "calendar", "label": "服务经验:8年" },
|
||||
{ "icon": "navigate", "label": "擅长领域:自然探索" }
|
||||
],
|
||||
"qrImage": "https://example.test/assets/advisor-qr.png",
|
||||
"isActive": true,
|
||||
"sortOrder": 0,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"updatedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -173,7 +179,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
成功返回 `201` 和新建的 `ConciergeAdvisorRecord`。未传 `sortOrder` 时追加到当前最大顺序之后。
|
||||
成功返回 `201` 和 `data` 内新建的 `ConciergeAdvisorRecord`。未传 `sortOrder` 时追加到当前最大顺序之后。
|
||||
|
||||
### 编辑顾问
|
||||
|
||||
@@ -194,7 +200,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
成功返回更新后的 `ConciergeAdvisorRecord`。不存在的顾问返回 `404 CONCIERGE_ADVISOR_NOT_FOUND`。
|
||||
成功返回 `data` 内更新后的 `ConciergeAdvisorRecord`。不存在的顾问返回 `404`,业务码为 `CONCIERGE_ADVISOR_NOT_FOUND`。
|
||||
|
||||
### 删除顾问
|
||||
|
||||
@@ -206,7 +212,11 @@ Authorization: Bearer <admin-jwt>
|
||||
删除成功返回:
|
||||
|
||||
```json
|
||||
{ "id": "advisor-001" }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "id": "advisor-001" }
|
||||
}
|
||||
```
|
||||
|
||||
删除后应重新规范化剩余顾问的 `sortOrder`,从 `0` 开始连续编号。若业务要求至少保留一名启用顾问,服务端在删除最后一名启用顾问时返回 `409 CONCIERGE_LAST_ACTIVE_ADVISOR`,否则允许删除并由前台处理空状态。
|
||||
@@ -228,7 +238,11 @@ Content-Type: application/json
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{ "items": [] }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "items": [] }
|
||||
}
|
||||
```
|
||||
|
||||
其中 `items` 为更新 `sortOrder` 后的 `ConciergeAdvisorRecord[]`。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400 CONCIERGE_REORDER_INVALID`。
|
||||
@@ -247,7 +261,7 @@ type PublicConciergeResponse = {
|
||||
};
|
||||
```
|
||||
|
||||
顾问列表为空时返回 `{ "advisors": [] }`。MiniAPP 应在请求期间展示 loading,失败时展示错误和重试入口,响应字段缺失时通过归一化函数过滤无效顾问。
|
||||
顾问列表为空时返回 `data: { "advisors": [] }`。MiniAPP 应在请求期间展示 loading,失败时展示错误和重试入口,响应字段缺失时通过归一化函数过滤无效顾问。
|
||||
|
||||
## 图片与素材
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
`key` 固定使用玩法路线 ID(即 `WanfaRoute.id`,例如 `family-water`),但 `DetailRecord` 不建立数据库外键。详情页通过 `/pages/detail/index?routeId={key}` 定位内容;详情记录删除不会跨领域级联删除路线或其他数据。
|
||||
|
||||
所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||||
|
||||
## 与 `detailPresentation.ts` 的关系
|
||||
|
||||
`detailPresentation.ts` 是前台展示适配器,不是持久化模型:
|
||||
@@ -54,7 +56,7 @@ API 前缀为 `/api/admin`,除登录接口外均需要后台 JWT。
|
||||
- 创建和编辑返回最新详情对象;排序接口返回排序后的 `items`。
|
||||
- ID 由后端生成并作为非空字符串返回;`key` 由调用方提供并保持稳定。
|
||||
- 空列表返回 `[]`,不能返回 `null` 或省略字段。
|
||||
- 失败响应沿用 Admin API 约定,包含 `message`、`code` 和可选的 `details`。
|
||||
- 成功结果放入 `data`;失败返回数字 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。
|
||||
|
||||
## 数据类型
|
||||
|
||||
@@ -141,26 +143,30 @@ Authorization: Bearer <admin-jwt>
|
||||
|
||||
```json
|
||||
{
|
||||
"details": [
|
||||
{
|
||||
"id": "detail-001",
|
||||
"key": "classic-panorama",
|
||||
"eyebrow": "玩法推荐",
|
||||
"duration": "5天4晚",
|
||||
"title": "经典贵州全景",
|
||||
"subtitle": "贵州·瀑布、苗寨、古城与山地风光",
|
||||
"intro": "沿着贵州山地的自然纹理深入探索。",
|
||||
"highlights": ["核心景观串联", "小团出行,按同行人节奏调整"],
|
||||
"included": ["行程内用车与接送服务"],
|
||||
"excluded": ["往返大交通及个人消费"],
|
||||
"notes": ["贵州多山多雨,请准备防滑鞋和轻便雨具。"],
|
||||
"gallery": ["https://example.test/assets/detail-01.jpg"],
|
||||
"isActive": true,
|
||||
"sortOrder": 0,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"updatedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"details": [
|
||||
{
|
||||
"id": "detail-001",
|
||||
"key": "classic-panorama",
|
||||
"eyebrow": "玩法推荐",
|
||||
"duration": "5天4晚",
|
||||
"title": "经典贵州全景",
|
||||
"subtitle": "贵州·瀑布、苗寨、古城与山地风光",
|
||||
"intro": "沿着贵州山地的自然纹理深入探索。",
|
||||
"highlights": ["核心景观串联", "小团出行,按同行人节奏调整"],
|
||||
"included": ["行程内用车与接送服务"],
|
||||
"excluded": ["往返大交通及个人消费"],
|
||||
"notes": ["贵州多山多雨,请准备防滑鞋和轻便雨具。"],
|
||||
"gallery": ["https://example.test/assets/detail-01.jpg"],
|
||||
"isActive": true,
|
||||
"sortOrder": 0,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"updatedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -171,7 +177,7 @@ GET /api/admin/details/{detailId}
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
成功返回单个 `DetailRecord`。记录不存在返回 `404 DETAIL_NOT_FOUND`。
|
||||
成功返回 `data` 内的单个 `DetailRecord`。记录不存在返回 `404`,业务码为 `DETAIL_NOT_FOUND`。
|
||||
|
||||
### 新增详情
|
||||
|
||||
@@ -181,7 +187,7 @@ Authorization: Bearer <admin-jwt>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体为 `DetailCreate`。成功返回 `201` 和新建的 `DetailRecord`;未传 `sortOrder` 时追加到当前列表末尾。
|
||||
请求体为 `DetailCreate`。成功返回 `201` 和 `data` 内新建的 `DetailRecord`;未传 `sortOrder` 时追加到当前列表末尾。
|
||||
|
||||
### 编辑详情
|
||||
|
||||
@@ -191,7 +197,7 @@ Authorization: Bearer <admin-jwt>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体为 `DetailPatch`,只更新提交的字段。修改 `key` 时仍须保证全局唯一。成功返回更新后的 `DetailRecord`;记录不存在返回 `404 DETAIL_NOT_FOUND`,`key` 冲突返回 `409 DETAIL_KEY_EXISTS`。
|
||||
请求体为 `DetailPatch`,只更新提交的字段。修改 `key` 时仍须保证全局唯一。成功返回 `data` 内更新后的 `DetailRecord`;记录不存在返回 `404`,业务码为 `DETAIL_NOT_FOUND`,`key` 冲突返回 `409`,业务码为 `DETAIL_KEY_EXISTS`。
|
||||
|
||||
### 删除详情
|
||||
|
||||
@@ -203,7 +209,11 @@ Authorization: Bearer <admin-jwt>
|
||||
成功返回:
|
||||
|
||||
```json
|
||||
{ "id": "detail-001" }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "id": "detail-001" }
|
||||
}
|
||||
```
|
||||
|
||||
删除后应重新规范化剩余记录的 `sortOrder`,从 `0` 开始连续编号。详情记录没有 Product、订单或线索外键,因此删除不触发跨领域级联操作。
|
||||
@@ -225,7 +235,11 @@ Content-Type: application/json
|
||||
成功返回:
|
||||
|
||||
```json
|
||||
{ "items": [] }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "items": [] }
|
||||
}
|
||||
```
|
||||
|
||||
其中 `items` 为更新 `sortOrder` 后的 `DetailRecord[]`。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400 DETAIL_REORDER_INVALID`。
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
当前首页通过 `GET /api/public/home` 消费四类内容;三个卡片 mock 数组仍作为接口失败、空响应或字段缺失时的前台 fallback,玩法推荐无本地模拟数据时保持空态。Admin UI 通过本文件列出的 Admin API 维护正式数据。
|
||||
|
||||
本文件所有 JSON 示例的业务对象均位于统一响应的 `data` 字段内,完整包裹格式见 [api-response-contract.md](./api-response-contract.md)。
|
||||
|
||||
## 接口清单
|
||||
|
||||
API 前缀为 `/api/admin`,除登录接口外均需要后台 JWT。
|
||||
@@ -69,7 +71,7 @@ MiniAPP Public API:
|
||||
- ID 由后端生成并作为非空字符串返回。初始化迁移时应优先保留三个 mock 文件中的稳定 ID。
|
||||
- 空集合返回 `[]`,不能返回 `null` 或省略字段。
|
||||
- 所有资源的 `sortOrder` 从 `0` 开始,数值越小越靠前;新增未传排序时追加到末尾。
|
||||
- 失败响应沿用 Admin API 主契约,包含 `message`、`code` 和可选的 `details`。
|
||||
- 成功结果放入 `data`;失败返回数字 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。
|
||||
|
||||
## 数据类型
|
||||
|
||||
@@ -227,25 +229,29 @@ Authorization: Bearer <admin-jwt>
|
||||
|
||||
团队共创和极境视界分别使用 `/api/admin/home/team-buildings`、`/api/admin/home/wild-archives`。
|
||||
|
||||
成功响应示例:
|
||||
成功响应示例(统一响应包裹):
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "waterfall-descent",
|
||||
"badge": "玩过推荐",
|
||||
"category": "瀑降体验",
|
||||
"title": "悬崖瀑降",
|
||||
"englishTitle": "WATERFALL DESCENT",
|
||||
"image": "https://example.test/assets/waterfall-descent.jpg",
|
||||
"demandKeyword": "悬崖瀑降",
|
||||
"isActive": true,
|
||||
"sortOrder": 0,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"updatedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "waterfall-descent",
|
||||
"badge": "玩过推荐",
|
||||
"category": "瀑降体验",
|
||||
"title": "悬崖瀑降",
|
||||
"englishTitle": "WATERFALL DESCENT",
|
||||
"image": "https://example.test/assets/waterfall-descent.jpg",
|
||||
"demandKeyword": "悬崖瀑降",
|
||||
"isActive": true,
|
||||
"sortOrder": 0,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"updatedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -253,9 +259,9 @@ Authorization: Bearer <admin-jwt>
|
||||
|
||||
### 新增、编辑与删除
|
||||
|
||||
- `POST /api/admin/home/{resource}`:请求体为对应资源的 `Create` 类型,成功返回 `201` 和新建记录;未传 `sortOrder` 时追加到末尾。
|
||||
- `PATCH /api/admin/home/{resource}/{id}`:请求体为对应资源的 `Patch` 类型,只更新提交字段;成功返回更新后的记录,不存在返回 `404`。
|
||||
- `DELETE /api/admin/home/{resource}/{id}`:成功返回 `{ "id": "..." }`,删除后重新规范化同一资源剩余记录的 `sortOrder`。
|
||||
- `POST /api/admin/home/{resource}`:请求体为对应资源的 `Create` 类型,成功返回 `201` 和 `data` 内的新建记录;未传 `sortOrder` 时追加到末尾。
|
||||
- `PATCH /api/admin/home/{resource}/{id}`:请求体为对应资源的 `Patch` 类型,只更新提交字段;成功返回 `data` 内的更新记录,不存在返回 `404`。
|
||||
- `DELETE /api/admin/home/{resource}/{id}`:成功返回 `data: { "id": "..." }`,删除后重新规范化同一资源剩余记录的 `sortOrder`。
|
||||
|
||||
其中 `{resource}` 只能是 `experiences`、`team-buildings` 或 `wild-archives`;对应路径参数分别为 `experienceId`、`teamBuildingId`、`archiveId`。删除不触发商品、订单、预订或线索级联操作。
|
||||
|
||||
@@ -273,7 +279,7 @@ Content-Type: application/json
|
||||
{ "itemIds": ["cave-exploration", "waterfall-descent"] }
|
||||
```
|
||||
|
||||
成功响应为 `{ "items": [] }`,其中 `items` 是更新 `sortOrder` 后的完整记录列表。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400 HOME_REORDER_INVALID`。
|
||||
成功响应为 `data: { "items": [] }`,其中 `items` 是更新 `sortOrder` 后的完整记录列表。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400`,业务码为 `HOME_REORDER_INVALID`。
|
||||
|
||||
### MiniAPP Public API
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
| 管理前端 | `WonderQ-Admin-UI` | 维护首页结构、目的地、线索和页面模块配置 | `admin-api-requirements.md`、`module-config-api.md` |
|
||||
| 前台 MiniAPP | `WonderQ-MiniAPP` | H5 与微信小程序前台展示、咨询和线索提交 | `public-api.md` |
|
||||
|
||||
三端所有 JSON 接口还必须遵守 [api-response-contract.md](./api-response-contract.md):成功业务数据位于 `data`,失败为 `data: null`,`code` 必须等于 HTTP 状态码。
|
||||
|
||||
## 本地启动顺序
|
||||
|
||||
1. 确认 Docker Desktop 已运行,然后启动后端依赖和数据库迁移。
|
||||
@@ -72,6 +74,7 @@ yarn dev:mp-weixin
|
||||
- `GET /health` 返回服务健康状态。
|
||||
- `GET /api/public/site-config` 返回前台所需数组字段。
|
||||
- `POST /api/admin/auth/login` 能返回 token 和 user。
|
||||
- 使用客户端或 curl 检查响应顶层包含数字 `code`、字符串 `msg` 和 `data`;不能继续接受旧的未包裹响应。
|
||||
|
||||
管理端联调重点:
|
||||
|
||||
@@ -84,6 +87,7 @@ MiniAPP 联调重点:
|
||||
- `site-config` 失败时仍能回退本地内容。
|
||||
- 首页模块按 Public API 字段渲染,不依赖后台未发布或未启用数据。
|
||||
- 线索提交调用 `POST /api/public/leads`,失败时显示可理解错误。
|
||||
- 首页、玩法、路线详情、管家、团队共创和客片案例请求都由公共 API 层解包 `data`,页面不重复解包。
|
||||
|
||||
## 路线详情三端联调
|
||||
|
||||
@@ -101,6 +105,7 @@ MiniAPP 联调重点:
|
||||
## 接口变更流程
|
||||
|
||||
1. 先更新契约文档。
|
||||
- 响应包裹、错误结构或状态码变化:更新 `api-response-contract.md`。
|
||||
- Public API 变更:更新 `public-api.md`。
|
||||
- Admin API 变更:更新 `admin-api-requirements.md`。
|
||||
2. 后端实现或调整接口,并补充对应验证。
|
||||
|
||||
58
docs/module-config-api.md
Normal file
58
docs/module-config-api.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 页面模块配置 API 契约
|
||||
|
||||
本文档补充 `WonderQ-Admin` 和 `WonderQ-Admin-UI` 对站点页面模块的维护约定。接口路径保持现有实现不变,所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md)。
|
||||
|
||||
## 接口清单
|
||||
|
||||
除登录接口外,所有接口需要 `Authorization: Bearer <admin-jwt>`。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/admin/site-config` | 获取全部模块和停用记录 |
|
||||
| `POST` | `/api/admin/site-config/{module}` | 新增模块项,成功 `201` |
|
||||
| `PATCH` | `/api/admin/site-config/{module}/{id}` | 更新模块项 |
|
||||
| `DELETE` | `/api/admin/site-config/{module}/{id}` | 删除模块项 |
|
||||
| `PATCH` | `/api/admin/site-config/{module}/reorder` | 按完整 ID 列表排序 |
|
||||
|
||||
允许的 `module`:`heroSlides`、`destinationHero`、`demandHero`、`demandFeatureCards`、`demandForm`、`vehicleOptions`。
|
||||
|
||||
## 响应约定
|
||||
|
||||
列表、详情、删除和排序的业务字段放在 `data` 内:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"heroSlides": [],
|
||||
"destinationHero": [],
|
||||
"demandHero": [],
|
||||
"demandFeatureCards": [],
|
||||
"demandForm": [],
|
||||
"vehicleOptions": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
创建接口返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 201,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "module-item-001"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
排序请求必须提交当前模块的完整 `itemIds`,不能重复;成功返回 `data: { "items": [] }`。删除成功返回 `data: { "id": "..." }`。参数错误、资源不存在和服务异常分别使用统一契约的 `400`、`404` 和 `500` 响应。
|
||||
|
||||
## 字段边界
|
||||
|
||||
- `GET` 返回启用和停用的完整记录,前端负责显示状态。
|
||||
- `sortOrder` 为从 `0` 开始的非负整数,后端负责重新规范化。
|
||||
- 图片字段保存最终 HTTP(S) URL,不接受 base64。
|
||||
- `demandForm` 为单例模块,不执行无意义的排序。
|
||||
- 站点模块只负责站点配置;首页内容、玩法、详情、管家和客片案例使用各自领域文档。
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- API 前缀:`/api/public`。
|
||||
- 响应使用 JSON;时间使用 ISO 8601 字符串。
|
||||
- 所有 `/health` 和 `/api/public/**` JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||||
- H5 本地开发通过 `/api` 代理访问后端。
|
||||
- 内容接口失败时,MiniAPP 使用 `src/content.ts` 的本地兜底内容。
|
||||
|
||||
@@ -137,7 +138,7 @@ type PublicConciergeResponse = {
|
||||
}
|
||||
```
|
||||
|
||||
无可用顾问时返回 `{ "advisors": [] }`。MiniAPP 应处理 loading、错误、重试和空态,不能依赖固定顾问姓名或本地模拟数组。
|
||||
无可用顾问时返回 `data: { "advisors": [] }`。MiniAPP 应处理 loading、错误、重试和空态,不能依赖固定顾问姓名或本地模拟数组。
|
||||
|
||||
## 首页内容
|
||||
|
||||
@@ -265,8 +266,12 @@ type DemandForm = {
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<customer-jwt>",
|
||||
"customer": { "id": "customer-id", "phoneMasked": "138****0000" }
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"token": "<customer-jwt>",
|
||||
"customer": { "id": "customer-id", "phoneMasked": "138****0000" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -277,5 +282,9 @@ type DemandForm = {
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{ "id": "customer-id", "phoneMasked": "138****0000" }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "id": "customer-id", "phoneMasked": "138****0000" }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
> 适用端:`WonderQ-Admin`、`WonderQ-Admin-UI`、`WonderQ-MiniAPP`。
|
||||
> 目标:复用 `HomeTeamBuilding` 表,维护首页团队共创卡片和沉浸式详情页内容。
|
||||
|
||||
所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||||
|
||||
## 领域边界
|
||||
|
||||
- 团队共创卡片和详情共用一条 `HomeTeamBuilding` 记录。
|
||||
@@ -121,6 +123,8 @@ type PublicHomeTeamBuildingDetail = PublicHomeTeamBuildingSummary & {
|
||||
};
|
||||
```
|
||||
|
||||
上面的 `PublicHomeTeamBuildingDetail` 是统一响应 `data` 内的业务对象,不是完整 HTTP 响应包。失败响应使用 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。
|
||||
|
||||
错误响应:
|
||||
|
||||
| 状态码 | 场景 |
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
当前实现:`WonderQ-Admin` 通过迁移 `0015_wanfa` 创建 `WanfaCategory`、`WanfaRoute` 表并导入稳定初始 ID;`WonderQ-Admin-UI` 已接入分类和路线的查询、新增、编辑、删除及排序操作。
|
||||
|
||||
所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||||
|
||||
路线接口只维护分类和路线摘要字段。路线详情不写入 `WanfaRoute`,由独立 `DetailRecord` 通过 `docs/detail-api.md` 管理,详情记录的 `key` 等于路线 ID。首页玩法推荐和玩法页路线点击后统一跳转 `/pages/detail/index?routeId={route.id}`;无关联路线时才回退到需求页。
|
||||
|
||||
## 领域边界
|
||||
@@ -51,7 +53,7 @@ API 前缀为 `/api/admin`,除登录接口外均需要后台 JWT。
|
||||
- ID 由后端生成并作为非空字符串返回。初始化迁移时应优先保留 `playData.ts` 中已有的稳定 ID。
|
||||
- 分类和路线的数组顺序就是管理端和前台的展示顺序,接口不要求前端依赖 `sortOrder` 字段。
|
||||
- 空集合返回 `[]`,不能返回 `null` 或省略字段。
|
||||
- 失败响应沿用 Admin API 约定,包含 `message`、`code` 和可选的 `details`。
|
||||
- 成功结果放入 `data`;失败返回数字 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。
|
||||
- 被首页玩法推荐关联的分类不能直接删除;需先调用首页内容域的移除关联接口,否则返回 `409 WANFA_CATEGORY_RECOMMENDED`。
|
||||
|
||||
## 数据类型
|
||||
@@ -130,22 +132,26 @@ Authorization: Bearer <admin-jwt>
|
||||
|
||||
```json
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"id": "family-route",
|
||||
"label": "亲子路线",
|
||||
"routes": [
|
||||
{
|
||||
"id": "family-water",
|
||||
"title": "亲子玩水",
|
||||
"subtitle": "贵州·轻松节奏与自然课堂",
|
||||
"image": "https://example.test/assets/family-water.jpg",
|
||||
"routeCount": 4,
|
||||
"demandKeyword": "亲子玩水"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"categories": [
|
||||
{
|
||||
"id": "family-route",
|
||||
"label": "亲子路线",
|
||||
"routes": [
|
||||
{
|
||||
"id": "family-water",
|
||||
"title": "亲子玩水",
|
||||
"subtitle": "贵州·轻松节奏与自然课堂",
|
||||
"image": "https://example.test/assets/family-water.jpg",
|
||||
"routeCount": 4,
|
||||
"demandKeyword": "亲子玩水"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -163,7 +169,7 @@ Content-Type: application/json
|
||||
{ "label": "亲子路线" }
|
||||
```
|
||||
|
||||
成功返回 `201` 和新分类对象,初始 `routes` 为 `[]`,并追加到分类列表末尾。
|
||||
成功返回 `201` 和 `data` 内的新分类对象,初始 `routes` 为 `[]`,并追加到分类列表末尾。
|
||||
|
||||
### 编辑分类
|
||||
|
||||
@@ -179,7 +185,7 @@ Content-Type: application/json
|
||||
{ "label": "家庭路线" }
|
||||
```
|
||||
|
||||
成功返回更新后的 `WanfaCategory`。不存在的分类返回 `404 WANFA_CATEGORY_NOT_FOUND`。
|
||||
成功返回 `data` 内的更新后 `WanfaCategory`。不存在的分类返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND`。
|
||||
|
||||
### 删除分类
|
||||
|
||||
@@ -188,10 +194,14 @@ DELETE /api/admin/wanfa/categories/{categoryId}
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
为避免误删路线,分类仍包含路线时不得级联删除,应返回 `409 WANFA_CATEGORY_NOT_EMPTY`。删除前由 Admin UI 提示先移除分类内路线。删除成功返回:
|
||||
为避免误删路线,分类仍包含路线时不得级联删除,应返回 `409`,业务码为 `WANFA_CATEGORY_NOT_EMPTY`。删除前由 Admin UI 提示先移除分类内路线。删除成功返回:
|
||||
|
||||
```json
|
||||
{ "id": "family-route" }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "id": "family-route" }
|
||||
}
|
||||
```
|
||||
|
||||
### 分类排序
|
||||
@@ -211,10 +221,14 @@ Content-Type: application/json
|
||||
成功返回:
|
||||
|
||||
```json
|
||||
{ "items": [] }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "items": [] }
|
||||
}
|
||||
```
|
||||
|
||||
其中 `items` 为排序后的 `WanfaCategory[]`。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400 WANFA_CATEGORY_REORDER_INVALID`。
|
||||
其中 `items` 为排序后的 `WanfaCategory[]`。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400`,业务码为 `WANFA_CATEGORY_REORDER_INVALID`。
|
||||
|
||||
### 新增路线
|
||||
|
||||
@@ -236,7 +250,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
成功返回 `201` 和新建的 `WanfaRoute`,并追加到对应分类路线末尾。分类不存在返回 `404 WANFA_CATEGORY_NOT_FOUND`。
|
||||
成功返回 `201` 和 `data` 内新建的 `WanfaRoute`,并追加到对应分类路线末尾。分类不存在返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND`。
|
||||
|
||||
### 编辑路线
|
||||
|
||||
@@ -246,7 +260,7 @@ Authorization: Bearer <admin-jwt>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体为 `WanfaRoutePatch`,只更新提交的字段。成功返回更新后的 `WanfaRoute`;分类或路线不存在时分别返回 `404 WANFA_CATEGORY_NOT_FOUND` 或 `404 WANFA_ROUTE_NOT_FOUND`。
|
||||
请求体为 `WanfaRoutePatch`,只更新提交的字段。成功返回 `data` 内更新后的 `WanfaRoute`;分类或路线不存在时分别返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND` 或 `WANFA_ROUTE_NOT_FOUND`。
|
||||
|
||||
### 删除路线
|
||||
|
||||
@@ -255,7 +269,7 @@ DELETE /api/admin/wanfa/categories/{categoryId}/routes/{routeId}
|
||||
Authorization: Bearer <admin-jwt>
|
||||
```
|
||||
|
||||
成功返回 `{ "id": "..." }`。删除后,分类内路线保持原有相对顺序。
|
||||
成功返回 `data: { "id": "..." }`。删除后,分类内路线保持原有相对顺序。
|
||||
|
||||
### 分类内路线排序
|
||||
|
||||
@@ -274,10 +288,14 @@ Content-Type: application/json
|
||||
成功返回排序后的路线:
|
||||
|
||||
```json
|
||||
{ "items": [] }
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": { "items": [] }
|
||||
}
|
||||
```
|
||||
|
||||
分类不存在返回 `404 WANFA_CATEGORY_NOT_FOUND`;路线 ID 不完整、重复或不属于该分类时返回 `400 WANFA_ROUTE_REORDER_INVALID`。
|
||||
分类不存在返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND`;路线 ID 不完整、重复或不属于该分类时返回 `400`,业务码为 `WANFA_ROUTE_REORDER_INVALID`。
|
||||
|
||||
## 当前本地数据映射
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
>
|
||||
> 目标:维护“极境视界”首页卡片,并提供客片案例更多列表和案例详情页面。
|
||||
|
||||
所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||||
|
||||
## 领域边界
|
||||
|
||||
客片案例是首页内容领域的一类展示内容,不属于商品、Product、ProductImage、订单或预订领域。
|
||||
@@ -95,15 +97,19 @@ GET /api/public/home/wild-archives
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "shilong-cave",
|
||||
"title": "石龙洞——客片案例",
|
||||
"image": "https://example.test/cover.jpg",
|
||||
"demandKeyword": "地心探险",
|
||||
"photoCount": 5
|
||||
}
|
||||
]
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "shilong-cave",
|
||||
"title": "石龙洞——客片案例",
|
||||
"image": "https://example.test/cover.jpg",
|
||||
"demandKeyword": "地心探险",
|
||||
"photoCount": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -117,15 +123,19 @@ GET /api/public/home/wild-archives/{archiveId}
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "shilong-cave",
|
||||
"title": "石龙洞——客片案例",
|
||||
"image": "https://example.test/cover.jpg",
|
||||
"demandKeyword": "地心探险",
|
||||
"photoCount": 5,
|
||||
"images": [
|
||||
"https://example.test/cover.jpg",
|
||||
"https://example.test/cave-1.jpg"
|
||||
]
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": "shilong-cave",
|
||||
"title": "石龙洞——客片案例",
|
||||
"image": "https://example.test/cover.jpg",
|
||||
"demandKeyword": "地心探险",
|
||||
"photoCount": 5,
|
||||
"images": [
|
||||
"https://example.test/cover.jpg",
|
||||
"https://example.test/cave-1.jpg"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user