第 4 章:数据库设计 (MySQL 版)
“NoSQL 是一场说走就走的旅行,而 MySQL 是那张必须要买的回程票。”
在上一章我们确定了使用 MySQL。很多同学可能会问:“都 2025 年了,为什么还要用 SQL?” 答案很简单:因为安全感。在报名扣库存、投票防刷这种场景下,关系型数据库的事务机制 (Transaction) 依然是王者。
4.1 为什么我们要用 MySQL?
- 结构化数据的归宿: 用户、活动、报名记录,这些数据天然就是结构化的,用表 (Table) 来存再合适不过。
- JSON 的灵活: MySQL 5.7+ 引入了
JSON数据类型。我们可以在config字段里存 JSON,既享受了 SQL 的严谨,又有了 NoSQL 的灵活。 - 关系维护:
JOIN查询虽然被诟病性能,但在后台管理这种复杂查询场景下,它比 NoSQL 的多次查询拼装要方便得多。
4.2 ER 图设计:理清表之间的暧昧关系
我们一共设计了 5 张核心表。
4.2.1 Users(用户表)
存储用户的基本信息。
| 字段名 | 类型 | 描述 | 示例/备注 |
|---|---|---|---|
id | BIGINT | 主键 | 自增 |
openid | VARCHAR(64) | 微信 OpenID | 唯一索引 (UK) |
account | VARCHAR(32) | 内部账号 | 随机生成,如 "AXF12309" |
nickname | VARCHAR(64) | 昵称 | |
avatar_url | VARCHAR(255) | 头像URL | |
role | ENUM | 角色 | 'user', 'admin', 'super_admin' |
created_at | DATETIME | 创建时间 | 自动生成 |
updated_at | DATETIME | 更新时间 | 自动更新 |
deleted_at | DATETIME | 删除时间 | 用于软删除 |
4.2.2 Activities(活动表)
核心表,存储活动的基础信息和配置。
| 字段名 | 类型 | 描述 | 示例/备注 |
|---|---|---|---|
id | BIGINT | 主键 | 自增 |
title | VARCHAR(100) | 活动标题 | "2025春季摄影大赛" |
description | TEXT | 活动描述 | 富文本内容 |
cover_url | VARCHAR(255) | 封面图 | |
start_time | DATETIME | 开始时间 | |
end_time | DATETIME | 结束时间 | |
type | ENUM | 活动类型 | 'registration', 'vote' |
is_active | TINYINT | 上架状态 | 1: 上架, 0: 下架 |
creator_id | BIGINT | 创建者ID | FK -> Users.id |
components | JSON | 页面组件配置 | [{"type":"image",...}] |
reg_config | JSON | 报名配置 | {"fields":[...]} |
vote_config | JSON | 投票配置 | {"options":[...]} |
4.2.3 Registrations(报名表)
记录用户的报名信息。
| 字段名 | 类型 | 描述 | 示例/备注 |
|---|---|---|---|
id | BIGINT | 主键 | 自增 |
activity_id | BIGINT | 活动ID | FK -> Activities.id |
user_id | BIGINT | 用户ID | FK -> Users.id |
form_data | JSON | 动态表单数据 | {"name":"张三", "phone":"..."} |
payment_proof | VARCHAR(255) | 支付凭证 | 图片 FileID |
status | ENUM | 审核状态 | 'pending', 'approved', 'rejected' |
payment_status | ENUM | 支付状态 | 'pending', 'success', 'failed' |
4.2.4 Votes(投票表)
记录投票动作。
| 字段名 | 类型 | 描述 | 示例/备注 |
|---|---|---|---|
id | BIGINT | 主键 | 自增 |
activity_id | BIGINT | 活动ID | FK -> Activities.id |
user_id | BIGINT | 用户ID | FK -> Users.id |
option_ids | JSON | 投票选项列表 | ["opt_1", "opt_2"] |
4.2.5 Barrages(弹幕表)
记录实时弹幕。
| 字段名 | 类型 | 描述 | 示例/备注 |
|---|---|---|---|
id | BIGINT | 主键 | 自增 |
activity_id | BIGINT | 活动ID | FK -> Activities.id |
content | VARCHAR(255) | 弹幕内容 | |
type | VARCHAR(20) | 类型 | 'registration', 'vote' |
4.3 JSON 字段的妙用
在 Activities 表中,我们把页面配置存在 components 字段里。
在查询时,MySQL 可以直接解析 JSON:
-- 查询所有使用了“倒计时”组件的活动
SELECT * FROM Activities
WHERE JSON_CONTAINS(components, '{"type": "countdown"}');
(当然,云函数里我们通常用 ORM 来做这件事,不用手写这种 SQL。)
本章小结: 我们设计了一套“混合模式”的数据库结构:严谨的关系表 + 灵活的 JSON 字段。这既保证了数据一致性,又没丢掉页面配置的灵活性。下一章,我们将看看如何在云函数里操作这些表。