# Design: 用户和组织模型架构设计 ## Context ### 背景 系统需要支持以下四种用户类型和对应的登录端口: | 用户类型 | 登录端口 | 组织归属 | 角色数量 | |---------|---------|---------|---------| | 平台用户 | Web后台 | 无(平台级) | 可分配多个角色 | | 代理账号 | Web后台 + H5 | 店铺 | 只能分配一种角色 | | 企业账号 | H5 | 企业 | 只能分配一种角色 | | 个人客户 | H5(个人端) | 无 | 无角色无权限 | ### 组织层级关系 ``` 平台(系统) ├── 店铺A(一级代理) │ ├── 店铺B(二级代理,最多7级) │ │ └── 企业X │ └── 企业Y ├── 店铺C(一级代理) │ └── ... └── 企业Z(平台直属企业) ``` ### 约束条件 - 代理层级最多 7 级 - 代理的上下级关系不可变更 - 一个店铺多个账号(账号权限相同) - 一个企业目前只有一个账号 - 个人客户独立表,不参与 RBAC 体系 - 遵循项目的数据库设计原则:禁止外键、禁止 GORM 关联 ## Goals / Non-Goals ### Goals 1. 设计清晰的用户和组织模型,支持四种用户类型 2. 建立店铺层级关系,支持 7 级代理 3. 支持店铺、企业、个人客户的数据归属 4. 为后续的角色权限体系打好基础 5. 为后续的数据权限过滤打好基础 ### Non-Goals 1. 本提案不实现角色权限体系(后续提案) 2. 本提案不实现个人客户的微信登录(后续提案) 3. 本提案不实现数据权限过滤逻辑(已在 004-rbac-data-permission 中定义) 4. 本提案不处理资产绑定(未来功能) ## Decisions ### Decision 1: 账号统一存储 vs 分表存储 **决策**: 平台用户、代理账号、企业账号统一存储在 `tb_account` 表,个人客户独立存储在 `tb_personal_customer` 表。 **理由**: - 平台/代理/企业账号都参与 RBAC 体系,有相似的字段结构 - 个人客户不参与 RBAC,有独特的微信绑定需求 - 统一存储便于账号管理和登录验证 - 通过 `user_type` 字段区分账号类型 ### Decision 2: 代理层级关系的存储位置 **决策**: 代理层级关系存储在 `tb_shop`(店铺表)的 `parent_id` 字段,而非 `tb_account` 的 `parent_id`。 **理由**: - 层级关系是店铺之间的关系,不是个人之间的关系 - 一个店铺有多个账号,账号之间不应该有上下级关系 - 现有 `tb_account.parent_id` 字段将重新定义用途或移除 **变更**: - `tb_account.parent_id` 字段移除或废弃 - 新增 `tb_shop.parent_id` 表示店铺的上级店铺 - 递归查询下级改为查询店铺的下级,而非账号的下级 ### Decision 3: 企业的归属关系 **决策**: 企业通过 `owner_shop_id` 字段表示归属于哪个店铺,`NULL` 表示平台直属。 **理由**: - 企业可以归属于任意级别的代理(店铺) - 企业也可以直接归属于平台 - 上级代理能看到下级代理的企业数据 ### Decision 4: 账号与组织的关联方式 **决策**: 账号通过 `shop_id` 或 `enterprise_id` 字段关联到组织。 **实现**: - 平台用户:`shop_id = NULL`, `enterprise_id = NULL` - 代理账号:`shop_id = 店铺ID`, `enterprise_id = NULL` - 企业账号:`shop_id = NULL`, `enterprise_id = 企业ID` ### Decision 5: 数据权限过滤的调整 **决策**: 数据权限过滤基于 `shop_id`(店铺归属)而非 `owner_id`(账号归属)。 **理由**: - 同一店铺的所有账号应该能看到店铺的所有数据 - 上级店铺应该能看到下级店铺的数据 - `owner_id` 字段保留用于记录数据的创建者(审计用途) **变更**: - 递归查询改为查询店铺的下级店铺 ID 列表 - 数据过滤条件改为 `WHERE shop_id IN (当前店铺及下级店铺)` - 平台用户(`user_type = 1` 或 `user_type = 2`)跳过过滤 ## Data Models ### Shop(店铺) ```go type Shop struct { gorm.Model BaseModel `gorm:"embedded"` ShopName string `gorm:"not null;size:100"` // 店铺名称 ShopCode string `gorm:"uniqueIndex;size:50"` // 店铺编号 ParentID *uint `gorm:"index"` // 上级店铺ID(NULL表示一级代理) Level int `gorm:"not null;default:1"` // 层级(1-7) ContactName string `gorm:"size:50"` // 联系人姓名 ContactPhone string `gorm:"size:20"` // 联系人电话 Province string `gorm:"size:50"` // 省份 City string `gorm:"size:50"` // 城市 District string `gorm:"size:50"` // 区县 Address string `gorm:"size:255"` // 详细地址 Status int `gorm:"not null;default:1"` // 状态 0=禁用 1=启用 } ``` ### Enterprise(企业) ```go type Enterprise struct { gorm.Model BaseModel `gorm:"embedded"` EnterpriseName string `gorm:"not null;size:100"` // 企业名称 EnterpriseCode string `gorm:"uniqueIndex;size:50"` // 企业编号 OwnerShopID *uint `gorm:"index"` // 归属店铺ID(NULL表示平台直属) LegalPerson string `gorm:"size:50"` // 法人代表 ContactName string `gorm:"size:50"` // 联系人姓名 ContactPhone string `gorm:"size:20"` // 联系人电话 BusinessLicense string `gorm:"size:100"` // 营业执照号 Province string `gorm:"size:50"` // 省份 City string `gorm:"size:50"` // 城市 District string `gorm:"size:50"` // 区县 Address string `gorm:"size:255"` // 详细地址 Status int `gorm:"not null;default:1"` // 状态 0=禁用 1=启用 } ``` ### PersonalCustomer(个人客户) ```go type PersonalCustomer struct { gorm.Model Phone string `gorm:"uniqueIndex;size:20"` // 手机号(唯一标识) Nickname string `gorm:"size:50"` // 昵称 AvatarURL string `gorm:"size:255"` // 头像URL WxOpenID string `gorm:"index;size:100"` // 微信OpenID WxUnionID string `gorm:"index;size:100"` // 微信UnionID Status int `gorm:"not null;default:1"` // 状态 0=禁用 1=启用 } ``` ### Account(账号)- 修改 ```go type Account struct { gorm.Model BaseModel `gorm:"embedded"` Username string `gorm:"uniqueIndex;size:50"` // 用户名 Phone string `gorm:"uniqueIndex;size:20"` // 手机号 Password string `gorm:"not null;size:255" json:"-"` // 密码 UserType int `gorm:"not null;index"` // 用户类型 1=超级管理员 2=平台用户 3=代理账号 4=企业账号 ShopID *uint `gorm:"index"` // 店铺ID(代理账号必填) EnterpriseID *uint `gorm:"index"` // 企业ID(企业账号必填) Status int `gorm:"not null;default:1"` // 状态 0=禁用 1=启用 // 移除 ParentID 字段,层级关系由 Shop 表维护 } ``` ## Risks / Trade-offs ### Risk 1: 现有数据迁移 - **风险**: 现有 `tb_account.parent_id` 字段被移除,可能影响现有数据 - **缓解**: 当前系统是空架子,无实际数据需要迁移 ### Risk 2: 数据权限过滤逻辑变更 - **风险**: 从 `owner_id` 过滤改为 `shop_id` 过滤,需要调整现有代码 - **缓解**: 现有的数据权限过滤尚未完全实现,可以直接按新设计实现 ### Risk 3: 店铺层级查询性能 - **风险**: 7 级店铺层级的递归查询可能影响性能 - **缓解**: 继续使用 Redis 缓存店铺的下级 ID 列表,30 分钟过期 ## Migration Plan 1. 创建新表:`tb_shop`、`tb_enterprise`、`tb_personal_customer` 2. 修改 `tb_account` 表结构: - 添加 `enterprise_id` 字段 - 移除 `parent_id` 字段(如果有数据则先迁移) 3. 更新 GORM 模型定义 4. 更新 Store 层实现 5. 更新常量定义 ## Open Questions 1. ~~店铺层级关系是否需要记录完整路径(如 `/1/2/3/`)以优化查询?~~ - 暂不需要,使用递归查询 + Redis 缓存 2. ~~企业账号未来扩展为多账号时,是否需要区分主账号和子账号?~~ - 未来再设计