feat(gateway): 多租户地基·增量1 —— 租户身份/成员/上下文(T4.A/SaaS P1)
按 SAAS_DESIGN.md P1 第一刀(真·多租户 + 桌面端为主):先立租户身份,不动查询。 - store.Tenant / TenantMember 表 + AutoMigrate - store: CreateTenant / AddMember(幂等) / DefaultTenantForUser / EnsureDefaultTenant(幂等) / BackfillDefaultTenants / GetTenant / MemberRole - 注册即建单人默认租户(owner);启动回填给存量用户补建(幂等) - middleware.TenantContext(挂 Auth 后)解析当前租户→注入 tenant_id;handler.tenantID(c) 助手 - GET /api/v1/tenants/current 验证端点(租户上下文 + 角色) - live:存量用户(回填 7 租户)/tenants/current 返回默认租户+owner;新注册自动建租户 下一步(增量2):核心表加 tenant_id + 统一 gorm scope 强制隔离 + 存量行回填 + 重写查询。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -511,6 +511,34 @@ func userID(c *gin.Context) string {
|
||||
return "anonymous"
|
||||
}
|
||||
|
||||
// tenantID 取当前请求的租户标识 —— 由 TenantContext 中间件注入(多租户作用域用;未解析返回空)。
|
||||
func tenantID(c *gin.Context) string {
|
||||
if v, ok := c.Get("tenant_id"); ok {
|
||||
if s, _ := v.(string); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TenantCurrent: GET /api/v1/tenants/current —— 当前用户的租户上下文 + 角色(前端 + 联调验证)。
|
||||
func (h *Handler) TenantCurrent(c *gin.Context) {
|
||||
tid := tenantID(c)
|
||||
if tid == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"tenant": nil})
|
||||
return
|
||||
}
|
||||
t, _ := h.db.GetTenant(c.Request.Context(), tid)
|
||||
if t == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"tenant": nil})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"tenant": gin.H{"id": t.ID, "name": t.Name, "slug": t.Slug, "plan": t.Plan, "status": t.Status},
|
||||
"role": h.db.MemberRole(c.Request.Context(), tid, userID(c)),
|
||||
})
|
||||
}
|
||||
|
||||
// sessionID 从请求取会话标识(真实场景应由会话中间件注入)。
|
||||
func sessionID(c *gin.Context) string {
|
||||
if s := c.GetHeader("X-Session-ID"); s != "" {
|
||||
|
||||
Reference in New Issue
Block a user