library-picturebook-activity/docs/project/03-database-design.md
aid 418aa57ea8 Day4: 超管端设计优化 + UGC绘本创作社区P0实现
一、超管端设计优化
- 文档管理SOP体系建立,docs目录重组
- 统一用户管理:跨租户全局视角,合并用户管理+公众用户
- 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作)
- 菜单精简:移除评委管理/评审规则/通知管理
- Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一

二、UGC绘本创作社区P0
- 数据库:10张新表(user_works/user_work_pages/work_tags等)
- 子女账号独立化:Child升级为独立User,家长切换+独立登录
- 用户作品库:CRUD+发布审核,8个API
- AI创作流程:提交→生成→保存到作品库,4个API
- 作品广场:首页改造为推荐流,标签+搜索+排序
- 内容审核(超管端):作品审核+作品管理+标签管理
- 活动联动:WorkSelector作品选择器
- 布局改造:底部5Tab(发现/创作/活动/作品库/我的)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:20:25 +08:00

300 lines
13 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.

# 数据库设计
## 1. 变更概览
| 操作 | 表名 | 说明 |
|------|------|------|
| 修改 | `users` | 新增 phone、wx_openid、user_source、city 等字段 |
| 修改 | `tenants` | 新增 tenant_type 字段 |
| 修改 | `registrations` | 新增 participant_type、child_id 字段 |
| 修改 | `contests` | 新增 visibility 字段 |
| 新增 | `children` | 子女信息表 |
| 新增(二期) | `user_identities` | 用户身份表(一号多身份) |
| 新增(二期) | `contest_co_organizers` | 活动协办机构表 |
---
## 2. 表结构详细设计
### 2.1 users 表(改造)
新增字段:
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `phone` | VARCHAR(20) | 否 | NULL | 手机号,全局唯一,用于手机号登录 |
| `wx_openid` | VARCHAR(64) | 否 | NULL | 微信 OpenID全局唯一 |
| `wx_unionid` | VARCHAR(64) | 否 | NULL | 微信 UnionID跨应用统一标识 |
| `user_source` | ENUM | 否 | 'admin_created' | 用户来源admin_created / self_registered |
| `city` | VARCHAR(50) | 否 | NULL | 所在城市 |
| `birthday` | DATE | 否 | NULL | 出生日期(自注册用户可填写) |
```sql
-- 新增字段
ALTER TABLE users ADD COLUMN phone VARCHAR(20) UNIQUE DEFAULT NULL COMMENT '手机号';
ALTER TABLE users ADD COLUMN wx_openid VARCHAR(64) UNIQUE DEFAULT NULL COMMENT '微信OpenID';
ALTER TABLE users ADD COLUMN wx_unionid VARCHAR(64) DEFAULT NULL COMMENT '微信UnionID';
ALTER TABLE users ADD COLUMN user_source VARCHAR(20) NOT NULL DEFAULT 'admin_created' COMMENT '用户来源admin_created/self_registered';
ALTER TABLE users ADD COLUMN city VARCHAR(50) DEFAULT NULL COMMENT '所在城市';
ALTER TABLE users ADD COLUMN birthday DATE DEFAULT NULL COMMENT '出生日期';
-- 索引
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_users_wx_openid ON users(wx_openid);
CREATE INDEX idx_users_user_source ON users(user_source);
```
**设计说明**
- `phone` 设为 UNIQUE 但允许 NULL因为管理员创建的机构用户可能没有手机号
- `wx_openid``wx_unionid` 为二期微信登录预留
- `user_source` 区分用户是管理员创建的还是自主注册的,方便后台统计和管理
- 自注册用户归属公众租户code='public', tenantId=8而非 tenant_id 为 NULL以兼容现有 JWT 认证链路
---
### 2.2 children 表(新增)
子女信息表,关联家长账号。
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | INT | 是 | AUTO_INCREMENT | 主键 |
| `parent_id` | INT | 是 | - | 家长用户 ID关联 users.id |
| `name` | VARCHAR(50) | 是 | - | 子女姓名 |
| `gender` | VARCHAR(10) | 否 | NULL | 性别male / female |
| `birthday` | DATE | 否 | NULL | 出生日期 |
| `grade` | VARCHAR(20) | 否 | NULL | 年级(如:大班、一年级) |
| `city` | VARCHAR(50) | 否 | NULL | 所在城市 |
| `school_name` | VARCHAR(100) | 否 | NULL | 学校/幼儿园名称(手动填写) |
| `avatar` | VARCHAR(500) | 否 | NULL | 头像 URL |
| `is_deleted` | TINYINT(1) | 否 | 0 | 软删除标记 |
| `create_time` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 |
| `modify_time` | DATETIME | 否 | CURRENT_TIMESTAMP | 修改时间 |
```sql
CREATE TABLE children (
id INT AUTO_INCREMENT PRIMARY KEY,
parent_id INT NOT NULL COMMENT '家长用户ID',
name VARCHAR(50) NOT NULL COMMENT '子女姓名',
gender VARCHAR(10) DEFAULT NULL COMMENT '性别male/female',
birthday DATE DEFAULT NULL COMMENT '出生日期',
grade VARCHAR(20) DEFAULT NULL COMMENT '年级',
city VARCHAR(50) DEFAULT NULL COMMENT '所在城市',
school_name VARCHAR(100) DEFAULT NULL COMMENT '学校/幼儿园名称',
avatar VARCHAR(500) DEFAULT NULL COMMENT '头像URL',
is_deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
modify_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
INDEX idx_children_parent_id (parent_id),
CONSTRAINT fk_children_parent FOREIGN KEY (parent_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='子女信息表';
```
**设计说明**
- `parent_id` 关联 users 表,一个家长可以有多个子女
- `school_name` 是手动填写的文本字段,不关联租户表,因为公众用户的学校不一定是平台上的租户
- `grade` 存储文本如"大班""一年级",不做枚举约束,因为不同教育体系的年级名称不同
- 使用软删除(`is_deleted`),避免误删导致历史报名数据关联丢失
---
### 2.3 tenants 表(改造)
新增字段:
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `tenant_type` | VARCHAR(20) | 否 | 'other' | 租户类型 |
```sql
ALTER TABLE tenants ADD COLUMN tenant_type VARCHAR(20) NOT NULL DEFAULT 'other'
COMMENT '租户类型platform/library/kindergarten/school/institution/other';
CREATE INDEX idx_tenants_type ON tenants(tenant_type);
```
**租户类型枚举值**
| 值 | 说明 |
|----|------|
| `platform` | 平台租户(乐绘世界,全局唯一) |
| `library` | 图书馆 |
| `kindergarten` | 幼儿园 |
| `school` | 学校 |
| `institution` | 社会机构 |
| `other` | 其他 |
---
### 2.4 registrations 表(改造)
新增字段:
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `participant_type` | VARCHAR(10) | 否 | 'self' | 参与者类型self自己/ child子女 |
| `child_id` | INT | 否 | NULL | 子女 IDparticipant_type 为 child 时必填 |
```sql
ALTER TABLE registrations ADD COLUMN participant_type VARCHAR(10) NOT NULL DEFAULT 'self'
COMMENT '参与者类型self-自己/child-代子女报名';
ALTER TABLE registrations ADD COLUMN child_id INT DEFAULT NULL
COMMENT '子女ID代子女报名时填写';
CREATE INDEX idx_registrations_participant ON registrations(participant_type);
CREATE INDEX idx_registrations_child ON registrations(child_id);
```
**设计说明**
- `user_id`(现有字段)始终记录操作人(谁提交的报名)
- `participant_type = 'self'`:用户以自己身份报名,`child_id` 为 NULL
- `participant_type = 'child'`:家长代子女报名,`child_id` 指向 children 表
- 管理端查看报名记录时,能清晰区分"张妈妈帮小明报名"和"李同学自己报名"
---
### 2.5 contests 表(改造)
新增字段:
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `visibility` | VARCHAR(20) | 否 | 'designated' | 活动可见范围 |
```sql
ALTER TABLE contests ADD COLUMN visibility VARCHAR(20) NOT NULL DEFAULT 'designated'
COMMENT '可见范围public-公开/designated-指定机构/internal-仅内部';
```
**可见范围说明**
| 值 | 说明 | 数据过滤逻辑 |
|----|------|-------------|
| `public` | 全体公开 | 所有注册用户可见,公众端活动大厅展示 |
| `designated` | 指定机构 | 仅 contest_tenants 中列出的租户可见(现有逻辑) |
| `internal` | 仅内部 | 仅创建该活动的机构内部可见 |
---
### 2.6 user_identities 表(二期新增)
一号多身份的身份关联表。
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | INT | 是 | AUTO_INCREMENT | 主键 |
| `user_id` | INT | 是 | - | 用户 ID |
| `identity_type` | VARCHAR(20) | 是 | - | 身份类型 |
| `tenant_id` | INT | 否 | NULL | 关联租户(公众身份为 NULL |
| `role_id` | INT | 否 | NULL | 关联角色 |
| `status` | VARCHAR(10) | 否 | 'active' | 状态active / inactive |
| `create_time` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 |
```sql
-- 二期实现
CREATE TABLE user_identities (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
identity_type VARCHAR(20) NOT NULL COMMENT '身份类型public/tenant_admin/judge/staff',
tenant_id INT DEFAULT NULL COMMENT '关联租户ID',
role_id INT DEFAULT NULL COMMENT '关联角色ID',
status VARCHAR(10) NOT NULL DEFAULT 'active' COMMENT '状态active/inactive',
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_identity_user (user_id),
INDEX idx_identity_tenant (tenant_id),
UNIQUE KEY uk_user_tenant_type (user_id, tenant_id, identity_type),
CONSTRAINT fk_identity_user FOREIGN KEY (user_id) REFERENCES users(id),
CONSTRAINT fk_identity_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户身份表';
```
---
### 2.7 contest_co_organizers 表(二期新增)
活动协办机构关联表。
```sql
-- 二期实现
CREATE TABLE contest_co_organizers (
id INT AUTO_INCREMENT PRIMARY KEY,
contest_id INT NOT NULL COMMENT '活动ID',
tenant_id INT NOT NULL COMMENT '协办机构租户ID',
permission_level VARCHAR(20) NOT NULL DEFAULT 'readonly' COMMENT '权限级别readonly/limited/full',
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_contest_tenant (contest_id, tenant_id),
CONSTRAINT fk_co_org_contest FOREIGN KEY (contest_id) REFERENCES contests(id),
CONSTRAINT fk_co_org_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动协办机构表';
```
---
## 3. ER 关系图
```
┌──────────┐
│ tenants │
│──────────│
│ id │
│ name │
│ tenant_ │
┌──────────│ type │──────────┐
│ └──────────┘ │
│ tenant_id │ organizer_
│ │ tenant_id
┌────▼─────┐ ┌──────▼──────┐
│ users │ │ contests │
│──────────│ │─────────────│
│ id │ │ id │
│ username │ │ contestName │
│ phone │ │ visibility │
│ user_ │ │ contestType │
│ source │ └──────┬──────┘
└──┬───┬───┘ │
│ │ │ contest_id
│ │ parent_id ┌─────▼────────┐
│ │ │registrations │
│ ├──────────┐ │──────────────│
│ │ │ │ id │
│ │ ┌──────▼──────┐ │ user_id ◄────┼──── users.id
│ │ │ children │ │ participant_ │
│ │ │─────────────│ │ type │
│ │ │ id │ │ child_id ◄───┼──── children.id
│ │ │ parent_id │ │ contest_id │
│ │ │ name │ │ status │
│ │ │ birthday │ └──────────────┘
│ │ └─────────────┘
│ │
│ └─── user_id ─────────── registrations.user_id
└─── tenant_id ──────────── tenants.id (公众用户归属 public 租户)
```
---
## 4. 数据迁移注意事项
### 4.1 现有数据兼容
- `users.user_source`:现有用户默认值为 `admin_created`,无需迁移
- `users.phone`:允许 NULL现有用户无需填写
- `registrations.participant_type`:现有报名记录默认值为 `self`,无需迁移
- `tenants.tenant_type`:现有租户默认值为 `other`,需要手动为已有租户设置正确类型
- `contests.visibility`:现有活动默认值为 `designated`,保持现有行为不变
### 4.2 Prisma Schema 同步
所有数据库变更需要同步到 Prisma Schema 文件,通过 `prisma migrate` 管理迁移。
### 4.3 广东省图初始化数据
```sql
-- 创建广东省图租户
INSERT INTO tenants (name, code, tenant_type, description, valid_state)
VALUES ('广东省立中山图书馆', 'gdlib', 'library', '广东省图少儿绘本创作活动主办方', 1);
-- 创建广东省图管理员账号
-- (通过初始化脚本创建,关联对应的角色和权限)
```