Files
WonderQ-Project/docs/public-api.md
duanshuwen e082bd2d98 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文档,调整文档分类顺序将响应契约置于首位
2026-08-19 22:02:20 +08:00

291 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WonderQ MiniAPP Public API
本文档是 `WonderQ-MiniAPP` 当前使用的 Public API 契约。接口提供站点内容、首页卡片、玩法展示、管家展示、登录和出行需求提交能力。
## 基础约定
- API 前缀:`/api/public`。
- 响应使用 JSON;时间使用 ISO 8601 字符串。
- 所有 `/health` 和 `/api/public/**` JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
- H5 本地开发通过 `/api` 代理访问后端。
- 内容接口失败时,MiniAPP 使用 `src/content.ts` 的本地兜底内容。
## 接口清单
| 方法 | 路径 | 鉴权 | 用途 |
| ------ | ------------------------------ | ------------ | ------------------ |
| `GET` | `/health` | 否 | 服务健康检查 |
| `GET` | `/api/public/site-config` | 否 | 获取启用的站点内容 |
| `GET` | `/api/public/home` | 否 | 获取已启用的首页内容和玩法推荐 |
| `GET` | `/api/public/home/team-buildings/{teamBuildingId}` | 否 | 获取团队共创详情 |
| `GET` | `/api/public/home/wild-archives` | 否 | 获取客片案例更多列表 |
| `GET` | `/api/public/home/wild-archives/{archiveId}` | 否 | 获取客片案例详情和图片 |
| `GET` | `/api/public/wanfa/categories` | 否 | 获取玩法分类和路线 |
| `GET` | `/api/public/details/{key}` | 否 | 获取玩法路线详情 |
| `GET` | `/api/public/concierge/advisors` | 否 | 获取已启用的管家顾问 |
| `POST` | `/api/public/auth/phone-login` | 否 | 微信手机号登录 |
| `GET` | `/api/public/auth/me` | Customer JWT | 获取当前客户 |
| `POST` | `/api/public/leads` | 否 | 提交出行需求 |
## 站点配置
`GET /api/public/site-config` 返回以下稳定数组字段:
```ts
type SiteConfig = {
heroSlides: HeroSlide[];
destinationHero: DestinationHero[];
vehicleOptions: VehicleOption[];
demandHero: DemandHero[];
demandFeatureCards: DemandFeatureCard[];
demandForm: DemandForm[];
};
```
Public 响应只返回启用内容,其余模块按 `isActive` 过滤。
## 玩法展示
### `GET /api/public/wanfa/categories`
无需鉴权。接口按后台维护的分类和路线顺序返回 MiniAPP 玩法页所需的最小展示字段,不返回后台排序、审计和时间字段。
成功响应:
```ts
type PublicWanfaResponse = {
categories: PublicWanfaCategory[];
};
type PublicWanfaCategory = {
id: string;
label: string;
routes: PublicWanfaRoute[];
};
type PublicWanfaRoute = {
id: string;
title: string;
subtitle: string;
image: string;
routeCount: number;
demandKeyword: string;
};
```
MiniAPP 使用 `demandKeyword` 作为需求页预填关键词;接口失败或响应为空时,玩法页展示对应的错误、重试或空态,不再读取 `playData.ts` 模拟数据。
### 路线详情
`GET /api/public/details/{key}` 无需鉴权,`key` 使用玩法路线 ID,例如 `family-water`。成功响应只返回详情展示字段:
```ts
type PublicDetail = {
key: string;
eyebrow: string;
duration: string;
title: string;
subtitle: string;
intro: string;
highlights: string[];
included: string[];
excluded: string[];
notes: string[];
gallery: string[];
};
```
不存在、停用或未配置详情返回 `404`。MiniAPP 路线详情页通过 `/pages/detail/index?routeId={key}` 进入;接口失败或字段不完整时按路线 ID 使用本地网络图片和模拟文案。该详情页暂不提供价格、收藏、在线订阅、预订、订单或管家联系动作。
## 管家展示
### `GET /api/public/concierge/advisors`
无需鉴权。接口只返回后台启用的管家顾问,按后台维护顺序返回;不返回管理端 ID、启停状态、排序元数据和时间字段。
```ts
type PublicConciergeDetail = {
icon: string;
label: string;
};
type PublicConciergeAdvisor = {
avatar: string;
name: string;
role: string;
details: PublicConciergeDetail[];
qrImage: string;
};
type PublicConciergeResponse = {
advisors: PublicConciergeAdvisor[];
};
```
成功响应示例:
```json
{
"advisors": [
{
"avatar": "https://example.test/assets/advisor-avatar.jpg",
"name": "示例顾问",
"role": "SENIOR TRAVEL ADVISOR",
"details": [{ "icon": "calendar", "label": "服务经验:8年" }],
"qrImage": "https://example.test/assets/advisor-qr.png"
}
]
}
```
无可用顾问时返回 `data: { "advisors": [] }`。MiniAPP 应处理 loading、错误、重试和空态,不能依赖固定顾问姓名或本地模拟数组。
## 首页内容
### `GET /api/public/home`
无需鉴权。接口只返回后台启用的首页体验推荐、团队共创和极境视界内容,按后台排序返回,不暴露管理元数据。
```ts
type PublicHomeResponse = {
experiences: HomeExperience[];
teamBuildings: HomeTeamBuilding[];
wildArchives: HomeWildArchive[];
playRecommendations: HomeWanfaRecommendation[];
};
type HomeWanfaRecommendation = {
id: string;
categoryId: string;
label: string;
routes: WanfaRoute[];
};
type WanfaRoute = {
id: string;
title: string;
subtitle: string;
image: string;
routeCount: number;
demandKeyword: string;
};
type HomeExperience = {
id: string;
badge: string;
category: string;
title: string;
englishTitle: string;
image: string;
demandKeyword: string;
};
type HomeTeamBuilding = {
id: string;
tag: string;
title: string;
description: string;
image: string;
demandKeyword: string;
};
type PublicHomeTeamBuildingDetail = HomeTeamBuilding & {
detailSubtitle: string;
detailParagraphs: string[];
};
type HomeWildArchive = {
id: string;
title: string;
image: string;
demandKeyword: string;
photoCount: number;
};
```
无可用内容时,`experiences`、`teamBuildings`、`wildArchives` 和 `playRecommendations` 均返回空数组。玩法推荐由首页内容域关联玩法分类后生成,接口只返回启用的关联及分类当前路线;MiniAPP 接口失败或字段不完整时使用首页对应 fallback,玩法推荐缺省为空态。
客片案例更多列表使用 `GET /api/public/home/wild-archives`,返回同样的摘要字段;详情使用 `GET /api/public/home/wild-archives/{archiveId}`,在摘要字段基础上增加 `images: string[]`。首页卡片点击详情,`查看更多` 点击案例列表,不再跳转需求页。
### 团队共创详情
`GET /api/public/home/team-buildings/{teamBuildingId}` 无需鉴权,只返回启用的团队共创详情。响应包含首页摘要字段,以及 `detailSubtitle` 和 `detailParagraphs`。不存在或已停用返回 `404`。
详情页接口失败时,MiniAPP 按 ID 使用 `homeTeamBuildingData.ts` 中的网络图片和模拟正文 fallback,并显示接口不可用提示;首页 `/api/public/home` 不返回 `detailParagraphs`,避免首页请求携带长正文。
### 通用字段
站点模块通常包含 `id`、`createdAt`、`updatedAt`、`isActive` 和 `sortOrder`。客户端按 `sortOrder` 消费排序模块,不依赖固定 ID。
### 需求配置
```ts
type DemandHero = {
id: string;
title: string;
kicker: string | null;
description: string | null;
steps: string[];
isActive: boolean;
sortOrder: number;
};
type DemandFeatureCard = {
id: string;
title: string;
description: string | null;
isActive: boolean;
sortOrder: number;
};
type DemandForm = {
id: string;
destinationLabel: string;
destinationPlaceholder: string | null;
phoneLabel: string;
phonePlaceholder: string | null;
noteLabel: string;
notePlaceholder: string | null;
submitLabel: string;
chips: string[];
isActive: boolean;
};
```
## 登录接口
### `POST /api/public/auth/phone-login`
请求:
```json
{ "code": "wechat-phone-code" }
```
成功响应:
```json
{
"code": 200,
"msg": "success",
"data": {
"token": "<customer-jwt>",
"customer": { "id": "customer-id", "phoneMasked": "138****0000" }
}
}
```
### `GET /api/public/auth/me`
请求头:`Authorization: Bearer <customer-jwt>`。
成功响应:
```json
{
"code": 200,
"msg": "success",
"data": { "id": "customer-id", "phoneMasked": "138****0000" }
}
```