# 03 · 数据模型 > **读者**:实现数据库与领域层的 AI > **前置**:[01-product.md](01-product.md) > **后续**:[04-api.md](04-api.md)、[05-business-rules.md](05-business-rules.md) > **源码**:[`model/models.go`](../../models/models.go)、[`model/oauth.go`](../../models/oauth.go)、[`model/gitea.go`](../../models/gitea.go)、[`model/level.go`](../../models/level.go)、[`model/db.go`](../../models/db.go)、[`model/user_view.go`](../../models/user_view.go)、[`service/settings.go`](../../services/settings.go) 当前无独立 SQL migration;表由 GORM `AutoMigrate` 创建。新站可用正式 migration,但**字段语义应对齐**。 --- ## 1. ER 概览 ```mermaid erDiagram User ||--o{ Post : authors User ||--o{ Comment : authors Board ||--o{ Post : contains Post ||--o{ Comment : has Post ||--o{ PostLike : likes Post ||--o{ PostFavorite : favorites Post ||--o{ PostRevision : revisions Comment ||--o{ CommentLike : likes Comment ||--o{ CommentRevision : revisions Post ||--o| Poll : poll Poll ||--o{ PollOption : options PollOption ||--o{ PollVote : votes Post ||--o{ PostLotteryWinner : winners Post ||--o{ PostContentUnlock : unlocks User ||--o{ PointLedger : ledger User ||--o{ CheckIn : checkins User ||--o{ LotteryDraw : draws User ||--o{ UserBadge : earns BadgeDef ||--o{ UserBadge : defines User ||--o{ PrivateMessage : sends User ||--o{ PostReport : reports User ||--o{ FriendLinkApply : applies User ||--o{ Media : uploads ``` 另有:`ForumSetting`(键值)、`OAuthClient` / `OAuthAuthCode`、`GiteaRepo`、`SitePage`。 --- ## 2. 表与字段 说明:`json:"-"` 表示默认 API 序列化隐藏;软删列 `deleted_at` 表示 GORM soft delete。 ### 2.1 users | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | uint PK | | | | username | string(128) | unique, not null | 登录名 | | email | string(128) | index, default '' | 公开主页不返回 | | password | string(128) | not null | bcrypt 哈希,永不返回 | | nickname | string(64) | | 展示名 | | signature | string(512) | default '' | 个人签名 | | avatar | string(512) | | 相对或绝对 URL | | role | string(16) | default `user` | `user` \| `admin` | | verified | bool | index | 站长认证,免审 | | exp | int | default 0 | 经验(不可消费) | | points | int | default 0 | 可用积分 | | creator_income_total | int | default 0 | 创作分成累计 | | banned | bool | | 禁言 | | banned_at | *time | | | | last_login_at | *time | json 隐藏 | | | last_login_ip | string(45) | json 隐藏 | | | last_access_at | *time | json 隐藏 | 带鉴权访问 | | created_at / updated_at | time | | | | deleted_at | soft | | | **非落库展示字段**:`level`(由 Exp 推导)、`badges`(附加)。 视图结构:`UserPublic` / `UserSelf` / `UserAdmin`(见 [`model/user_view.go`](../../models/user_view.go))。 ### 2.2 boards | 字段 | 说明 | |------|------| | id, name(64), description(512) | | | icon(64), color_index(default -1) | -1=按 id 自动取色 | | sort_order | 升序 | | created_at, updated_at, deleted_at | | ### 2.3 posts | 字段 | 说明 | |------|------| | id, board_id, user_id | FK 索引 | | title(256), content(text) | HTML 正文 | | content_plain(text) | 纯文本搜索索引,`json:"-"` | | tags(256) | 逗号或空格分隔标签串 | | post_type | `normal`\|`question`\|`poll`\|`bounty`\|`lottery` | | question_resolved | 仅问答 | | bounty_points, bounty_status, bounty_comment_id | 悬赏 | | lottery_winner_count, lottery_status | 抽奖帖 | | pinned | 全局置顶 | | board_pinned | 版内置顶 | | featured | 精华 | | edit_locked | 禁止编辑 | | comments_locked | 禁止新评论 | | status | `pending`\|`published`\|`rejected` | | like_count, view_count | | | timestamps + soft delete | | 关联:Board, User, Comments。 ### 2.4 post_revisions 每次修改前保存旧版:`post_id`, `editor_id`, `title`, `content`, `tags`, `created_at`。 ### 2.5 comments | 字段 | 说明 | |------|------| | post_id, user_id | user_id=0 表示游客 | | floor | 楼层号 | | content | HTML/富文本 | | reply_to | *uint 回复目标评论 | | guest_nick / guest_email / guest_url | 游客信息 | | is_private | 私密评论 | | status | pending\|published\|rejected | | like_count | | | soft delete | | **非落库**:`reply_target`, `thread_parent_id`, `content_hidden`, `liked`。 ### 2.6 comment_revisions `comment_id`, `editor_id`, `content`, `created_at`(管理员可查)。 ### 2.7 post_likes / comment_likes / post_favorites 唯一索引:(post_id|comment_id, user_id)。收藏带 Post 关联。 ### 2.8 private_messages | 字段 | 说明 | |------|------| | from_user_id | 0=系统 | | to_user_id | | | subject(256), content(text) | | | kind | 见枚举 | | related_post_id, related_report_id | 可选 | | is_read | | | created_at | | ### 2.9 post_reports 帖或评举报:`post_id` 必填;`comment_id` 有值则为评论举报。 `reason`, `detail`, `status`, `handler_id`, `handle_note`, `handled_at`。 ### 2.10 friend_link_applies 申请字段:name, url, description, logo, reciprocal_page_url, link_on_homepage, reciprocal_verified / check_note / checked_at, status, review_note, reviewed_at + soft delete。 ### 2.11 media 上传索引:`category`=`avatars`\|`posts`\|`site`;`name`, `url`(unique), `size`, `content_type`, `storage_type`=`local`\|`s3`, `user_id`。 ### 2.12 point_ledgers `user_id`, `delta`, `balance`(变动后), `reason`, `ref_type`, `ref_id`, `note`, `created_at`。 ### 2.13 check_ins 唯一 `(user_id, day)`,day=`YYYY-MM-DD`;`points`, `streak`。 ### 2.14 lottery_draws 每日抽奖唯一 `(user_id, day)`;`points` 可为 0。 ### 2.15 post_content_unlocks 唯一 `(user_id, post_id, block_key)`;`cost`。 ### 2.16 site_pages `title`, `slug`(unique), `content`, `published`, `sort_order`, `show_in_footer`, `show_in_nav` + soft delete。 ### 2.17 polls / poll_options / poll_votes - Poll:`post_id` unique;`multi`, `max_choices`, `closed`, `ends_at` - Option:`post_id`, `text`(64), `sort_order`, `vote_count` - Vote:唯一 `(post_id, option_id, user_id)`(多选时多行) ### 2.18 post_lottery_winners `post_id`, `user_id`, `comment_id`, `created_at`。 ### 2.19 badge_defs / user_badges BadgeDef:`code` unique, `name`, `description`, `icon`, `kind`=`auto`\|`limited`, `metric`, `threshold`, `sort_order`, `enabled`。 UserBadge:唯一 `(user_id, badge_id)`;`awarded_at`, `awarded_by`(0=系统)。 ### 2.20 forum_settings | 字段 | 说明 | |------|------| | key | PK string(64) | | value | string(2048) | ### 2.21 oauth_clients / oauth_auth_codes Client:`client_id` unique, `client_secret_hash`, `name`, `redirect_uris`(可多行), `enabled`。 AuthCode:一次性码 + PKCE 字段 + `expires_at` + `used`。 ### 2.22 gitea_repos 同步缓存:`gitea_id` unique, owner/name/full_name, description, html_url, language, stars/forks, private, updated_at_remote, forum_user_id, synced_at。 --- ## 3. 枚举全集 ### 3.1 角色 Role `user` | `admin` ### 3.2 内容状态 ContentStatus `pending` | `published` | `rejected` ### 3.3 帖类型 PostType `normal` | `question` | `poll` | `bounty` | `lottery` ### 3.4 悬赏 BountyStatus `open` | `awarded` | `refunded`(空串视为非悬赏) ### 3.5 帖内抽奖 PostLotteryStatus `open` | `drawn` ### 3.6 私信 kind | 值 | 含义 | |----|------| | user | 用户互发 | | system | 系统通知 | | reject | 帖/评被拒 | | report_result | 举报处理结果 | | reply | 被回复 | | mention | 被 @ | | moderation | 待审提醒管理员 | ### 3.7 举报 Status:`pending` | `resolved` | `dismissed` Reason:`spam` | `abuse` | `illegal` | `irrelevant` | `other` ### 3.8 友链申请 `pending` | `approved` | `rejected` ### 3.9 积分 reason | 值 | 含义 | |----|------| | check_in | 签到 | | lottery | 每日抽奖 | | unlock_spend | 解锁消费 | | creator_income | 创作分成 | | admin_adjust | 管理员调账 | | bounty_escrow | 悬赏托管 | | bounty_award | 悬赏发放 | | bounty_refund | 悬赏退回 | ### 3.10 徽章 Kind:`auto` | `limited` Metric:`tenure_days` | `likes_received` | `creator_income` --- ## 4. 等级(Exp → Level) 源:[`model/level.go`](../../models/level.go) | Level | 最低 Exp | |-------|----------| | 1 | 0 | | 2 | 20 | | 3 | 50 | | 4 | 100 | | 5 | 200 | | 6 | 400 | | 7 | 800 | | 8 | 1500 | | 9 | 3000 | | 10 | 5000 | 管理员设等级时,应把 Exp 调到该等级门槛(见后台 API)。 --- ## 5. 内置自动徽章(seed) 源:[`model/db.go`](../../models/db.go) `seedDefaultBadges` | code | 名称 | metric | threshold | |------|------|--------|-----------| | tenure_30 | 初来乍到 | tenure_days | 30 | | tenure_365 | 资深居民 | tenure_days | 365 | | likes_10 | 小有人气 | likes_received | 10 | | likes_100 | 人气作者 | likes_received | 100 | | likes_1000 | 人气巨星 | likes_received | 1000 | | income_100 | 小有进账 | creator_income | 100 | | income_1000 | 创作达人 | creator_income | 1000 | 已存在同 `code` 则跳过插入。 --- ## 6. forum_settings 键与默认值 源:[`service/settings.go`](../../services/settings.go)、[`service/permalink.go`](../../services/permalink.go) ### 6.1 论坛限制 | Key | 默认 | 说明 | |-----|------|------| | post_edit_window_hours | 24 | 0 可表示特殊策略,以实现为准 | | comment_edit_window_minutes | 3 | | | rate_limit_post | 10 | 窗口内次数 | | rate_limit_comment | 10 | | | rate_limit_register | 10 | | | rate_limit_login | 10 | | | rate_limit_window_sec | 60 | | | post_title_max | 128 | | | post_tags_max | 256 | | | post_content_max | 50000 | | | comment_max | 5000 | | | search_keyword_min | 1 | | | search_keyword_max | 50 | | | page_size_default | 30 | API 硬上限 100 | | password_min_len | 6 | | | avatar_max_mb | 2 | | | signature_max | 200 | | | open_posts_in_new_tab | 1 | | | open_content_links_in_new_tab | 1 | | ### 6.2 Feed / 侧栏 / 友链展示 | Key | 默认 | |-----|------| | feed_list_style | `title`(另有 `excerpt` / `thumbnail`) | | aside_show_tag_cloud | 0 | | aside_show_recent_comments | 0 | | aside_show_friend_links | 1 | | aside_widgets | JSON 数组,见下 | | nav_show_friend_links | 1 | | footer_show_friend_links | 1 | | friend_link_reciprocal_check | 0 | | permalink_enabled | 0 | | permalink_ext | `html` | 默认 `aside_widgets`: ```json [ {"id":"tag_cloud","enabled":false}, {"id":"recent_comments","enabled":false}, {"id":"friend_links","enabled":true} ] ``` 合法 widget id:`tag_cloud` | `recent_comments` | `recent_users` | `friend_links`。 ### 6.3 SMTP | Key | 默认 | |-----|------| | smtp_enabled | 0 | | smtp_host | | | smtp_port | 465 | | smtp_username / smtp_password | | | smtp_from | | | smtp_from_name | 姜十三论坛 | | smtp_encryption | `ssl`(另有 `none` / `starttls`) | ### 6.4 OIDC | Key | 默认 | |-----|------| | oidc_enabled | 0 | | oidc_root_url | | | oidc_group_claim | groups | | oidc_admin_group | gitea-admin | | oidc_user_group | gitea-users | ### 6.5 Gitea 同步 | Key | 默认 | |-----|------| | gitea_sync_enabled | 0 | | gitea_base_url | | | gitea_token | | | gitea_sync_interval_min | 60 | ### 6.6 存储 | Key | 默认 | |-----|------| | storage_type | local | | storage_endpoint / region / bucket | region 默认 us-east-1 | | storage_access_key / storage_secret_key | | | storage_public_base_url / storage_prefix | | | storage_force_path_style | 1 | | storage_image_delivery | webp(或 original) | ### 6.7 站点品牌 | Key | 默认 | |-----|------| | site_name | 姜十三论坛 | | site_slogan | 拾三一隅,自在交流 | | site_description / site_keywords | 空 | | site_logo_mark | 姜 | | site_logo / site_favicon / site_og_image | 空 | | site_icp_beian | 空 | | site_icp_beian_url | https://beian.miit.gov.cn/ | | site_friend_links | `[]` JSON,最多 20 条 | --- ## 7. 升级兼容补丁(现网 InitDB) [`model/db.go`](../../models/db.go) 在 AutoMigrate 后: - 空 `status` 的帖/评 → `published` - 空 `post_type` → `normal` - Exp=0 用户按存量内容粗算经验:`posts*10 + comments*2 + like_sum` 新站若从空库开始可忽略;若迁移旧库需保留等价 backfill。 --- ## 8. 内容门控在库中的形态 **无独立表**存放门控块;存在 `posts.content` HTML 中,例如: ```html ... ... ... ``` 积分解锁 `block_key` = `sha256(innerHTML)[:16]`(hex),见 [`service/unlock.go`](../../services/unlock.go)。