Gogs 数据库表结构全解:八张核心表在 PostgreSQL / MySQL / SQLite3 下的设计与源码映射

Gogs 数据库表结构全解:八张核心表在 PostgreSQL / MySQL / SQLite3 下的设计与源码映射 Gogs 数据库表结构全解八张核心表在 PostgreSQL / MySQL / SQLite3 下的设计与源码映射【免费下载链接】gogsThe painless way to host your own Git service项目地址: https://gitcode.com/GitHub_Trending/go/gogsGogs 采用少表、多复用的数据库设计全部核心数据被收敛在docs/dev/database_schema.md所定义的八张表中access、access_token、action、email_address、follow、lfs_object、login_source、notice。本文以该文档中各表的跨数据库字段定义为骨架结合internal/database下的模型结构与internal/database/migrations中的版本迁移实现逐表解读每张表的字段语义、索引策略、GORM 标签映射以及数据迁移时的关键细节帮助你在排查权限问题、审计日志、LFS 对象归属或升级故障时能直接从表结构定位到源码实现。一、总体设计八张表与三数据库类型兼容docs/dev/database_schema.md中每张表都以三列对照的形式给出字段定义PostgreSQL 一列、MySQL 一列、SQLite3 一列这正是 Gogs 支持三种数据库后端的直接体现。与文档一一对应的是源码中的建表清单 Tables它以字母序排列、逐行独立列出八个模型结构体// Tables is the list of struct-to-table mappings. var Tables []any{ new(Access), new(AccessToken), new(Action), new(EmailAddress), new(Follow), new(LFSObject), new(LoginSource), new(Notice), }建表行为发生在 NewConnection 中GORM 配置了NamingStrategy{SingularTable: true}表名用单数形式MySQL 下显式指定ENGINEInnoDB。值得注意的是其中一条注释only use it to create new tables, and do customize migration with future changes——即AutoMigrate仅用于不存在的表HasTable检测后跳过已有表后续 schema 变更一律走独立的自定义迁移逻辑。这解释了为什么文档中记录的 schema 是稳定态而新增列、加索引这类改动都沉淀在迁移文件里详见后文版本迁移机制一节。从源码结构看各表的字段类型遵循一套统一约定理解它之后即可读懂文档中三列类型对照的含义自增主键PostgreSQL 用BIGSERIALMySQL 用BIGINT AUTO_INCREMENTSQLite3 用INTEGER AUTOINCREMENT在 GORM 中统一映射为int64gorm:primaryKey外键式关联用户 ID、仓库 ID统一为各数据库的大整数类型BIGINT/INTEGER不依赖数据库外键约束关联完整性由应用层保证文本PostgreSQL 用TEXT、MySQL 用LONGTEXT注意lfs_object.oid和login_source.name例外MySQL 下为VARCHAR(191)这通常是为了兼容旧版本索引前缀长度限制、SQLite3 用TEXT布尔值PostgreSQL/MySQL 为BOOLEANSQLite3 以NUMERIC表示时间戳多数表存 Unix 秒created_unix仅lfs_object例外存created_at时间类型。二、access仓库权限表文档定义FieldColumnPostgreSQLMySQLSQLite3IDidBIGSERIALBIGINT AUTO_INCREMENTINTEGER AUTOINCREMENTUserIDuser_idBIGINT NOT NULLBIGINT NOT NULLINTEGER NOT NULLRepoIDrepo_idBIGINT NOT NULLBIGINT NOT NULLINTEGER NOT NULLModemodeBIGINT NOT NULLBIGINT NOT NULLINTEGER NOT NULL主键为id唯一索引access_user_repo_unique (user_id, repo_id)保证同一用户对同一仓库至多一条权限记录。Mode列的取值由源码中的枚举 AccessMode 定义0none、1read、2write、3admin、4owner对应文档中该列的整型存储。Access 结构体 通过gorm:uniqueIndex:access_user_repo_unique;not null标签精确复现了文档中的唯一索引名。这张表有一个容易被忽略的语义细节结构体注释明确说明仓库的真实所有者并不存入该表只有组织仓库中 owners 团队成员、协作者等非所有者权限才记录于此。权限判定的完整链路见 AccessMode 方法公开仓库对所有人先给读权限 → 匿名到此为止 → 命中所有者 ID 直接返回 Owner → 最后按user_id repo_id查表。批量授权走 SetRepoPerms在一个事务里先删该仓库全部旧记录再整批重建。排查某用户为什么能/不能 push时这条判定顺序就是依据。三、access_token个人访问令牌表文档定义FieldColumnPostgreSQLMySQLSQLite3IDidBIGSERIALBIGINT AUTO_INCREMENTINTEGER AUTOINCREMENTUserIDuidBIGINTBIGINTINTEGERNamenameTEXTLONGTEXTTEXTSha1sha1VARCHAR(40) UNIQUEVARCHAR(40) UNIQUEVARCHAR(40) UNIQUESHA256sha256VARCHAR(64) NOT NULL UNIQUE三库一致CreatedUnix / UpdatedUnixcreated_unix / updated_unixBIGINT三库对应整型主键id普通索引idx_access_token_user_id (uid)。注意两列列名细节UserID落库列为uid而非user_id由 AccessToken 结构体 的gorm:column:uid;index标签保证SHA256列在 GORM 中的大写字段名会按标签映射为小写sha256列。这张表是版本迁移机制的典型案例。SHA256列是后加的——迁移 migrateAccessTokenToSHA256 完整展示了给存量表加带约束新列的三步事务操作先不加约束地AddColumn此时所有行均为 NULL无法直接上NOT NULL逐行取sha256 IS NULL的记录用 cryptox.SHA256 对旧sha1值做二次摘要回填数据补齐后再通过带unique;not null约束的结构体执行AutoMigrate安全地施加约束。sha1列保留为兼容历史 token 的查询通道sha256列则承担当前认证路径。另外AfterFind钩子access_tokens.go#L41-L49会从updated_unix派生HasUsed和近 7 天有活动两个瞬态字段用于 UI 上提示闲置令牌这些字段不落库gorm:-。四、action用户操作日志表文档定义字段较多完整列出FieldColumnPostgreSQLMySQLSQLite3IDidBIGSERIALBIGINT AUTO_INCREMENTINTEGER AUTOINCREMENTUserIDuser_idBIGINTBIGINTINTEGEROpTypeop_typeBIGINTBIGINTINTEGERActUserIDact_user_idBIGINTBIGINTINTEGERActUserNameact_user_nameTEXTLONGTEXTTEXTRepoIDrepo_idBIGINTBIGINTINTEGERRepoUserNamerepo_user_nameTEXTLONGTEXTTEXTRepoNamerepo_nameTEXTLONGTEXTTEXTRefNameref_nameTEXTLONGTEXTTEXTIsPrivateis_privateBOOLEAN NOT NULL DEFAULT FALSESQLite3 为 NUMERICContentcontentTEXTLONGTEXTTEXTCreatedUnixcreated_unixBIGINTBIGINTINTEGER主键id索引idx_action_repo_id (repo_id)与idx_action_user_id (user_id)。两个索引的来历值得对照源码repo_id索引由 Action 结构体 上gorm:index标签声明而user_id索引是后来补的——迁移 addIndexToActionUserID 先HasIndex探测、已存在则跳过再CreateIndex。这说明该表查询热点在按仓库翻动态和按用户查收件两条路径上。字段语义上有三对易混淆概念读表前应分清user_id是动态的接收者订阅了动态的用户act_user_id/act_user_name是操作者doer仓库归属信息则冗余存在repo_user_name/repo_name中避免联表op_type覆盖推送、创建/关闭 issue 与 pull request、分支标签增删、fork、mirror 同步等二十余种操作类型见 actions.go#L700-L709 的枚举尾部content存操作详情如 issue 编号、分支名is_private标记该动态所属仓库是否私有用于动态页的可见性过滤。结构体上的BeforeCreate/AfterFind钩子actions.go#L731-L743解释了created_unix的使用方式写入时若未显式赋值则自动取当前 Unix 秒读取时再换算回time.Time供模板渲染——这是 Gogs 时间存储的通用模式lfs_object是唯一例外。五、email_address 与 follow用户关联表email_address文档定义FieldColumnPostgreSQLMySQLSQLite3IDidBIGSERIALBIGINT AUTO_INCREMENTINTEGER AUTOINCREMENTUserIDuidBIGINT NOT NULLBIGINT NOT NULLINTEGER NOT NULLEmailemailVARCHAR(254) NOT NULLVARCHAR(254) NOT NULLTEXT NOT NULLIsActivatedis_activatedBOOLEAN NOT NULL DEFAULT FALSESQLite3 为 NUMERIC主键id联合唯一索引email_address_user_email_unique (uid, email)外加uid单列索引idx_email_address_user_id。模型 EmailAddress 中email列显式size:254与文档的VARCHAR(254)一致IsPrimary是瞬态字段不落库。follow文档定义id自增主键、user_id、follow_id三列均 NOT NULL唯一索引follow_user_follow_unique (user_id, follow_id)防止重复关注。模型 Follow 用uniqueIndex:follow_user_follow_unique复现该约束。这张表是标准的关系表(user_id, follow_id)一行即user_id 关注了 follow_id双向查询粉丝/关注列表各走该索引的一个方向。六、lfs_objectLFS 对象归属表文档定义是八张表中结构最特殊的一张FieldColumnPostgreSQLMySQLSQLite3RepoIDrepo_idBIGINTBIGINTINTEGEROIDoidTEXTVARCHAR(191)TEXTSizesizeBIGINT NOT NULLBIGINT NOT NULLINTEGER NOT NULLStoragestorageTEXT NOT NULLLONGTEXT NOT NULLTEXT NOT NULLCreatedAtcreated_atTIMESTAMPTZ NOT NULLDATETIME(3) NOT NULLDATETIME NOT NULL主键为复合主键(repo_id, oid)且没有自增 ID 列。它记录某个 LFS 对象归属于哪个仓库配合internal/lfsx的对象存储层使用LFSObject 结构体 中RepoID声明auto_increment:false并参与复合主键OID使用强校验的lfsx.OID类型。写入入口 CreateObject 在对象成功落盘后登记该关系。由于主键即业务键同一仓库下同一 OID 天然幂等重复推送同一 LFS 对象不会产生脏记录。七、login_source登录源配置表文档定义FieldColumnPostgreSQLMySQLSQLite3IDidBIGSERIALBIGINT AUTO_INCREMENTINTEGER AUTOINCREMENTTypetypeBIGINTBIGINTINTEGERNamenameTEXT UNIQUEVARCHAR(191) UNIQUETEXT UNIQUEIsActivedis_activedBOOLEAN NOT NULLBOOLEAN NOT NULLNUMERIC NOT NULLIsDefaultis_defaultBOOLEANBOOLEANNUMERICConfigcfgTEXTTEXTTEXTCreatedUnix / UpdatedUnixcreated_unix / updated_unixBIGINT主键id无额外索引name的 UNIQUE 约束自带索引。模型 LoginSource 中Config落库列为cfggorm:column:cfg存的是各 Provider 的 JSON 配置Provider字段是运行时反序列化出来的gorm:-瞬态对象。type列区分 PAM、LDAP、SMTP、GitHub 等登录方式与conf/auth.d/下的示例配置如 github.conf.example、pam.conf.example共同构成管理员后台配置登录源时的取值参考。八、notice系统通知表文档定义id自增主键、typeBIGINT、descriptionTEXT、created_unixBIGINT无附加索引。模型 Notice 中当前只定义了NoticeTypeRepository 1一种类型notices.go#L63-L65TrStr按admin.notices.type_N生成翻译键说明该表是面向管理员后台的系统级提示例如定时清理任务失败的兜底通知见同文件的RemoveAllWithNotice。九、版本迁移机制schema 演进的落点上述各表中的后加列如access_token.sha256与后加索引如action.user_id都不是靠启动时全量AutoMigrate实现的而是沉淀在 migrations.go 的有序迁移列表中。该文件的机制值得完整理解版本锚点minDBVersion 19version表仅存一行id 1的版本记录Version 结构体迁移序列migrations.go#L42-L62v19 → v20令牌 SHA256 迁移 →v20 → v21action 加索引 →v21 → v22是一个 noop源码注释解释了它存在的原因曾有一处版本计算 bug 导致部分实例停留在 v21、部分在 v22noop 保证两条路径汇合后都能命中后续真实迁移执行逻辑Migrate先保证version表存在新库直接写入minDBVersion len(migrations)作为当前版本旧库若版本低于minDBVersion则打印分版本回迁指引并拒绝继续注释给出了 0.7.33 / 0.9.141 / 0.12.0 三个最后支持版本的对应关系检测到降级部署时把版本号回调到可执行上限否则从version - minDBVersion处逐条执行、每执行一条立即持久化版本号单条迁移可返回errMigrationSkipped幂等跳过。升级 Gogs 后如遇数据库结构报错正确排查顺序是先看version.version是否与minDBVersion 迁移数匹配再对照迁移日志中Migration: xxx行定位卡在哪一条。十、跨数据库差异速查与实操建议把文档三列类型差异归纳为四条实操规则布尔列SQLite3 一律是NUMERIC手写 SQL 判空用 1/ 0最稳妥唯一索引命名GORM 标签显式指定了索引名access_user_repo_unique、email_address_user_email_unique、follow_user_follow_unique跨数据库查表结构时可直接用这些名字EXPLAIN验证索引是否命中MySQL 变长文本除lfs_object.oid、login_source.nameVARCHAR(191)外统一LONGTEXT若需手工建兼容表注意区分时间列除lfs_object.created_at时间类型与 MySQL 的DATETIME(3)精度外其余表统一 Unix 秒整数跨表按时间过滤时应先确认目标表属于哪种。结合 NewConnection 中按conf.Database.Type分支设置UsePostgreSQL/UseMySQL/UseSQLite3的逻辑可以确认三种后端在应用层是同一套 GORM 模型数据库特性差异被压缩到类型映射与gorm:table_options这一层这也是文档能以一张三列表完整描述全部 schema 的前提。小结docs/dev/database_schema.md定义的八张表各自承担明确职责accessfollow构成权限与社交关系action承载操作审计access_token支撑 API 认证email_address管理用户邮箱lfs_object记录 LFS 对象归属login_source保存外部登录源配置notice存储系统级提示。所有字段与 Tables 列表 中的 GORM 模型一一对应schema 演进则集中在 internal/database/migrations 的版本化迁移中完成。理解这套结构后无论是调试权限判定、排查令牌认证失败还是处理升级过程中的迁移报错都可以从表定义直接走到对应的模型方法与迁移函数。【免费下载链接】gogsThe painless way to host your own Git service项目地址: https://gitcode.com/GitHub_Trending/go/gogs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考