4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
355 lines
13 KiB
Markdown
355 lines
13 KiB
Markdown
---
|
||
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`。
|