Files
vibe_coding/docs/architecture/data-model.md
2026-03-05 21:27:11 +08:00

749 lines
29 KiB
Markdown
Raw Permalink 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.
# Data Model — MySQL 数据建模
> 定义数据库表结构、关系和设计约定。AI Agent 在生成 Migration 和 Model 时参考本文档。
---
## 1. 设计原则
- 主键统一使用 `BIGINT UNSIGNED AUTO_INCREMENT`
- 字符集 `utf8mb4`,排序规则 `utf8mb4_unicode_ci`
- 所有表包含 `created_at`, `updated_at`, `deleted_at` (软删除)
- 外键字段命名 `<table_singular>_id`,必须有索引
- 金额字段使用 `DECIMAL(10,2)`
- 状态字段使用 `VARCHAR(20)``TINYINT`(避免 ENUM
- 时间字段使用 `TIMESTAMP`
- 排序字段使用 `INT UNSIGNED DEFAULT 0`
## 2. 命名规范
### 2.1 表命名规则
**表名必须以所属模块名作为前缀**snake_case格式`<module>_<entity_plural>`
| 模块 | 前缀 | 说明 |
|------|------|------|
| 用户与权限 | `auth_` | 用户、角色、菜单、OAuth 等 |
| 案例核心 | `case_` | 案例主表、图库、文件、分类等 |
| 设计师 | `designer_` | 设计师档案、奖项、标签关联等 |
| 运营内容 | `operation_` | 轮播图、专题、标签字典等 |
| 用户互动 | `interaction_` | 收藏、点赞、评论、关注、订阅等 |
| 日志 | `log_` | 登录日志、操作日志、下载日志等 |
| 安全风控 | `security_` | 黑名单、风控事件、会话等 |
| 系统配置 | `system_` | 系统参数配置等 |
> **规则说明**
> - 同一业务域下的所有表使用相同前缀,避免不同模块表名冲突
> - 多对多关联表同样需要加模块前缀:`<module>_<a>_belongs_<b>`
> - 跨模块关联的中间表,以**主体模块**的前缀命名
### 2.2 通用命名约定
| 类型 | 规范 | 示例 |
|------|------|------|
| 表名 | `<module>_` 前缀 + snake_case 复数 | `case_cases`, `auth_users` |
| 字段名 | snake_case | `total_building_area` |
| 主键 | `id` | `BIGINT UNSIGNED` |
| 外键 | `<singular>_id` | `case_id`, `designer_id` |
| 索引 | `idx_<table>_<cols>` | `idx_case_cases_status` |
| 唯一索引 | `uk_<table>_<cols>` | `uk_case_cases_code` |
| 多对多关联表 | `<module>_<a>_belongs_<b>` | `case_case_belongs_tag` |
| 布尔字段 | `is_<adj>` | `is_featured`, `is_active` |
| 计数缓存 | `<noun>_count` | `view_count`, `like_count` |
### 2.3 各模块表名对照
| 当前表名 | 所属模块 | 规范表名 |
|----------|----------|----------|
| `users` | 用户与权限 | `auth_users` |
| `oauth_bindings` | 用户与权限 | `auth_oauth_bindings` |
| `roles` | 用户与权限 | `auth_roles` |
| `menus` | 用户与权限 | `auth_menus` |
| `user_belongs_role` | 用户与权限 | `auth_user_belongs_role` |
| `role_belongs_menu` | 用户与权限 | `auth_role_belongs_menu` |
| `categories` | 案例核心 | `case_categories` |
| `tags` | 运营内容 | `operation_tags` |
| `cases` | 案例核心 | `case_cases` |
| `case_images` | 案例核心 | `case_images`(已有前缀) |
| `case_files` | 案例核心 | `case_files`(已有前缀) |
| `case_belongs_tag` | 案例核心 | `case_belongs_tag`(已有前缀) |
| `case_team_members` | 案例核心 | `case_team_members`(已有前缀) |
| `designers` | 设计师 | `designer_profiles` |
| `designer_awards` | 设计师 | `designer_awards`(已有前缀) |
| `designer_belongs_tag` | 设计师 | `designer_belongs_tag`(已有前缀) |
| `banners` | 运营内容 | `operation_banners` |
| `topics` | 运营内容 | `operation_topics` |
| `topic_belongs_case` | 运营内容 | `operation_topic_belongs_case` |
| `favorites` | 用户互动 | `interaction_favorites` |
| `likes` | 用户互动 | `interaction_likes` |
| `comments` | 用户互动 | `interaction_comments` |
| `follows` | 用户互动 | `interaction_follows` |
| `newsletter_subscriptions` | 用户互动 | `interaction_newsletter_subscriptions` |
| `user_login_logs` | 日志 | `log_user_logins` |
| `admin_operation_logs` | 日志 | `log_admin_operations` |
| `download_logs` | 日志 | `log_downloads` |
| `user_sessions` | 安全风控 | `security_user_sessions` |
| `security_risk_events` | 安全风控 | `security_risk_events`(已有前缀) |
| `security_blacklists` | 安全风控 | `security_blacklists`(已有前缀) |
| `system_configs` | 系统配置 | `system_configs`(已有前缀) |
---
## 3. ER 关系总览
```
用户与权限
─────────
users ←→ user_belongs_role ←→ roles
│ │
└→ oauth_bindings role_belongs_menu ←→ menus
案例核心
────────
categories ←── cases ──→ case_images
│ └──→ case_files
│ └──→ case_belongs_tag ←── tags
│ └──→ case_team_members ──→ designers
│ │
│ └→ designer_awards
│ └→ designer_belongs_tag ←── tags
└──→ favorites ←── users
└──→ likes ←── users
└──→ comments ←── users
运营内容
────────
banners (独立)
topics ←→ topic_belongs_case ←→ cases
会话与安全
──────────
users ──→ user_sessions
users ──→ security_risk_events
security_blacklists (独立)
日志与系统
──────────
user_login_logs
admin_operation_logs
download_logs
system_configs
newsletter_subscriptions
```
---
## 4. 核心数据表
### 4.1 用户与权限域
#### `users` — 统一用户表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK, AUTO_INCREMENT | |
| `user_type` | VARCHAR(20) | NOT NULL, DEFAULT 'user' | user / admin |
| `username` | VARCHAR(50) | NULL, UNIQUE | 登录账号 |
| `phone` | VARCHAR(20) | NULL, UNIQUE | 手机号 |
| `password` | VARCHAR(255) | NULL | bcrypt 哈希 |
| `enterprise_user_id` | VARCHAR(100) | NULL, UNIQUE | 企业平台用户唯一标识 |
| `source` | VARCHAR(20) | NOT NULL, DEFAULT 'local' | local / enterprise / wechat / qq |
| `is_enterprise_linked` | TINYINT | NOT NULL, DEFAULT 0 | 是否已绑定企业账号 |
| `nickname` | VARCHAR(50) | NULL | 显示昵称 |
| `avatar` | VARCHAR(500) | NULL | 头像 URL |
| `role_label` | VARCHAR(50) | NULL | 展示角色标签(如 Architect |
| `previous_password_hash` | VARCHAR(255) | NULL | 上次密码哈希(防重复使用) |
| `agree_at` | TIMESTAMP | NULL | 用户同意协议时间 |
| `agreement_version` | VARCHAR(30) | NULL | 同意时协议版本 |
| `status` | TINYINT | NOT NULL, DEFAULT 1 | 1=启用, 0=禁用 |
| `deactivation_requested_at` | TIMESTAMP | NULL | 注销申请时间(冷静期起始) |
| `scheduled_deletion_at` | TIMESTAMP | NULL | 计划删除时间(冷静期结束) |
| `last_login_at` | TIMESTAMP | NULL | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
| `deleted_at` | TIMESTAMP | NULL | |
索引:`uk_users_username`, `uk_users_phone`, `uk_users_enterprise_user_id`, `idx_users_user_type`, `idx_users_status`
#### `oauth_bindings` — 第三方登录绑定
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `user_id` | BIGINT UNSIGNED | FK → users | |
| `provider` | VARCHAR(20) | NOT NULL | wechat / qq / enterprise |
| `provider_user_id` | VARCHAR(100) | NOT NULL | 第三方平台用户 ID |
| `access_token` | VARCHAR(500) | NULL | |
| `refresh_token` | VARCHAR(500) | NULL | |
| `expires_at` | TIMESTAMP | NULL | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`uk_oauth_provider_uid (provider, provider_user_id)`, `idx_oauth_user_id`
#### `roles` — 角色表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `name` | VARCHAR(50) | NOT NULL, UNIQUE | 角色标识super_admin / content_ops / reviewer |
| `display_name` | VARCHAR(50) | NOT NULL | 超级管理员 / 内容运营 / 审核员 |
| `description` | VARCHAR(200) | NULL | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
#### `menus` — 菜单/权限表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `parent_id` | BIGINT UNSIGNED | DEFAULT 0 | 树形结构 |
| `name` | VARCHAR(50) | NOT NULL | 权限标识 `case:create` |
| `display_name` | VARCHAR(50) | NOT NULL | 菜单显示名 |
| `type` | VARCHAR(10) | NOT NULL | menu / button |
| `path` | VARCHAR(200) | NULL | 前端路由路径 |
| `icon` | VARCHAR(50) | NULL | 图标名 |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `is_visible` | TINYINT | DEFAULT 1 | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
#### `user_belongs_role` — 用户-角色关联
| 字段 | 类型 | 约束 |
|------|------|------|
| `user_id` | BIGINT UNSIGNED | PK(联合), FK → users |
| `role_id` | BIGINT UNSIGNED | PK(联合), FK → roles |
#### `role_belongs_menu` — 角色-菜单关联
| 字段 | 类型 | 约束 |
|------|------|------|
| `role_id` | BIGINT UNSIGNED | PK(联合), FK → roles |
| `menu_id` | BIGINT UNSIGNED | PK(联合), FK → menus |
---
### 4.2 案例核心域
#### `categories` — 建筑类型分类
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `name` | VARCHAR(50) | NOT NULL, UNIQUE | 别墅 / 民宿 / 室内 / 景观 / 农村自建房 / 庭院 |
| `slug` | VARCHAR(50) | NOT NULL, UNIQUE | villa / homestay / interior / landscape |
| `icon` | VARCHAR(50) | NULL | |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `case_count` | INT UNSIGNED | DEFAULT 0 | 缓存引用计数 |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
#### `tags` — 统一标签字典
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `type` | VARCHAR(20) | NOT NULL | style / feature / specialty |
| `name` | VARCHAR(50) | NOT NULL | 现代 / 落地窗 / 参数化设计 |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `reference_count` | INT UNSIGNED | DEFAULT 0 | 被引用次数 |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`uk_tags_type_name (type, name)`, `idx_tags_type`
#### `cases` — 案例主表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `category_id` | BIGINT UNSIGNED | FK → categories | 建筑类型 |
| `title` | VARCHAR(200) | NOT NULL | 项目名称 |
| `code` | VARCHAR(30) | NOT NULL, UNIQUE | 项目编号 P-2024-8839 |
| `cover_image` | VARCHAR(500) | NULL | 封面图 URL |
| `status` | VARCHAR(20) | NOT NULL, DEFAULT 'draft' | draft / pending / published / archived |
| `is_featured` | TINYINT | DEFAULT 0 | 是否精选 |
| `featured_sort` | INT UNSIGNED | DEFAULT 0 | 精选排序 |
| — **户型布局** — | | | |
| `layout` | VARCHAR(50) | NULL | 3室2厅3卫 |
| `floor_height` | VARCHAR(50) | NULL | 3.6/3.3/3.3m |
| — **用地条件** — | | | |
| `site_width` | VARCHAR(50) | NULL | 面宽 ≥ 10.5m |
| `site_depth` | VARCHAR(50) | NULL | 进深 ≥ 12.2m |
| `site_area` | DECIMAL(10,2) | NULL | 占地面积 m² |
| `lighting_restriction` | VARCHAR(100) | NULL | 采光限制 |
| `site_shape` | VARCHAR(50) | NULL | 用地形状 |
| — **规模造价** — | | | |
| `floors_above` | TINYINT UNSIGNED | NULL | 地上层数 |
| `floors_below` | TINYINT UNSIGNED | NULL | 地下层数 |
| `cost_min` | DECIMAL(12,2) | NULL | 造价预估下限(万元) |
| `cost_max` | DECIMAL(12,2) | NULL | 造价预估上限(万元) |
| `total_building_area` | DECIMAL(10,2) | NULL | 建筑总面积 m² |
| `bedroom_count` | TINYINT UNSIGNED | NULL | 卧室数量 |
| `bedroom_note` | VARCHAR(100) | NULL | 含1主卧套房 |
| — **平面功能** — | | | |
| `stair_type` | VARCHAR(100) | NULL | 楼梯类型 |
| `special_space` | VARCHAR(200) | NULL | 特色空间 / 农村特色 |
| `bedroom_config` | VARCHAR(200) | NULL | 卧室配置 |
| `leisure_space` | VARCHAR(200) | NULL | 休闲空间 |
| `kitchen_dining` | VARCHAR(200) | NULL | 厨房餐厅 |
| `bathroom_config` | VARCHAR(100) | NULL | 卫生间配置 |
| — **外观风格** — | | | |
| `architecture_style` | VARCHAR(100) | NULL | 建筑风格 |
| `roof_type` | VARCHAR(100) | NULL | 屋顶形式 |
| `facade_material` | VARCHAR(200) | NULL | 外立面材料 |
| — **结构与性能** — | | | |
| `structure_type` | VARCHAR(100) | NULL | 结构类型 |
| `foundation_type` | VARCHAR(100) | NULL | 基础类型 |
| `layout_logic` | VARCHAR(200) | NULL | 户型逻辑 |
| `wall_thickness` | VARCHAR(100) | NULL | 墙体厚度 |
| `seismic_rating` | VARCHAR(100) | NULL | 抗震设防 |
| `insulation_type` | VARCHAR(100) | NULL | 保温形式 |
| `roof_structure` | VARCHAR(200) | NULL | 屋面构造 |
| — **设计理念** — | | | |
| `design_concept` | TEXT | NULL | 设计理念富文本 |
| `concept_style` | VARCHAR(100) | NULL | 风格提炼 |
| `concept_material` | VARCHAR(100) | NULL | 材质提炼 |
| `concept_target` | VARCHAR(100) | NULL | 受众提炼 |
| — **分类特有扩展** — | | | |
| `meta` | JSON | NULL | 分类特有参数(景观面积 / 客房数 / 主材等) |
| — **统计计数** — | | | |
| `view_count` | INT UNSIGNED | DEFAULT 0 | |
| `like_count` | INT UNSIGNED | DEFAULT 0 | |
| `favorite_count` | INT UNSIGNED | DEFAULT 0 | |
| `download_count` | INT UNSIGNED | DEFAULT 0 | |
| `comment_count` | INT UNSIGNED | DEFAULT 0 | |
| — **时间** — | | | |
| `published_at` | TIMESTAMP | NULL | 发布时间 |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
| `deleted_at` | TIMESTAMP | NULL | |
索引:`uk_cases_code`, `idx_cases_category_id`, `idx_cases_status`, `idx_cases_is_featured`, `idx_cases_status_created (status, created_at)`, `idx_cases_status_featured (status, is_featured, featured_sort)`
> `meta` JSON 用于存储不同分类的特有参数。例如:
> - 景观类案例:`{"landscape_area": 270, "main_vegetation": "黑松/苔藓", "paving_material": "花岗岩/白砂"}`
> - 民宿类案例:`{"room_count": 12, "room_note": "12间套房"}`
> - 室内类案例:`{"main_material": "微水泥/钢构"}`
> - 商业类案例:`{"building_height": "120m", "function_layout": "办公/商业"}`
#### `case_images` — 案例图库
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `case_id` | BIGINT UNSIGNED | FK → cases | |
| `type` | VARCHAR(20) | NOT NULL | effect / floor_plan / elevation |
| `url` | VARCHAR(500) | NOT NULL | 图片 URL |
| `thumbnail_url` | VARCHAR(500) | NULL | 缩略图 URL |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`idx_case_images_case_type (case_id, type)`
#### `case_files` — 案例文件资产
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `case_id` | BIGINT UNSIGNED | FK → cases | |
| `stage` | VARCHAR(20) | NOT NULL | floor_plan / exterior / construction / structure / interior / courtyard |
| `file_name` | VARCHAR(200) | NOT NULL | 原始文件名 |
| `file_path` | VARCHAR(500) | NOT NULL | 存储路径 / OSS key |
| `file_size` | BIGINT UNSIGNED | NOT NULL | 字节数 |
| `file_ext` | VARCHAR(20) | NOT NULL | dwg / pdf / rvt / jpg / png / zip |
| `mime_type` | VARCHAR(100) | NULL | |
| `uploader_id` | BIGINT UNSIGNED | FK → users | 上传者 |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`idx_case_files_case_stage (case_id, stage)`
> `stage` 枚举值对应 PRD F5.1 的 6 个设计阶段:
> - `floor_plan` — 平面阶段
> - `exterior` — 外观阶段
> - `construction` — 施工图阶段
> - `structure` — 结构阶段
> - `interior` — 室内阶段
> - `courtyard` — 庭院阶段
#### `case_belongs_tag` — 案例-标签关联
| 字段 | 类型 | 约束 |
|------|------|------|
| `case_id` | BIGINT UNSIGNED | PK(联合), FK → cases |
| `tag_id` | BIGINT UNSIGNED | PK(联合), FK → tags |
#### `case_team_members` — 案例主创团队
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `case_id` | BIGINT UNSIGNED | FK → cases | |
| `designer_id` | BIGINT UNSIGNED | FK → designers | |
| `role` | VARCHAR(30) | NOT NULL | scheme / structure / mep (方案/结构/水电) |
| `is_lead` | TINYINT | DEFAULT 0 | 是否主创 |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `created_at` | TIMESTAMP | | |
索引:`uk_case_team_case_designer_role (case_id, designer_id, role)`, `idx_case_team_designer_id`
---
### 4.3 设计师域
#### `designers` — 设计师档案
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `name_cn` | VARCHAR(50) | NOT NULL | 中文姓名 |
| `name_en` | VARCHAR(100) | NULL | 英文姓名 |
| `title` | VARCHAR(50) | NULL | 职位:方案主创设计师 |
| `title_badge` | VARCHAR(50) | NULL | 职位徽章Lead Architect |
| `company` | VARCHAR(100) | NULL | 所属机构 |
| `city` | VARCHAR(50) | NULL | 所在城市 |
| `portrait` | VARCHAR(500) | NULL | 个人写真 URL |
| `portrait_caption` | VARCHAR(100) | NULL | STUDIO PORTRAIT © 2023 |
| `years_of_experience` | TINYINT UNSIGNED | NULL | 从业年限 |
| `project_count` | INT UNSIGNED | DEFAULT 0 | 完成项目数 |
| `design_philosophy_quote` | TEXT | NULL | 设计理念引用语 |
| `design_philosophy` | TEXT | NULL | 设计理念详述(富文本) |
| `follower_count` | INT UNSIGNED | DEFAULT 0 | 关注者数量 |
| `view_count` | INT UNSIGNED | DEFAULT 0 | 主页浏览量 |
| `is_active` | TINYINT | DEFAULT 1 | 是否公开展示 |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
| `deleted_at` | TIMESTAMP | NULL | |
索引:`idx_designers_is_active`
#### `designer_awards` — 设计师荣誉奖项
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `designer_id` | BIGINT UNSIGNED | FK → designers | |
| `year` | SMALLINT UNSIGNED | NOT NULL | 获奖年份 |
| `title` | VARCHAR(200) | NOT NULL | 红点设计大奖 Red Dot Award |
| `description` | VARCHAR(300) | NULL | 最佳室内设计类 - The Cloud Villa |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`idx_awards_designer_year (designer_id, year DESC)`
#### `designer_belongs_tag` — 设计师-专长标签
| 字段 | 类型 | 约束 |
|------|------|------|
| `designer_id` | BIGINT UNSIGNED | PK(联合), FK → designers |
| `tag_id` | BIGINT UNSIGNED | PK(联合), FK → tags (type=specialty) |
---
### 4.4 运营内容域
#### `banners` — 首页轮播
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `title` | VARCHAR(200) | NOT NULL | 大标题 |
| `description` | TEXT | NULL | 描述摘要 |
| `image` | VARCHAR(500) | NOT NULL | 背景大图 URL |
| `label` | VARCHAR(50) | NULL | 标签文字:精选项目 |
| `link_type` | VARCHAR(20) | NOT NULL, DEFAULT 'case' | case / topic / external |
| `link_url` | VARCHAR(500) | NOT NULL | 跳转链接 |
| `sort` | INT UNSIGNED | DEFAULT 0 | |
| `is_active` | TINYINT | DEFAULT 1 | 启用/停用 |
| `published_at` | TIMESTAMP | NULL | 展示日期 |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`idx_banners_active_sort (is_active, sort)`
#### `topics` — 专题合集
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `title` | VARCHAR(200) | NOT NULL | 专题标题 |
| `cover_image` | VARCHAR(500) | NULL | 封面图 |
| `description` | TEXT | NULL | 富文本描述 |
| `status` | VARCHAR(20) | NOT NULL, DEFAULT 'draft' | draft / published |
| `case_count` | INT UNSIGNED | DEFAULT 0 | 关联案例数 |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
| `deleted_at` | TIMESTAMP | NULL | |
#### `topic_belongs_case` — 专题-案例关联
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `topic_id` | BIGINT UNSIGNED | PK(联合), FK → topics | |
| `case_id` | BIGINT UNSIGNED | PK(联合), FK → cases | |
| `sort` | INT UNSIGNED | DEFAULT 0 | 专题内排序 |
---
### 4.5 用户互动域
#### `favorites` — 收藏
| 字段 | 类型 | 约束 |
|------|------|------|
| `id` | BIGINT UNSIGNED | PK |
| `user_id` | BIGINT UNSIGNED | FK → users |
| `case_id` | BIGINT UNSIGNED | FK → cases |
| `created_at` | TIMESTAMP | |
索引:`uk_favorites_user_case (user_id, case_id)`, `idx_favorites_case_id`
#### `likes` — 点赞
| 字段 | 类型 | 约束 |
|------|------|------|
| `id` | BIGINT UNSIGNED | PK |
| `user_id` | BIGINT UNSIGNED | FK → users |
| `case_id` | BIGINT UNSIGNED | FK → cases |
| `created_at` | TIMESTAMP | |
索引:`uk_likes_user_case (user_id, case_id)`, `idx_likes_case_id`
#### `comments` — 评论
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `case_id` | BIGINT UNSIGNED | FK → cases | |
| `user_id` | BIGINT UNSIGNED | FK → users | |
| `parent_id` | BIGINT UNSIGNED | NULL, FK → comments | 回复嵌套 |
| `content` | TEXT | NOT NULL | 评论内容 |
| `status` | VARCHAR(20) | DEFAULT 'pending' | pending / approved / rejected |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
| `deleted_at` | TIMESTAMP | NULL | |
索引:`idx_comments_case_status (case_id, status)`, `idx_comments_user_id`, `idx_comments_parent_id`
#### `follows` — 关注设计师
| 字段 | 类型 | 约束 |
|------|------|------|
| `id` | BIGINT UNSIGNED | PK |
| `user_id` | BIGINT UNSIGNED | FK → users |
| `designer_id` | BIGINT UNSIGNED | FK → designers |
| `created_at` | TIMESTAMP | |
索引:`uk_follows_user_designer (user_id, designer_id)`, `idx_follows_designer_id`
#### `newsletter_subscriptions` — 邮箱订阅
| 字段 | 类型 | 约束 |
|------|------|------|
| `id` | BIGINT UNSIGNED | PK |
| `email` | VARCHAR(200) | NOT NULL, UNIQUE |
| `created_at` | TIMESTAMP | |
---
### 4.6 日志与系统域
#### `user_login_logs` — 登录日志
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `user_id` | BIGINT UNSIGNED | FK → users | |
| `login_type` | VARCHAR(20) | NOT NULL | password / sms / qr / wechat / qq / enterprise_sso |
| `ip` | VARCHAR(45) | NULL | |
| `login_location` | VARCHAR(100) | NULL | 登录地理位置(由 IP 库反查填充) |
| `device_fingerprint` | VARCHAR(128) | NULL | 设备指纹 |
| `risk_level` | VARCHAR(10) | NULL | L1 / L2 / L3 / L4 |
| `risk_reason` | VARCHAR(255) | NULL | 命中策略摘要 |
| `result` | VARCHAR(20) | NOT NULL, DEFAULT 'success' | success / failed / blocked |
| `user_agent` | VARCHAR(500) | NULL | |
| `created_at` | TIMESTAMP | | |
索引:`idx_login_logs_user_created (user_id, created_at)`, `idx_login_logs_ip_created (ip, created_at)`
#### `user_sessions` — 活跃会话
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `user_id` | BIGINT UNSIGNED | FK → users | |
| `session_token` | VARCHAR(255) | NOT NULL, UNIQUE | Refresh Token 标识(哈希值) |
| `device_name` | VARCHAR(100) | NULL | 设备名称(如 Chrome on macOS |
| `device_fingerprint` | VARCHAR(128) | NULL | 设备指纹 |
| `ip` | VARCHAR(45) | NULL | 登录 IP |
| `login_location` | VARCHAR(100) | NULL | 登录地理位置 |
| `is_current` | TINYINT | NOT NULL, DEFAULT 0 | 是否为当前请求会话 |
| `last_active_at` | TIMESTAMP | NULL | 最近活跃时间 |
| `expires_at` | TIMESTAMP | NOT NULL | 会话过期时间 |
| `revoked_at` | TIMESTAMP | NULL | 被踢出/注销时间(非空即失效) |
| `created_at` | TIMESTAMP | | |
索引:`idx_sessions_user_active (user_id, revoked_at, expires_at)`, `uk_sessions_token (session_token)`
> 活跃会话表用于 F1.9 多端会话管理。创建会话时写入Token 刷新时更新 `last_active_at`,退出/踢出时设置 `revoked_at`。定期清理过期记录。
#### `security_risk_events` — 风控事件日志
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `user_id` | BIGINT UNSIGNED | NULL, FK → users | 可为空(未登录攻击流量) |
| `event_type` | VARCHAR(30) | NOT NULL | login / sms_send / register / reset_password / sso_callback |
| `risk_level` | VARCHAR(10) | NOT NULL | L1 / L2 / L3 / L4 |
| `action` | VARCHAR(30) | NOT NULL | captcha_upgrade / rate_limit / temporary_block / blacklist |
| `ip` | VARCHAR(45) | NULL | |
| `device_fingerprint` | VARCHAR(128) | NULL | |
| `user_agent` | VARCHAR(500) | NULL | |
| `payload` | JSON | NULL | 事件上下文 |
| `created_at` | TIMESTAMP | | |
索引:`idx_risk_events_user_created (user_id, created_at)`, `idx_risk_events_level_created (risk_level, created_at)`, `idx_risk_events_ip_created (ip, created_at)`
> 风控事件日志不可编辑、不可删除,无 `updated_at` 和 `deleted_at`。
#### `security_blacklists` — 安全黑名单
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `target_type` | VARCHAR(20) | NOT NULL | ip / device / account |
| `target_value` | VARCHAR(255) | NOT NULL | IP、设备指纹或账号标识 |
| `reason` | VARCHAR(255) | NULL | 拉黑原因 |
| `source` | VARCHAR(20) | NOT NULL, DEFAULT 'system' | system / manual |
| `status` | VARCHAR(20) | NOT NULL, DEFAULT 'active' | active / released / expired |
| `expire_at` | TIMESTAMP | NULL | 为空表示永久 |
| `created_by` | BIGINT UNSIGNED | NULL, FK → users | 手动操作人(系统自动为空) |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`uk_blacklists_target (target_type, target_value)`, `idx_blacklists_status_expire (status, expire_at)`
#### `admin_operation_logs` — 管理端操作日志
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `user_id` | BIGINT UNSIGNED | FK → users | 操作人 |
| `action` | VARCHAR(30) | NOT NULL | create / update / delete / status_change |
| `resource_type` | VARCHAR(50) | NOT NULL | case / banner / topic / designer / comment |
| `resource_id` | BIGINT UNSIGNED | NOT NULL | 操作对象 ID |
| `before_value` | JSON | NULL | 变更前值 |
| `after_value` | JSON | NULL | 变更后值 |
| `ip` | VARCHAR(45) | NULL | |
| `created_at` | TIMESTAMP | | |
索引:`idx_op_logs_user_created (user_id, created_at)`, `idx_op_logs_resource (resource_type, resource_id)`
> 操作日志不可编辑、不可删除,无 `updated_at` 和 `deleted_at`。
#### `download_logs` — 文件下载日志
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `user_id` | BIGINT UNSIGNED | FK → users | |
| `case_id` | BIGINT UNSIGNED | FK → cases | |
| `case_file_id` | BIGINT UNSIGNED | FK → case_files | |
| `ip` | VARCHAR(45) | NULL | |
| `created_at` | TIMESTAMP | | |
索引:`idx_dl_logs_user_created (user_id, created_at)`, `idx_dl_logs_case_id`
#### `system_configs` — 系统配置
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | BIGINT UNSIGNED | PK | |
| `group` | VARCHAR(50) | NOT NULL | site / seo / upload / comment / watermark / security |
| `key` | VARCHAR(100) | NOT NULL | |
| `value` | TEXT | NULL | |
| `created_at` | TIMESTAMP | | |
| `updated_at` | TIMESTAMP | | |
索引:`uk_configs_group_key (group, key)`
---
## 5. 索引策略
### 必须索引
- 所有外键字段
- `status` 字段
- `created_at` 字段
- 唯一业务标识(`cases.code``tags.type+name``users.phone`
### 核心复合索引
```sql
-- 案例列表查询(分类 + 状态 + 时间)
idx_cases_category_status_created (category_id, status, created_at)
-- 案例精选排序
idx_cases_featured_sort (status, is_featured, featured_sort)
-- 文件按阶段查询
idx_case_files_case_stage (case_id, stage)
-- 评论列表
idx_comments_case_status (case_id, status, created_at)
```
### 索引限制
- 单表索引不超过 6 个
- 避免冗余索引
- 定期检查未使用的索引
---
## 6. 百万级数据表设计
### 分页优化
```sql
-- 游标分页(案例列表推荐使用)
SELECT * FROM cases WHERE status = 'published' AND id > :last_id ORDER BY id DESC LIMIT 20;
```
### 大表 COUNT 优化
```sql
-- 使用 categories.case_count / tags.reference_count 缓存计数
-- 使用 cases.view_count / like_count / favorite_count 避免 COUNT JOIN
```
### 分区策略(日志表)
```sql
-- 管理操作日志
ALTER TABLE admin_operation_logs PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
PARTITION p202601 VALUES LESS THAN (202602),
PARTITION p202602 VALUES LESS THAN (202603),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
-- 用户登录日志(数据量最大,优先分区)
ALTER TABLE user_login_logs PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
PARTITION p202601 VALUES LESS THAN (202602),
PARTITION p202602 VALUES LESS THAN (202603),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
-- 风控事件日志
ALTER TABLE security_risk_events PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
PARTITION p202601 VALUES LESS THAN (202602),
PARTITION p202602 VALUES LESS THAN (202603),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
```
---
*最后更新: 2026-02-28*