CRM项目多角色登录系统、子管理员限额与数据库设计咨询
面向B端CRM的多角色账号系统设计方案(适配Java8/Spring/MySQL技术栈)
一、适配场景的MySQL数据库结构设计
整体采用“租户-账号”两层拆分设计,避免主、子管理员分表带来的扩展难题,核心表结构如下:
- 租户(采购企业)表:作为独立采购主体的根存储,对应单个采购CRM产品的企业
id:bigint 主键自增tenant_code:varchar(64) 全局唯一租户标识,采购完成后自动生成tenant_name:varchar(128) 企业名称,如示例中的“XYZ解决方案”max_sub_admin_limit:int 字段默认值为5,存储单企业允许的子管理员上限,支持后续按套餐档位灵活调整- 通用审计字段:创建时间、更新时间、逻辑删除标记
- 系统账号表:统一存储所有主管理员、子管理员的登录信息
id:bigint 主键自增tenant_id:bigint 关联租户表主键,明确账号所属企业account_email:varchar(128) 登录账号(如示例中的xyz@gmail.com、Santhosh@xyz.com),加全局唯一索引,避免跨企业账号重复password_hash:varchar(256) 存储加密后的密码哈希值,禁止存明文,适配Spring Security的BCrypt加密逻辑role_type:tinyint 角色标识,1=主管理员,2=子管理员is_active:tinyint 账号状态,1=可正常登录,0=禁用- 通用审计字段:创建时间、更新时间、最后登录时间、逻辑删除标记
- 索引优化:建立
idx_tenant_role (tenant_id, role_type, is_active)联合索引,加速子管理员数量的统计查询
二、多角色登录与子管理员数量限制的实现逻辑
1. 统一多角色登录流程
基于Spring Security实现统一认证入口,避免单独搭建两套登录逻辑:
- 登录接口接收用户传入的邮箱+密码,首先根据邮箱查询账号表
- 校验账号状态:若账号为禁用状态、已逻辑删除,直接返回无权限提示
- 校验密码哈希与数据库存储值是否匹配,匹配通过后生成登录态凭证(如JWT),凭证内携带
tenantId、roleType、accountId核心身份信息 - 接口权限匹配规则:
- 主管理员账号:可访问企业配置、子管理员管理类接口
- 子管理员账号:仅开放客户管理、跟进记录等业务操作接口,屏蔽账号管理入口
2. 子管理员数量上限规则的实现
禁止仅在前端做数量校验,需在服务端做全链路控制,防止接口被绕过:
- 主管理员触发新增子管理员接口时,服务层按顺序做三层校验:
- 权限校验:确认当前操作人的角色是主管理员,非主管理员角色直接拒绝新增请求
- 并发防护:通过行级锁锁定当前企业对应的租户配置行,避免多请求并发提交导致数量超阈值
- 数量校验:统计当前企业下所有状态为正常、未删除的子管理员总数,若数值达到租户表配置的
max_sub_admin_limit(默认5),直接抛出业务异常“当前企业子管理员账号数量已达上限,无法新增”
- 核心实现代码示例:
@Transactional(rollbackFor = Exception.class) public void addSubAdmin(Long tenantId, String subAdminEmail, String rawPassword) { // 锁定当前租户配置行,避免并发超量 Tenant currentTenant = tenantMapper.selectByIdForUpdate(tenantId); // 统计当前有效子管理员总数 Integer activeSubAdminCount = accountMapper.countActiveSubAdminByTenantId(tenantId); // 数量阈值校验 if (activeSubAdminCount >= currentTenant.getMaxSubAdminLimit()) { throw new BusinessException("企业子管理员数量已达上限,无法新增"); } // 全局账号唯一性校验 Integer existingAccount = accountMapper.checkAccountExist(subAdminEmail); if (existingAccount > 0) { throw new BusinessException("该邮箱账号已注册,无法重复创建"); } // 保存子管理员账号,密码做加密处理 Account subAccount = new Account(); subAccount.setTenantId(tenantId); subAccount.setAccountEmail(subAdminEmail); subAccount.setPasswordHash(passwordEncoder.encode(rawPassword)); subAccount.setRoleType(2); subAccount.setIsActive(1); accountMapper.insert(subAccount); }
内容的提问来源于stack exchange,提问作者Santhosh Athreya
相关产品推荐
相关产品推荐

