180 lines
4.3 KiB
Markdown
180 lines
4.3 KiB
Markdown
# 数据库与 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` 表示微信支付交易;三者必须独立建模。支付成功不能直接等价于预约完成,预约仍由业务流程决定。
|