docs: define initial product requirements

This commit is contained in:
2026-09-18 14:15:49 +08:00
commit e28140a0b3
9 changed files with 1346 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
# 数据库与 API 设计
## 1. 关系概览
订单模型和接口的最新增量见 [首轮需求访谈决策](./07-requirements-interview-decisions.md)。实现时必须包含客户微信身份、套餐价格快照、标签、未读状态和客户订单查询;与本文早期枚举冲突时,以该文档为准。
```text
BrandProfile 1
├── Services 1..n
└── PortfolioItems 1..n
Service 1 ── n Booking
Booking 1 ── 0..n Feedback
AdminUser 1 ── n AuditLog
```
第一版数据库只有一个品牌记录,但所有内容表仍通过 `brand_id` 关联,便于后续扩展。
## 2. 表与约束
### brand_profiles
- `id` UUID 主键
- `name` 必填
- `is_published` 默认 true
- `created_at``updated_at` 必填
### services
- `id` UUID 主键
- `brand_id` 外键
- `name` 必填
- `price_text` 可空
- `price_amount` 可空,单位为分,第一版不用于支付
- `booking_enabled` 默认 true
- `is_enabled` 默认 true
- `sort_order` 默认 0
-`(brand_id, sort_order)` 建索引
### portfolio_items
- `id` UUID 主键
- `brand_id` 外键
- `image_urls` 可使用 JSON 数组,或拆分为 media_assets 表
- `is_visible` 默认 true
- `sort_order` 默认 0
### bookings
- `id` UUID 主键
- `booking_no` 唯一
- `brand_id` 外键
- `service_id` 可空,防止服务删除后历史预约无法读取
- `status` 枚举:`pending/contacted/completed/cancelled`
- `extra_data` JSON可存储受控的行业扩展字段
-`(brand_id, status, created_at)` 建索引
- 不存支付状态
`extra_data` 不是任意键值存储。字段定义必须由品牌行业配置决定API 需要校验键名、类型和必填规则;通用字段仍保持结构化。
### feedbacks
- `id` UUID 主键
- `brand_id` 外键
- `booking_id` 可空
- `status` 枚举:`pending/approved/hidden`
-`(brand_id, status, created_at)` 建索引
### admin_users
- `id` UUID 主键
- `username` 唯一
- `password_hash` 必填
- `role` 枚举:`admin/operator`
- `is_enabled` 默认 true
### audit_logs
- `id` UUID 主键
- `admin_user_id` 外键
- `action``resource_type``resource_id` 必填
- `metadata` JSON可记录变更摘要不记录密码和完整联系方式
## 3. API 约定
### 响应格式
```json
{
"data": {},
"error": null,
"requestId": "..."
}
```
失败时:
```json
{
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "请填写有效的联系方式",
"fields": {"phone": "格式不正确"}
},
"requestId": "..."
}
```
### 公开接口
```text
GET /api/public/brand
GET /api/public/services
GET /api/public/services/:id
GET /api/public/portfolio
GET /api/public/portfolio/:id
GET /api/public/feedbacks
POST /api/public/bookings
POST /api/public/feedbacks
```
公开接口只能返回启用、可见和已审核数据。
### 管理接口
```text
POST /api/auth/login
POST /api/auth/logout
GET /api/auth/me
GET /api/admin/brand
PUT /api/admin/brand
GET /api/admin/services
POST /api/admin/services
PUT /api/admin/services/:id
DELETE /api/admin/services/:id
GET /api/admin/portfolio
POST /api/admin/portfolio
PUT /api/admin/portfolio/:id
DELETE /api/admin/portfolio/:id
GET /api/admin/bookings
GET /api/admin/bookings/:id
PATCH /api/admin/bookings/:id/status
GET /api/admin/feedbacks
PATCH /api/admin/feedbacks/:id/status
POST /api/admin/uploads/presign
```
## 4. 预约接口规则
- 服务不存在或已停用时拒绝提交。
- 期望日期早于当天时拒绝提交。
- 联系方式至少一项非空。
- 服务端再次执行所有校验,不能只依赖小程序校验。
- 使用请求幂等键或短时间重复检测避免重复预约。
- 创建成功后返回 `booking_no`,不返回管理员数据。
## 5. 文件上传流程
1. 后台请求上传凭证。
2. API 校验管理员权限和文件元数据。
3. 前端直接上传对象存储。
4. 上传完成后提交文件 URL 和元数据。
5. 内容实体引用该资源。
不得让浏览器上传任意路径或任意 MIME 文件。图片最大尺寸和大小由环境变量配置。
## 6. 远期支付边界
未来新增:
```text
orders
payments
refunds
```
`Booking` 表示客户意向或服务预约;`Order` 表示应收业务;`Payment` 表示微信支付交易;三者必须独立建模。支付成功不能直接等价于预约完成,预约仍由业务流程决定。