--- sidebar_position: 3 title: 数据模型 sidebar_label: 数据模型 description: Plaza 所有数据库表的完整字段定义、索引策略、约束规则和迁移说明 --- # 数据模型 ## 总览 Plaza 新增 **10 张业务表**(`plaza_*` 前缀 + `ops_audit_log`),复用现有 `h5_users` 和 `h5_publish_records`,不创建新的用户体系。 **逻辑名与物理表映射**(与 MindSpace 文档一致): | 文档逻辑名 | 物理表 | | --- | --- | | users | `h5_users` | | publications | `h5_publish_records` | **时间字段约定**:Plaza 新表与现有 H5 库一致,使用 `BIGINT` 毫秒 UTC(`created_at` / `updated_at` / `published_at` 等),不使用 `DATETIME`。 ```text h5_users (现有) │ ├──< plaza_posts 广场帖子(每条对应一个已发布的 publication) │ │ │ ├──< plaza_reactions 点赞 / 收藏 / 分享 │ ├──< plaza_comments 评论(支持二级回复) │ └──< plaza_comment_reactions 评论点赞 │ └──< plaza_follows 关注关系(创作者 ↔ 粉丝) plaza_categories 广场分类(独立配置表) └──< plaza_posts plaza_algorithm_config 热度公式权重(键值表) plaza_reports 用户举报 plaza_featured 精选位配置 ops_audit_log 运营操作审计(追加-only) ``` --- ## plaza_posts(广场帖子) ```sql CREATE TABLE plaza_posts ( id CHAR(36) NOT NULL, publication_id CHAR(36) NOT NULL, -- → h5_publish_records.id user_id CHAR(36) NOT NULL, -- → h5_users.id -- 快照字段(冗余,避免跨表 JOIN) title VARCHAR(200) NOT NULL, summary VARCHAR(500) NOT NULL DEFAULT '', cover_url VARCHAR(500) NOT NULL DEFAULT '', user_slug VARCHAR(100) NOT NULL, -- 快照,发帖时写入 user_display_name VARCHAR(100) NOT NULL, user_avatar_url VARCHAR(500) NOT NULL DEFAULT '', -- 分类和标签 category_id CHAR(36) NOT NULL, -- → plaza_categories.id tags JSON NOT NULL DEFAULT (JSON_ARRAY()), -- 最多 5 个 -- 状态 status ENUM( 'pending_review', -- 待审核(默认) 'published', -- 审核通过,公开展示 'hidden', -- 被运营隐藏或 publication 下线 'rejected' -- 审核拒绝 ) NOT NULL DEFAULT 'pending_review', -- 互动计数(热计数用 Redis,定时同步到这里) view_count INT UNSIGNED NOT NULL DEFAULT 0, like_count INT UNSIGNED NOT NULL DEFAULT 0, collect_count INT UNSIGNED NOT NULL DEFAULT 0, comment_count INT UNSIGNED NOT NULL DEFAULT 0, share_count INT UNSIGNED NOT NULL DEFAULT 0, -- 热度 hot_score DOUBLE NOT NULL DEFAULT 0, hot_updated_at BIGINT NULL, -- 设置 allow_comment TINYINT(1) NOT NULL DEFAULT 1, -- 时间(毫秒 UTC) published_at BIGINT NOT NULL, created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_publication (publication_id), -- 每个 publication 只能发一次 KEY idx_user_status (user_id, status), KEY idx_category_hot (category_id, status, hot_score DESC), KEY idx_published_at (published_at DESC), KEY idx_status_hot (status, hot_score DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **字段说明:** - `publication_id` 唯一约束:同一个发布记录只能出现在广场一次,防止重复发布。 - 快照字段(`user_slug` 等)在发帖时写入,此后不随用户改名而变。如果用户改名,需要额外的更新任务,但不影响已有帖子的显示。 - `status = hidden` 覆盖两种场景:运营主动隐藏、publication 被原作者下线。 - `hot_score` 由后台定时任务计算写入,不在每次请求时实时计算。 --- ## plaza_reactions(点赞 / 收藏 / 分享) ```sql CREATE TABLE plaza_reactions ( id CHAR(36) NOT NULL, post_id CHAR(36) NOT NULL, -- → plaza_posts.id user_id CHAR(36) NOT NULL, -- → h5_users.id type ENUM( 'like', 'collect', 'share' ) NOT NULL, created_at BIGINT NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_user_post_type (post_id, user_id, type), -- 同一用户同一帖子同类型只能有一条 KEY idx_post_type (post_id, type), KEY idx_user_type (user_id, type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **触发器逻辑(应用层实现,不用数据库触发器):** - 插入 reaction → Redis INCR `plaza:post:{id}:like_count` - 删除 reaction(取消点赞)→ Redis DECR - 定时任务每 5 分钟将 Redis 计数同步到 `plaza_posts` 对应字段 --- ## plaza_comments(评论) ```sql CREATE TABLE plaza_comments ( id CHAR(36) NOT NULL, post_id CHAR(36) NOT NULL, -- → plaza_posts.id user_id CHAR(36) NOT NULL, -- → h5_users.id parent_id CHAR(36) NULL, -- 二级回复时指向父评论,NULL 表示一级评论 -- 内容 content VARCHAR(500) NOT NULL, -- 状态 status ENUM( 'visible', 'deleted', -- 用户自己删除(内容替换为空,记录保留) 'flagged', -- 被举报,等待审核 'hidden' -- 运营隐藏 ) NOT NULL DEFAULT 'visible', -- 计数 like_count INT UNSIGNED NOT NULL DEFAULT 0, reply_count INT UNSIGNED NOT NULL DEFAULT 0, -- 仅一级评论有效 -- 删除人标记 deleted_by ENUM('user', 'ops') NULL, created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL, PRIMARY KEY (id), KEY idx_post_visible (post_id, status, created_at DESC), KEY idx_parent (parent_id), KEY idx_user (user_id, created_at DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **规则:** - 只支持两级:一级评论(`parent_id IS NULL`)和二级回复(`parent_id` 指向一级)。 - 二级回复不再允许继续嵌套,前端也不展示更深的层级。 - 删除评论时不物理删除,将 `content` 设为空字符串,`status` 设为 `deleted`,保留 `reply_count` 用于展示"该评论已删除,有 N 条回复"。 - `post_id` 的 `comment_count` 只计算 `status = visible` 的一级评论数量。 --- ## plaza_follows(关注关系) ```sql CREATE TABLE plaza_follows ( follower_id CHAR(36) NOT NULL, -- 关注者 → h5_users.id followee_id CHAR(36) NOT NULL, -- 被关注者 → h5_users.id created_at BIGINT NOT NULL, PRIMARY KEY (follower_id, followee_id), KEY idx_followee (followee_id, created_at DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **说明:** - 不允许自己关注自己(应用层校验:`follower_id != followee_id`)。 - `h5_users` 表新增冗余计数字段 `plaza_following_count` 和 `plaza_follower_count`,关注/取关时事务内更新。 - 取关是物理删除行。 --- ## plaza_categories(广场分类) ```sql CREATE TABLE plaza_categories ( id CHAR(36) NOT NULL, name VARCHAR(50) NOT NULL, slug VARCHAR(50) NOT NULL, -- URL 友好,如 work-report icon VARCHAR(10) NOT NULL DEFAULT '', -- emoji 或图标代码 description VARCHAR(200) NOT NULL DEFAULT '', sort_order INT NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at BIGINT NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_slug (slug), KEY idx_sort (is_active, sort_order) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **初始分类数据:** | name | slug | icon | sort_order | | --- | --- | --- | --- | | 职场报告 | work-report | 📊 | 1 | | 学习笔记 | study-notes | 📚 | 2 | | 创意作品 | creative | 🎨 | 3 | | 门店展示 | business | 🏪 | 4 | | 旅行攻略 | travel | ✈️ | 5 | | 数据分析 | data-analysis | 📈 | 6 | | 生活记录 | lifestyle | 🌿 | 7 | | 其他 | other | 💡 | 99 | --- ## plaza_comment_reactions(评论点赞) ```sql CREATE TABLE plaza_comment_reactions ( id CHAR(36) NOT NULL, comment_id CHAR(36) NOT NULL, -- → plaza_comments.id user_id CHAR(36) NOT NULL, -- → h5_users.id created_at BIGINT NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_user_comment (comment_id, user_id), KEY idx_comment (comment_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` - 点赞/取消点赞时同步更新 `plaza_comments.like_count`(事务内,不走 Redis)。 - 不提供独立 Redis 键;评论点赞量低于帖子级互动,直接写 MySQL 即可。 --- ## plaza_algorithm_config(热度权重) 见 [05-feed-algorithm](./05-feed-algorithm) 中的建表 SQL。Sprint 3 引入。 --- ## plaza_reports(举报) 见 [06-ops-platform](./06-ops-platform) 中的建表 SQL。Sprint 5 引入。 --- ## plaza_featured(精选位) 见 [06-ops-platform](./06-ops-platform) 中的建表 SQL。Sprint 5 引入。 --- ## ops_audit_log(运营审计) 见 [06-ops-platform](./06-ops-platform) 中的建表 SQL。Sprint 5 引入。 --- ## h5_users / h5_publish_records 扩展字段 在现有表上通过 `db.mjs` 增量迁移添加: ```sql ALTER TABLE h5_users ADD COLUMN plaza_post_count INT UNSIGNED NOT NULL DEFAULT 0, ADD COLUMN plaza_follower_count INT UNSIGNED NOT NULL DEFAULT 0, ADD COLUMN plaza_following_count INT UNSIGNED NOT NULL DEFAULT 0, ADD COLUMN plaza_verified TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN plaza_post_banned TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN plaza_comment_banned TINYINT(1) NOT NULL DEFAULT 0, ADD COLUMN ops_role ENUM('none','reviewer','editor','ops_admin') NOT NULL DEFAULT 'none'; ALTER TABLE h5_publish_records ADD COLUMN plaza_view_count BIGINT NOT NULL DEFAULT 0, ADD COLUMN plaza_like_count BIGINT NOT NULL DEFAULT 0; ``` **`total_likes`(用户主页 API)**:不单独落库,查询时 `SUM(plaza_posts.like_count) WHERE user_id = ? AND status = 'published'`,或维护 `h5_users.plaza_total_likes` 冗余字段(Sprint 2 可选优化)。 --- ## Redis 数据结构 ```text # 实时计数(定时同步到 MySQL) plaza:post:{post_id}:view_count STRING INCR plaza:post:{post_id}:like_count STRING INCR/DECR # Feed 缓存(TTL 5 分钟) plaza:feed:hot:{category_id}:page:{n} STRING JSON 数组,热门帖子 ID 列表 plaza:feed:new:{category_id}:page:{n} STRING JSON 数组,最新帖子 ID 列表 # 用户行为缓存(判断当前用户是否已点赞) plaza:user:{user_id}:liked SET post_id 集合,TTL 30 分钟 ``` --- ## 索引策略 | 查询场景 | 使用索引 | | --- | --- | | 广场首页热门 feed | `idx_status_hot (status, hot_score DESC)` | | 分类热门 feed | `idx_category_hot (category_id, status, hot_score DESC)` | | 用户主页帖子列表 | `idx_user_status (user_id, status)` | | 最新帖子列表 | `idx_published_at (published_at DESC)` | | 帖子评论列表 | `idx_post_visible (post_id, status, created_at DESC)` | | 用户关注列表 | `idx_followee (followee_id, created_at DESC)` | --- ## 迁移规范 - 每次 schema 变更:更新 `ui/h5/schema.sql`,并在 `db.mjs` 的 `migrateSchema()` 中做幂等 `ALTER`(与现有 MindSpace 迁移方式一致)。 - 可选归档文件命名:`ui/h5/migrations/V{n}__plaza_{description}.sql`(仅作审计,执行入口仍是 `initSchema`)。 - 新增字段必须有 DEFAULT 值,不允许直接在非空表上添加 NOT NULL 无默认值的列。 - `plaza_posts` 的快照字段(`user_slug` 等)在写入后不自动同步更新,需要独立的后台对账任务。 - 删除字段前先废弃读写代码,确认无流量后再执行 DDL。 --- ## 不变量 - 每个 `publication_id` 在 `plaza_posts` 中最多出现一次(更新元数据走 `PATCH`,不换 publication)。 - `plaza_posts.status = hidden` 必须在 `h5_publish_records.status != 'online'` 时下线联动触发(同一事务)。 - `comment_count` 只统计 `status = visible` 的一级评论。 - 所有计数字段不允许出现负数(应用层保证 DECR 前先检查当前值)。 - `plaza_follows` 不允许 `follower_id = followee_id`。