models.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Package model 组织域 GORM 领域模型(与 unified/02-data-model/core-model-ddl.sql 逐列对齐)。
  2. //
  3. // 设计决策(T1 D1/D2/D3,2026-11-06 用户拍板):
  4. // - D2 机构/校区主键 = int64 自增(InstitutionID/CampusID,非 ULID;对齐 scope.Scope 作用域字段 int64)。
  5. // - D1 引用人的外键列(Membership.CanonicalUserID / GuardianLink.GuardianID / StudentID)= string(ULID,size:26)。
  6. // - D3 关系表(Membership/GuardianLink)主键 = int64 自增。
  7. // - Role 为平台级字典表(不带作用域),role_code 为主键(字典码,非自增)。
  8. //
  9. // GORM 类型映射纪律(架构规格 §3.6,T2 验收"DDL 逐表与 GORM Model 对齐"的硬要求):
  10. // - **string 必须带 size:N**(GORM 未指定 size 的 string 默认 longtext,T2 对账实证漂移):
  11. // 每个 string/*string 字段的 size 严格等于 DDL 的 varchar(N) 的 N。
  12. // - **tinyint(1) 用 bool**:GORM bool → tinyint(1)(t_role.login_allowed / t_membership 无)。
  13. // - 列名显式 `gorm:"column:xxx"`,不依赖默认命名。
  14. // - 时间列 CreatedAt/UpdatedAt:GORM 自动管理(GORM v1.31 MySQL 默认 datetime(3),与 DDL 一致)。
  15. //
  16. // 多租户作用域(架构规格 §3.5):
  17. // - Institution/Campus/Membership/GuardianLink 四表均带 institution_id / campus_id 列,
  18. // Repository 强制 scoping(跨校区越权防线);Role 是平台级字典,Repository 不继承 BaseRepo。
  19. package model
  20. import "time"
  21. // InstitutionStatus 机构/校区状态。
  22. const (
  23. StatusActive = "active" // 正常
  24. StatusDisabled = "disabled" // 停用
  25. )
  26. // Institution 机构表 t_institution(多租户根·品牌级)。
  27. //
  28. // D2:主键 InstitutionID int64 自增(作用域根,scope.Scope.InstitutionID 引用此值)。
  29. type Institution struct {
  30. InstitutionID int64 `gorm:"column:institution_id;primaryKey" json:"institutionId"`
  31. InstitutionName string `gorm:"column:institution_name;size:128;not null" json:"institutionName"`
  32. InstitutionCode *string `gorm:"column:institution_code;size:32" json:"institutionCode,omitempty"`
  33. Address *string `gorm:"column:address;size:255" json:"address,omitempty"`
  34. Phone *string `gorm:"column:phone;size:20" json:"phone,omitempty"`
  35. Status string `gorm:"column:status;size:16;not null" json:"status"`
  36. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  37. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  38. }
  39. // TableName 指定表名(GORM 默认 Institution → institutions,与 DDL 单数 t_institution 不符)。
  40. func (Institution) TableName() string { return "t_institution" }
  41. // Campus 校区表 t_campus(隶属机构)。
  42. //
  43. // D2:主键 CampusID int64 自增;InstitutionID int64(→ t_institution,作用域字段)。
  44. type Campus struct {
  45. CampusID int64 `gorm:"column:campus_id;primaryKey" json:"campusId"`
  46. InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"`
  47. CampusName string `gorm:"column:campus_name;size:128;not null" json:"campusName"`
  48. CampusCode *string `gorm:"column:campus_code;size:32" json:"campusCode,omitempty"`
  49. Address *string `gorm:"column:address;size:255" json:"address,omitempty"`
  50. Phone *string `gorm:"column:phone;size:20" json:"phone,omitempty"`
  51. Status string `gorm:"column:status;size:16;not null" json:"status"`
  52. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  53. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  54. }
  55. // TableName 指定表名(GORM 默认 Campus → campuses,与 DDL 单数 t_campus 不符)。
  56. func (Campus) TableName() string { return "t_campus" }
  57. // Role 运营角色(7 类,ADR-007 / 2026-11-05 拍板)。
  58. // role_code 与 Membership.Role 列取值一致(跨包引用时 import 本包常量,避免字符串散落)。
  59. const (
  60. RoleSuperAdmin = "super_admin" // 超级管理员(平台级,跨机构)
  61. RoleAcademic = "academic" // 教务(机构/校区级)
  62. RoleFinance = "finance" // 财务(机构/校区级)
  63. RoleTeacher = "teacher" // 老师(校区级)
  64. RoleStudent = "student" // 学生(业务级)
  65. RoleParent = "parent" // 家长(业务级,按 GuardianLink 绑定)
  66. RoleMarket = "market" // 市场(数据保留、登录禁用,login_allowed=0)
  67. )
  68. // ScopeLevel 角色权限层级(t_role.scope_level)。
  69. const (
  70. ScopeLevelPlatform = "platform" // 平台级(超管,跨机构)
  71. ScopeLevelInstitution = "institution" // 机构/校区级(教务/财务/市场)
  72. ScopeLevelCampus = "campus" // 校区级(老师)
  73. ScopeLevelBusiness = "business" // 业务级(学生/家长,按报名/绑定归属)
  74. )
  75. // RoleModel 角色字典表 t_role(平台级元数据·不带作用域)。
  76. //
  77. // 主键 RoleCode 为 string(字典码,size:32),非自增——GORM primaryKey tag 标注(string 主键不触发自增)。
  78. // 种子数据由 core-model-ddl.sql 内置 INSERT IGNORE;Go 侧只读字典,故无 UpdatedAt 列。
  79. // LoginAllowed 为 bool 对齐 DDL tinyint(1)。
  80. type RoleModel struct {
  81. RoleCode string `gorm:"column:role_code;primaryKey;size:32" json:"roleCode"`
  82. RoleName string `gorm:"column:role_name;size:32;not null" json:"roleName"`
  83. ScopeLevel string `gorm:"column:scope_level;size:16;not null" json:"scopeLevel"`
  84. LoginAllowed bool `gorm:"column:login_allowed;not null" json:"loginAllowed"` // tinyint(1) → bool
  85. Description *string `gorm:"column:description;size:255" json:"description,omitempty"`
  86. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  87. }
  88. // TableName 指定表名(GORM 默认 RoleModel → role_models,与 DDL 单数 t_role 不符;
  89. // 类型名加 Model 后缀避免与常量 Role* 混淆,表名显式锁定 t_role)。
  90. func (RoleModel) TableName() string { return "t_role" }
  91. // MembershipStatus 成员关系状态。
  92. const (
  93. MembershipStatusActive = "active" // 在籍
  94. MembershipStatusInactive = "inactive" // 离开(离职/退学/解绑,left_at 填值)
  95. )
  96. // Membership 成员关系表 t_membership(人 ↔ 机构/校区 + 角色,多对多,多租户核心,ADR-007)。
  97. //
  98. // D3:主键 MembershipID int64 自增;CanonicalUserID string(ULID,size:26,→ t_user)。
  99. // UNIQUE(canonical_user_id, campus_id, role)——campus 隐含 institution。
  100. // campus_id=0 表示平台级(如超管,跨机构)。DDL 无 updated_at 列,故 model 不设 UpdatedAt。
  101. type Membership struct {
  102. MembershipID int64 `gorm:"column:membership_id;primaryKey" json:"membershipId"`
  103. CanonicalUserID string `gorm:"column:canonical_user_id;size:26;not null" json:"canonicalUserId"`
  104. InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"` // 0=平台级
  105. CampusID int64 `gorm:"column:campus_id;not null" json:"campusId"` // 0=平台级/机构级
  106. Role string `gorm:"column:role;size:32;not null" json:"role"`
  107. Status string `gorm:"column:status;size:16;not null" json:"status"`
  108. JoinedAt *time.Time `gorm:"column:joined_at" json:"joinedAt,omitempty"`
  109. LeftAt *time.Time `gorm:"column:left_at" json:"leftAt,omitempty"`
  110. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  111. }
  112. // TableName 指定表名(GORM 默认 Membership → memberships,与 DDL 单数 t_membership 不符)。
  113. func (Membership) TableName() string { return "t_membership" }
  114. // GuardianRelation 家长-学生关系类型(t_guardian_link.relation,可空)。
  115. const (
  116. GuardianRelationFather = "father" // 父
  117. GuardianRelationMother = "mother" // 母
  118. GuardianRelationGuardian = "guardian" // 监护人
  119. GuardianRelationOther = "other" // 其他
  120. )
  121. // GuardianLink 家长-学生绑定表 t_guardian_link(带作用域,多对多)。
  122. //
  123. // D3:主键 LinkID int64 自增;GuardianID / StudentID 均 string(ULID,size:26,→ t_user,D1)。
  124. // 一个学生多家长、一个家长多学生;家长只能看绑定的学生(跨家长越权防线,§3.5)。
  125. type GuardianLink struct {
  126. LinkID int64 `gorm:"column:link_id;primaryKey" json:"linkId"`
  127. GuardianID string `gorm:"column:guardian_id;size:26;not null" json:"guardianId"`
  128. StudentID string `gorm:"column:student_id;size:26;not null" json:"studentId"`
  129. InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"`
  130. CampusID int64 `gorm:"column:campus_id;not null" json:"campusId"`
  131. Relation *string `gorm:"column:relation;size:32" json:"relation,omitempty"`
  132. Status string `gorm:"column:status;size:16;not null" json:"status"`
  133. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  134. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  135. }
  136. // TableName 指定表名(GORM 默认 GuardianLink → guardian_links,与 DDL 单数 t_guardian_link 不符)。
  137. func (GuardianLink) TableName() string { return "t_guardian_link" }
  138. // AllTables 返回本域全部 model 指针切片(供装配层/集成测试合并后传入 infra/db.Migrate)。
  139. //
  140. // 入参:无。
  141. // 出参:[]any——org 域 5 表(Institution/Campus/Membership/Role/GuardianLink)。
  142. // 说明:返回新切片,调用方可安全 append。
  143. func AllTables() []any {
  144. return []any{
  145. &Institution{},
  146. &Campus{},
  147. &Membership{},
  148. &RoleModel{},
  149. &GuardianLink{},
  150. }
  151. }