feat: Add WonderQ-MiniAPP Public API documentation

- Introduced a comprehensive API contract for the WonderQ-MiniAPP, detailing endpoints for site configuration, product listings, and lead submissions.
- Defined data types for various entities including HeroSlide, Destination, Theme, CtaBanner, PublicProduct, and more.
- Specified request and response formats, including error handling guidelines.

chore: Update requirements to include python-multipart

- Added python-multipart dependency to requirements.txt for handling file uploads.

test: Implement API contract tests

- Created test suite for API contracts, validating serializers and endpoints for public products and leads.
- Included tests for destination and product serializers, ensuring correct data handling and validation.

test: Add configuration tests for OSS settings

- Implemented tests to verify that OSS settings are correctly loaded from environment variables.
This commit is contained in:
duanshuwen
2026-07-01 16:55:00 +08:00
parent 75f5a5a48b
commit a47b4b5dd0
14 changed files with 2210 additions and 104 deletions

View File

@@ -3,6 +3,8 @@
本目录存放后端 API 相关文档。
- `backend/README.md`:当前 Python + FastAPI 后端运行说明。
- `admin-ui-api-requirements.md`:后台管理前端当前必需的 Admin API 对接需求与字段契约。
- `miniapp-public-api.md`WonderQ-MiniAPP 前台对接 Public API 契约。
- `admin-backend-plan.md`:早期后台建设规划,保留作业务范围和阶段规划参考;其中技术栈建议已被当前 Python 迁移方案取代。
后台管理前端文档位于 `D:\www\znkj\WonderQ-Admin-UI\docs`

View File

@@ -0,0 +1,506 @@
# WonderQ-Admin-UI Admin API 接口需求
本文档用于指导 `WonderQ-Admin` 后端按当前 `WonderQ-Admin-UI` 管理端完成 Admin API 对接。接口需求来源于前端 `src/api.ts``src/App.tsx` 的实际类型、请求封装和页面调用。
## 范围
- 本文只覆盖当前后台管理前端必需的 Admin API。
- 不包含 H5 Public API、订单、媒体库、审计日志、活动专题等后续规划能力。
- 后端现有 `GET /api/admin/me``GET /api/admin/media-assets` 不在当前 UI 必需范围内。
## 通用约定
- 基础路径:`/api/admin`
- 请求与响应均使用 JSON。
-`POST /api/admin/auth/login` 外,其余接口都需要后台登录态。
- 登录成功后前端会把返回的 `token` 存入本地,并在后续请求头中发送:
```http
Authorization: Bearer <token>
Content-Type: application/json
```
- 日期时间字段返回 ISO 8601 字符串。
- ID 字段按字符串处理。
- 前端错误提示优先读取响应体的 `message` 字段;如后端沿用 FastAPI 默认 `detail`,建议同时兼容输出 `message`,避免管理端展示兜底错误。
## 当前对接状态
| 接口 | 前端依赖 | 后端现状 | 备注 |
| --- | --- | --- | --- |
| `POST /api/admin/auth/login` | 登录页 | 已覆盖 | 返回 token 和 user |
| `GET /api/admin/dashboard` | 客户端已封装 | 已覆盖 | 当前 UI 暂未展示 |
| `GET /api/admin/products` | 商品维护、结构维护 | 已覆盖 | UI 当前只传 `keyword` |
| `POST /api/admin/products` | 新建商品 | 已覆盖 | 返回完整 Product |
| `PATCH /api/admin/products/{id}` | 编辑商品 | 已覆盖 | 返回完整 Product |
| `GET /api/admin/destinations` | 商品目的地下拉、目的地页 | 已覆盖 | 需要返回别名和商品数 |
| `GET /api/admin/site-config` | 首页/目的地/活动结构维护 | 已覆盖 | 需要包含未启用内容 |
| `PATCH /api/admin/site-config/{module}/{item_id}` | 模块内容编辑 | 已覆盖 | 模块名需保持一致 |
| `GET /api/admin/leads` | 需求线索页 | 已覆盖 | UI 当前不传筛选参数 |
| `PATCH /api/admin/leads/{id}/status` | 线索状态流转 | 已覆盖 | UI 更新后会重新拉列表 |
| `POST /api/admin/publish` | 结构维护发布 | 已覆盖 | UI 使用 `title` 提示发布结果 |
| `POST /api/admin/reset-guizhou-content` | 贵州内容重置 | 已覆盖 | 高风险操作,需鉴权和审计 |
## 枚举
### ProductStatus
```ts
type ProductStatus = "draft" | "published" | "archived";
```
### LeadStatus
```ts
type LeadStatus = "new" | "assigned" | "contacted" | "planning" | "won" | "invalid";
```
### SiteModule
```ts
type SiteModule = "heroSlides" | "destinations" | "themes" | "ctaBanners";
```
## 公共数据结构
### Destination
```ts
type Destination = {
id: string;
name: string;
slug: string;
region?: string | null;
image?: string | null;
isHot: boolean;
isActive: boolean;
sortOrder: number;
aliases?: Array<{ id: string; alias: string }>;
_count?: { products: number };
};
```
### Product
```ts
type Product = {
id: string;
sourceId?: number | null;
title: string;
subtitle?: string | null;
priceAmount?: number | null;
priceUnit: string;
tags: string[];
coverImage?: string | null;
summary?: string | null;
images?: Array<{ id?: string; url: string; alt?: string | null; sortOrder: number }>;
detailSections?: ProductDetailSection[] | null;
status: ProductStatus;
sortWeight: number;
updatedAt: string;
destination?: Destination | null;
destinationId?: string | null;
};
```
### ProductDetailSection
```ts
type ProductDetailBlock =
| { type: "text"; text: string }
| { type: "image"; url: string; alt?: string | null };
type ProductDetailSection = {
key: string;
label: string;
title?: string | null;
blocks: ProductDetailBlock[];
};
```
### ProductInput
`POST /products``PATCH /products/{id}` 复用该结构;`PATCH` 可以只提交需要修改的字段。
```ts
type ProductInput = {
title: string;
subtitle?: string;
destinationId?: string | null;
priceAmount?: number | null;
priceUnit?: string;
tags: string[];
coverImage?: string | null;
summary?: string | null;
images?: Array<{ url: string; alt?: string | null; sortOrder: number }>;
detailSections?: ProductDetailSection[];
status: ProductStatus;
sortWeight: number;
};
```
字段处理要求:
- `title` 必填,后端至少应校验非空;当前后端 schema 为最少 2 个字符。
- `priceAmount` 可为空;不为空时应为大于等于 0 的整数。
- `priceUnit` 为空时后端默认使用 `起/人`
- `images` 保存前按数组顺序重排 `sortOrder`
- `detailSections` 中空 key、空 label、空 blocks 的模块不应保存为有效详情模块。
-`status` 首次变为 `published` 时,后端可写入发布时间。
### Lead
```ts
type Lead = {
id: string;
destination?: string | null;
phone: string;
note?: string | null;
sourcePage?: string | null;
status: LeadStatus;
createdAt: string;
sourceProduct?: { id: string; title: string } | null;
assignedUser?: { id: string; name: string } | null;
};
```
后端可额外返回 `travelDate``peopleCount``budgetMin``budgetMax` 等字段,但以上字段是当前管理端展示所需的最小集合。手机号属于隐私信息,日志、错误和文档示例中不得输出真实号码。
### SiteConfig
```ts
type SiteConfig = {
heroSlides: Array<{
id: string;
title: string;
kicker?: string | null;
image: string;
targetType?: string | null;
targetValue?: string | null;
isActive: boolean;
}>;
destinations: Destination[];
themes: Array<{
id: string;
label: string;
image: string;
targetType?: string | null;
targetValue?: string | null;
isActive: boolean;
}>;
ctaBanners: Array<{
id: string;
alt: string;
image: string;
targetType: string;
targetValue?: string | null;
isActive: boolean;
}>;
};
```
### SiteItemPatch
```ts
type SiteItemPatch = {
title?: string;
kicker?: string;
name?: string;
label?: string;
alt?: string;
image?: string | null;
targetType?: string | null;
targetValue?: string | null;
isActive?: boolean;
};
```
模块字段映射:
| module | 可编辑字段 |
| --- | --- |
| `heroSlides` | `title``kicker``image``targetType``targetValue``isActive` |
| `destinations` | `name``image``isActive` |
| `themes` | `label``image``targetType``targetValue``isActive` |
| `ctaBanners` | `alt``image``targetType``targetValue``isActive` |
## 接口明细
### 登录
```http
POST /api/admin/auth/login
```
请求体:
```json
{
"email": "admin@example.com",
"password": "example-password"
}
```
响应体:
```ts
{
token: string;
user: {
id?: string;
name: string;
email: string;
role: string;
};
}
```
状态码要求:
- `200`:登录成功。
- `401`:账号不存在、密码错误或账号停用。
### 工作台统计
```http
GET /api/admin/dashboard
```
当前前端客户端已封装该接口,但页面暂未展示。后端保持兼容即可。
响应体:
```ts
{
stats: {
productCount: number;
publishedProductCount: number;
destinationCount: number;
newLeadCount: number;
leadCount: number;
campaignCount: number;
};
recentLeads: Lead[];
}
```
### 商品列表
```http
GET /api/admin/products?keyword=<keyword>
```
查询参数:
| 参数 | 类型 | 当前 UI 是否使用 | 说明 |
| --- | --- | --- | --- |
| `keyword` | `string` | 是 | 搜索商品标题、短标题或标签 |
| `status` | `ProductStatus` | 否 | 后端可支持状态筛选 |
| `take` | `number` | 否 | 后端当前可限制返回条数 |
响应体:
```ts
{
items: Product[];
}
```
排序建议:`sortWeight` 升序,再按 `updatedAt` 倒序。结构维护页和商品维护页都会读取该接口。
### 新建商品
```http
POST /api/admin/products
```
请求体:`ProductInput`
响应体:`Product`
状态码要求:
- `201`:创建成功。
- `422`:字段校验失败。
### 更新商品
```http
PATCH /api/admin/products/{id}
```
请求体:`Partial<ProductInput>`
响应体:`Product`
状态码要求:
- `200`:更新成功。
- `404`:商品不存在。
- `422`:字段校验失败。
当前 UI 保存商品后会使用响应体刷新编辑状态,因此后端需要返回完整 Product而不是只返回成功标记。
### 目的地列表
```http
GET /api/admin/destinations
```
响应体:
```ts
{
items: Destination[];
}
```
要求:
- 返回所有目的地,包括未启用项,便于后台维护。
-`sortOrder` 升序。
- 每个目的地包含 `aliases`
- 每个目的地建议包含 `_count.products`,用于后台判断关联商品数量。
### 站点配置
```http
GET /api/admin/site-config
```
响应体:`SiteConfig`
要求:
- 返回 `heroSlides``destinations``themes``ctaBanners` 四个模块。
- Admin API 需要返回未启用内容Public API 才按发布/启用状态过滤。
- 各模块按 `sortOrder` 升序。
### 更新站点配置项
```http
PATCH /api/admin/site-config/{module}/{item_id}
```
路径参数:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `module` | `SiteModule` | 只能为 `heroSlides``destinations``themes``ctaBanners` |
| `item_id` | `string` | 对应模块内容项 ID |
请求体:`SiteItemPatch`
响应体:更新后的内容项对象。
状态码要求:
- `200`:更新成功。
- `400`:模块不存在。
- `404`:内容项不存在。
- `422`:字段校验失败。
当前 UI 保存后会重新调用 `GET /api/admin/site-config` 刷新页面,响应体只需保证是合法 JSON。
### 线索列表
```http
GET /api/admin/leads
```
查询参数:
| 参数 | 类型 | 当前 UI 是否使用 | 说明 |
| --- | --- | --- | --- |
| `status` | `LeadStatus` | 否 | 后端可支持状态筛选 |
| `take` | `number` | 否 | 后端当前可限制返回条数 |
响应体:
```ts
{
items: Lead[];
}
```
排序建议:`createdAt` 倒序。当前 UI 展示客户手机号、创建时间、目的地/备注、来源商品/来源页面和状态。
### 更新线索状态
```http
PATCH /api/admin/leads/{id}/status
```
请求体:
```ts
{
status: LeadStatus;
}
```
响应体:更新后的 `Lead`,至少需要包含 `id``status`
状态码要求:
- `200`:更新成功。
- `404`:线索不存在。
- `422`:状态值非法。
当前 UI 更新后会重新调用 `GET /api/admin/leads`,因此响应体不会直接用于渲染列表。
### 发布站点配置
```http
POST /api/admin/publish
```
请求体:空 JSON 对象或无请求体均可兼容。
响应体:
```ts
{
id: string;
title: string;
publishedAt: string;
}
```
当前 UI 只读取 `title` 展示发布结果。后端可额外返回 `status``snapshot` 等字段。
### 重置贵州内容
```http
POST /api/admin/reset-guizhou-content
```
请求体:空 JSON 对象或无请求体均可兼容。
响应体:
```ts
{
heroSlides: number;
destinations: number;
themes: number;
ctaBanners: number;
products: number;
}
```
要求:
- 该接口会重置内容数据,必须走后台鉴权。
- 后端需要记录审计日志。
- 生产环境调用前应通过部署流程或权限控制额外确认。
## 后端实现注意事项
- Admin API 默认使用 `require_admin`,登录接口除外。
- 所有外部输入通过 Pydantic schema 校验。
- 变更类接口需要继续写入 `AuditLog`
- 响应字段使用 camelCase以匹配当前前端类型。
- 允许后端返回额外字段,但不要移除本文列出的前端依赖字段。
- 当前管理端不会直接上传图片,只维护图片 URL媒体库接口暂不属于本需求范围。

353
docs/miniapp-public-api.md Normal file
View File

@@ -0,0 +1,353 @@
# WonderQ-MiniAPP Public API 对接文档
最后更新2026-06-30
本文档定义 `WonderQ-MiniAPP` 前台 H5/小程序对接 `WonderQ-Admin` 后端所需的 Public API 契约。当前 MiniAPP 主动调用站点配置、产品列表和线索提交 3 个接口;后端已存在的健康检查、产品详情和目的地列表接口建议继续保留,供后续前台按需接入。
## 基础约定
- 基础地址由 MiniAPP 环境变量 `VITE_API_BASE_URL` 控制;为空时前台按同源 `/api` 请求。
- Public API 不要求前台登录认证。
- 请求和响应均使用 JSON字符集为 UTF-8。
- 图片字段应返回可被 H5 和微信小程序访问的 URL现有前台兼容 `/assets/...` 形式。
- 列表字段建议返回空数组,不建议返回 `null`MiniAPP 对站点配置和产品列表有本地兜底内容。
- 错误响应需提供可展示信息,兼容 `{ "message": "..." }` 或 FastAPI 默认 `{ "detail": "..." }`。不要暴露内部异常、真实环境变量、Token、JWT secret、客服链接或企业 ID。
## 数据类型
### `HeroSlide`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 轮播图 ID |
| `title` | `string` | 是 | 主标题 |
| `kicker` | `string \| null` | 否 | 辅助短文案 |
| `image` | `string` | 是 | 图片 URL |
| `targetType` | `string \| null` | 否 | 点击目标类型 |
| `targetValue` | `string \| null` | 否 | 点击目标值 |
| `isActive` | `boolean` | 否 | 是否启用 |
### `Destination`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 目的地 ID |
| `name` | `string` | 是 | 目的地名称 |
| `image` | `string \| null` | 否 | 图片 URL |
| `isHot` | `boolean` | 否 | 是否热门 |
| `isActive` | `boolean` | 否 | 是否启用 |
| `aliases` | `Array<{ id: string; alias: string }>` | 否 | 搜索别名 |
### `Theme`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 主题 ID |
| `label` | `string` | 是 | 主题名称 |
| `image` | `string` | 是 | 图片 URL |
| `targetType` | `string \| null` | 否 | 点击目标类型 |
| `targetValue` | `string \| null` | 否 | 点击目标值 |
| `isActive` | `boolean` | 否 | 是否启用 |
### `CtaBanner`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | Banner ID |
| `alt` | `string` | 是 | 图片替代文案 |
| `image` | `string` | 是 | 图片 URL |
| `targetType` | `string \| null` | 否 | 点击目标类型 |
| `targetValue` | `string \| null` | 否 | 点击目标值 |
| `isActive` | `boolean` | 否 | 是否启用 |
### `PublicProduct`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 后端产品 UUID |
| `sourceId` | `number \| null` | 否 | 历史采集产品 ID前台可用于兼容旧数据 |
| `title` | `string` | 是 | 产品标题 |
| `subtitle` | `string \| null` | 否 | 副标题 |
| `destination` | `{ id: string; name: string } \| null` | 否 | 目的地信息 |
| `priceAmount` | `number \| null` | 否 | 参考起价,单位按后端内容约定 |
| `priceUnit` | `string \| null` | 否 | 价格单位文案 |
| `tags` | `string[]` | 否 | 标签列表 |
| `coverImage` | `string \| null` | 否 | 封面图 URL |
| `summary` | `string \| null` | 否 | 摘要 |
| `images` | `Array<ProductImage>` | 否 | 图集 |
| `detailSections` | `ProductDetailSection[] \| null` | 否 | 产品详情分区 |
| `status` | `string` | 否 | 产品状态,前台主要消费 `published` 内容 |
### `ProductImage`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 否 | 图片 ID |
| `url` | `string` | 是 | 图片 URL |
| `alt` | `string \| null` | 否 | 图片说明 |
| `sortOrder` | `number` | 是 | 排序值 |
### `ProductDetailSection`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `key` | `string` | 是 | 分区 key例如 `overview``itinerary``service``notice``price``manager` |
| `label` | `string` | 是 | Tab 展示文案 |
| `title` | `string \| null` | 否 | 分区标题 |
| `blocks` | `ProductDetailBlock[]` | 是 | 内容块 |
`ProductDetailBlock` 支持两种结构:
```json
{ "type": "text", "text": "文本内容" }
```
```json
{ "type": "image", "url": "/assets/example.jpg", "alt": "图片说明" }
```
## 接口清单
### `GET /health`
用于服务健康检查。
#### 响应示例
```json
{
"ok": true,
"service": "miniapp-api"
}
```
### `GET /api/public/site-config`
用于首页轮播、目的地、主题入口和底部 CTA 配置。MiniAPP 启动时会和产品列表并行请求该接口;接口不可用或关键数组为空时,前台会回退本地静态内容。
#### 响应字段
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `heroSlides` | `HeroSlide[]` | 首页顶部轮播 |
| `destinations` | `Destination[]` | 首页目的地入口 |
| `themes` | `Theme[]` | 主题甄选入口 |
| `ctaBanners` | `CtaBanner[]` | 底部 CTA Banner |
| `campaigns` | `unknown[]` | 后端现有扩展字段,可保留 |
| `routeSections` | `Array<{ id: string; title: string; productIds: string[] }>` | 后端现有扩展字段,可保留 |
#### 响应示例
```json
{
"heroSlides": [
{
"id": "hero-1",
"title": "贵州小包团定制",
"kicker": "万趣,你的小包团首选",
"image": "/assets/guizhou/libo-xiaoqikong.jpg",
"targetType": "search",
"targetValue": "贵州",
"isActive": true
}
],
"destinations": [
{
"id": "dest-1",
"name": "荔波小七孔",
"image": "/assets/guizhou/libo-xiaoqikong.jpg",
"isHot": true,
"isActive": true,
"aliases": [{ "id": "alias-1", "alias": "小七孔" }]
}
],
"themes": [],
"ctaBanners": []
}
```
### `GET /api/public/products`
用于首页产品分区、搜索结果、活动页、目的地页、详情推荐和预订入口。当前 MiniAPP 一次拉取列表后在前端做搜索、筛选和推荐。
#### Query 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `keyword` | `string` | 否 | 无 | 关键词搜索,建议匹配产品标题、副标题、标签、目的地名称和目的地别名 |
| `destinationId` | `string` | 否 | 无 | 按目的地 ID 筛选 |
| `status` | `string` | 否 | `published` | 产品状态 |
| `take` | `number` | 否 | `48` | 返回数量,后端当前限制 1-100 |
#### 响应字段
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `items` | `PublicProduct[]` | 产品列表 |
#### 响应示例
```json
{
"items": [
{
"id": "8a6e7c4f-0000-4000-9000-000000000001",
"sourceId": 101,
"title": "黄果树瀑布小包团",
"subtitle": "错峰入园,私家车接送",
"destination": { "id": "dest-anshun", "name": "黄果树" },
"priceAmount": 398000,
"priceUnit": "起/人",
"tags": ["贵州", "黄果树", "小包团"],
"coverImage": "/assets/guizhou/huangguoshu.jpg",
"summary": "适合首次到贵州的经典线路。",
"images": [
{
"id": "img-1",
"url": "/assets/guizhou/huangguoshu.jpg",
"alt": "黄果树瀑布",
"sortOrder": 0
}
],
"detailSections": [
{
"key": "overview",
"label": "行程概述",
"title": "小包团专属概览",
"blocks": [{ "type": "text", "text": "按同行人、预算和体力强度重排行程。" }]
}
],
"status": "published"
}
]
}
```
### `GET /api/public/products/{product_id}`
后端已存在,建议保留给 MiniAPP 后续详情页按需拉取。当前 MiniAPP 主要通过产品列表缓存进入详情。
#### Path 参数
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `product_id` | `string` | 产品 UUID如果传入纯数字后端按 `sourceId` 查询 |
#### 成功响应
返回单个 `PublicProduct`
#### 异常响应
| 状态码 | 说明 |
| --- | --- |
| `404` | 产品不存在 |
示例:
```json
{
"detail": "线路不存在"
}
```
### `GET /api/public/destinations`
后端已存在,建议保留给 MiniAPP 后续目的地页独立拉取。当前 MiniAPP 首页目的地来自 `site-config.destinations`
#### 响应示例
```json
{
"items": [
{
"id": "dest-libo",
"name": "荔波小七孔",
"slug": "libo-xiaoqikong",
"region": "黔南",
"image": "/assets/guizhou/libo-xiaoqikong.jpg",
"isHot": true,
"sortOrder": 0,
"isActive": true,
"aliases": [{ "id": "alias-1", "alias": "小七孔" }]
}
]
}
```
### `POST /api/public/leads`
用于首页快速定制、搜索页快速定制、需求页和预订咨询页提交线索。
#### 请求字段
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `phone` | `string` | 是 | 联系方式。前台输入文案为“手机号 / 微信号”,后端当前会去除首尾空白并压缩连续空格 |
| `destination` | `string` | 否 | 目的地或玩法 |
| `travelDate` | `string` | 否 | 出行日期MiniAPP 当前传 `YYYY-MM-DD` 字符串 |
| `peopleCount` | `number` | 否 | 出行人数,需大于 0 |
| `budgetMin` | `number` | 否 | 最低预算,需大于等于 0 |
| `budgetMax` | `number` | 否 | 最高预算,需大于等于 0 |
| `note` | `string` | 否 | 补充说明,后端当前限制最长 1000 字符 |
| `sourcePage` | `string` | 否 | 来源页面 |
| `sourceProductId` | `string` | 否 | 来源产品 UUID |
#### `sourcePage` 当前取值
| 值 | 来源 |
| --- | --- |
| `home_inline` | 首页快速定制入口 |
| `search_inline` | 搜索结果页快速定制入口 |
| `demand_page` | 提交需求页 |
| `product_consult` | 产品预订咨询页 |
#### 请求示例
```json
{
"destination": "荔波小七孔",
"phone": "187 8617 4929",
"travelDate": "2027-01-01",
"peopleCount": 2,
"note": "咨询线路:黄果树瀑布小包团;方案偏好:经典人文",
"sourcePage": "product_consult",
"sourceProductId": "8a6e7c4f-0000-4000-9000-000000000001"
}
```
#### 成功响应
状态码:`201`
```json
{
"id": "lead-uuid",
"status": "new"
}
```
#### 常见异常
| 状态码 | 场景 |
| --- | --- |
| `422` | 请求体校验失败,例如 `phone` 为空、`peopleCount` 小于等于 0、`note` 超长 |
| `500` | 服务端异常,响应不得暴露内部细节 |
## MiniAPP 当前依赖说明
- `site-config``products` 会在应用启动时并行请求任一请求失败时MiniAPP 会回退到本地静态内容。
- `products.items` 为空时MiniAPP 会使用本地产品兜底数据。
- 产品搜索当前主要在前端执行,依赖 `title``tags``destination.name``summary`
- 产品详情页当前使用已加载的产品列表数据;后续可改为进入详情页时请求 `GET /api/public/products/{product_id}`
- 收藏、浏览历史和最近咨询记录由 MiniAPP 本地存储处理,不需要后端接口。
- 企业微信客服由 MiniAPP 环境变量控制,不属于 `WonderQ-Admin` Public API文档和接口不得写入真实链接或企业 ID。
## 后端验证建议
-`GET /health` 增加或保留健康检查测试。
-`GET /api/public/site-config` 验证返回 JSON 包含 `heroSlides``destinations``themes``ctaBanners` 数组字段。
-`GET /api/public/products` 验证响应结构为 `{ items: [...] }`,并覆盖 `keyword``destinationId``status``take` 参数。
-`GET /api/public/products/{product_id}` 验证 UUID、数字 `sourceId` 和 404 场景。
-`GET /api/public/destinations` 验证只返回启用目的地及别名字段。
-`POST /api/public/leads` 验证成功创建、`phone` 规范化、必填校验、人数/预算边界和备注长度限制。