同步包路径与路由,去掉 SPA 构建步骤,对齐 Gitea 式 LICENSE,并更新规格/规则与占位 SSR。 Co-authored-by: Cursor <cursoragent@cursor.com>
12 KiB
03 · 数据模型
读者:实现数据库与领域层的 AI
前置:01-product.md
后续:04-api.md、05-business-rules.md
源码:model/models.go、model/oauth.go、model/gitea.go、model/level.go、model/db.go、model/user_view.go、service/settings.go
当前无独立 SQL migration;表由 GORM AutoMigrate 创建。新站可用正式 migration,但字段语义应对齐。
1. ER 概览
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 | 登录名 |
| 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)。
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_idunique;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)
| 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 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、service/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:
[
{"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 在 AutoMigrate 后:
- 空
status的帖/评 →published - 空
post_type→normal - Exp=0 用户按存量内容粗算经验:
posts*10 + comments*2 + like_sum
新站若从空库开始可忽略;若迁移旧库需保留等价 backfill。
8. 内容门控在库中的形态
无独立表存放门控块;存在 posts.content HTML 中,例如:
<members-only>...</members-only>
<reply-only>...</reply-only>
<points-only data-cost="10">...</points-only>
积分解锁 block_key = sha256(innerHTML)[:16](hex),见 service/unlock.go。