models.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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. // PermissionStatus 权限点状态。
  139. const (
  140. PermissionStatusActive = "active"
  141. PermissionStatusDisabled = "disabled" // disabled 不进菜单树/权限集(软下线,不物理删)
  142. )
  143. // PermissionType 权限点类型。
  144. const (
  145. PermissionTypeMenu = "menu" // 菜单节点:进菜单树(menuTree 下发),叶子必有 path
  146. PermissionTypeButton = "button" // 按钮节点:不进菜单树,仅供 v-perm 权限码判定
  147. )
  148. // Permission 权限点字典表 t_permission(平台级元数据·不带作用域,RBAC 菜单/按钮展示层,T6.5)。
  149. //
  150. // 主键 Code 为 string(三段式 `域.资源.动作`,如 org.institution.list;对齐 PC 前端契约
  151. // MenuNode.code / permissions[],前端按此做菜单显隐 + 按钮 v-perm 联动)。
  152. // ParentCode 自引用构成菜单树(type=menu 的子树即菜单树;button 挂对应 menu 下,不进树)。
  153. // 超级管理员**不依赖本表**放行(代码层全量放行,T6.5-d requireRole 中间件),
  154. // 本表只服务"非超管角色的可见菜单/按钮"。
  155. type Permission struct {
  156. Code string `gorm:"column:code;primaryKey;size:64" json:"code"`
  157. Type string `gorm:"column:type;size:16;not null" json:"type"`
  158. Name string `gorm:"column:name;size:64;not null" json:"name"`
  159. ParentCode *string `gorm:"column:parent_code;size:64" json:"parentCode,omitempty"`
  160. Path *string `gorm:"column:path;size:255" json:"path,omitempty"`
  161. Icon *string `gorm:"column:icon;size:64" json:"icon,omitempty"`
  162. Sort int32 `gorm:"column:sort;not null" json:"sort"`
  163. Status string `gorm:"column:status;size:16;not null" json:"status"`
  164. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  165. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  166. }
  167. // TableName 指定表名(GORM 默认 Permission → permissions,与 DDL 单数 t_permission 不符)。
  168. func (Permission) TableName() string { return "t_permission" }
  169. // RolePermission 角色-权限点矩阵表 t_role_permission(平台级,D3 主键自增)。
  170. //
  171. // UNIQUE(role_code, permission_code);矩阵由超管端点配置(T6.5-e:删旧插新事务),
  172. // 种子为起步态(core-model-ddl.sql v1.1 内置 INSERT IGNORE)。
  173. type RolePermission struct {
  174. ID int64 `gorm:"column:id;primaryKey" json:"id"`
  175. RoleCode string `gorm:"column:role_code;size:32;not null" json:"roleCode"`
  176. PermissionCode string `gorm:"column:permission_code;size:64;not null" json:"permissionCode"`
  177. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  178. }
  179. // TableName 指定表名(GORM 默认 RolePermission → role_permissions,与 DDL 单数 t_role_permission 不符)。
  180. func (RolePermission) TableName() string { return "t_role_permission" }
  181. // AllTables 返回本域全部 model 指针切片(供装配层/集成测试合并后传入 infra/db.Migrate)。
  182. //
  183. // 入参:无。
  184. // 出参:[]any——org 域 7 表(Institution/Campus/Membership/Role/GuardianLink/Permission/RolePermission)。
  185. // 说明:返回新切片,调用方可安全 append。
  186. func AllTables() []any {
  187. return []any{
  188. &Institution{},
  189. &Campus{},
  190. &Membership{},
  191. &RoleModel{},
  192. &GuardianLink{},
  193. &Permission{},
  194. &RolePermission{},
  195. }
  196. }