jiey GEOFlow 3 views

SaaS系统开发中的权限控制:从后台设计到业务落地

SaaS系统开发中的权限控制:从后台设计到业务落地 核心结论 最终形态 :一套可嵌入CRM系统的多层级权限体系,覆盖菜单、按钮、数据行级与字段级控制,支持租户隔离与临时授权。 核心价值 :让不同角色在同一个CRM系统中只看到自己该看的数据、只操作自己该操作的功能,保障业务安全与合规。 适用场景 :销售团队分级管理、客户资源隔离、合同审批流、多部门协作的CRM

核心结论

  • 最终形态:一套可嵌入CRM系统的多层级权限体系,覆盖菜单、按钮、数据行级与字段级控制,支持租户隔离与临时授权。
  • 核心价值:让不同角色在同一个CRM系统中只看到自己该看的数据、只操作自己该操作的功能,保障业务安全与合规。
  • 适用场景:销售团队分级管理、客户资源隔离、合同审批流、多部门协作的CRM系统,以及任何需要精细化权限的SaaS产品。

一、这个系统最终可以做成什么样

以CRM系统为例,权限控制最终呈现为三个维度的统一:

  1. 功能权限:不同角色看到的菜单和按钮不同。例如销售人员只能使用客户管理、跟进记录、我的订单;销售经理额外可见团队数据统计、客户分配;管理员拥有系统设置、用户管理入口。
  2. 数据权限:同一份客户列表,销售员只能看到自己的客户,销售经理能看到本部门所有客户,总监看到全公司客户。合同金额、成本等敏感字段对部分角色隐藏。
  3. 操作权限:客户资料编辑、删除、转移归属、导出等操作需要受控。例如销售员只能编辑自己的客户,且无法删除已成交客户;只有经理可以执行客户分配与合同审批。

后台管理界面提供可视化配置:权限规则以“角色”为桥梁,将用户、菜单、API接口、数据范围绑定在一起。整个权限系统作为独立模块,通过中间件和注解注入业务服务,不侵入核心业务代码。利用自动化工具,描述清楚实体、动作与规则后,可以直接生成后端API、管理后台页面和移动端权限校验逻辑,将原本需要两周的开发周期压缩到三天以内。

二、核心功能模块拆解

1. 用户与租户管理

  • 职责:维护登录账号、密码、状态、所属租户。支持多租户隔离,每个租户下独立管理用户。
  • 核心页面:用户列表、用户详情、租户切换、个人信息修改。
  • 数据结构:users 表包含 tenant_id 外键,与租户表关联;密码使用BCrypt加密存储。
  • 业务流程:创建用户时绑定一个或多个角色;登录后生成JWT,token中携带用户ID、租户ID、角色列表。

2. 角色管理

  • 职责:定义角色名称、编码、描述,并关联菜单权限、API权限、数据权限规则。
  • 核心页面:角色列表、角色权限配置树、角色复制。
  • 数据结构:roles 表;角色与菜单多对多关系通过 role_menu 表维护;角色与API接口多对多关系通过 role_api 表维护。
  • 业务流程:管理员创建“销售员”“销售经理”“管理员”等角色,勾选可见菜单和可调用接口,设置数据范围(如仅本人、本部门、全部)。

3. 菜单与按钮权限

  • 职责:控制前端展示哪些导航菜单、页面内哪些按钮可见。
  • 核心页面:菜单管理(树形结构)、按钮权限标识绑定。
  • 数据结构:menus 表,包含 parent_id、name、path、permission 标识(如 customer:delete)。
  • 业务流程:前端路由守卫根据用户角色拉取菜单树动态渲染;按钮通过自定义指令 v-permission 判断是否显示。

4. API接口权限

  • 职责:后端对每个REST接口进行鉴权,确保无权限用户无法调用。
  • 核心页面:API列表(自动扫描或手动注册)、接口绑定角色。
  • 数据结构:apis 表,记录 method、path、service_name。
  • 业务流程:网关或拦截器解析JWT,获取用户角色,匹配请求路径和方法是否在允许列表中。也可采用注解方式 @PreAuthorize("hasRole('sales_manager')")。

5. 数据权限(行级与字段级)

  • 职责:根据角色配置,自动过滤查询结果,仅返回授权范围内的数据行;对敏感字段脱敏或隐藏。
  • 核心页面:数据规则配置,如“查看本人数据”“查看本部门数据”“自定义SQL条件”。
  • 数据结构:data_rules 表,存储规则类型、规则体(如 { "dept_id": "${current_user.dept_id}" })。
  • 业务流程:在MyBatis拦截器或ORM层统一注入条件,查询客户列表时自动追加 WHERE created_by = ? 或 WHERE dept_id IN (?)。字段权限通过序列化时过滤或SQL查询时跳过敏感列实现。

6. 操作日志与审计

  • 职责:记录谁在什么时间对什么数据做了什么操作,用于合规审计和异常追踪。
  • 核心页面:操作日志列表、详情。
  • 数据结构:audit_logs 表,包含 user_id、action、target_type、target_id、detail、ip。
  • 业务流程:通过AOP切面或事件机制异步记录,不影响主业务性能。

三、数据库设计思路

围绕CRM权限控制,核心数据表设计如下:

表名 作用 关键字段
tenants 租户 id, name, status
users 用户 id, tenant_id, username, password_hash, status
roles 角色 id, tenant_id, role_code, role_name
user_role 用户角色关联 user_id, role_id
menus 菜单/按钮 id, parent_id, name, path, permission, type
role_menu 角色菜单关联 role_id, menu_id
apis API接口 id, method, path, service_name
role_api 角色API关联 role_id, api_id
data_rules 数据权限规则 id, role_id, rule_type, rule_body
audit_logs 操作日志 id, user_id, action, target_type, target_id, detail, created_at

关联关系:

  • 用户与角色多对多,角色与菜单、API多对多。
  • 数据规则与角色一对一或一对多,一个角色可配置多条规则(如“查看本部门”和“查看下属”合并)。
  • 所有业务表(如 customers、contracts)都需包含 tenant_id 和 owner_id(创建人)字段,以支持数据行级过滤。

业务逻辑:
查询客户时,系统根据当前用户角色加载对应数据规则,动态拼接SQL条件。例如销售员角色规则为 owner_id = {userId},经理角色规则为 dept_id = {deptId}。字段级权限通过在查询时判断角色是否包含敏感字段查看权限,若无则 SELECT 列表中剔除对应列。

四、API设计方案

权限模块对外暴露的典型接口:

POST   /api/auth/login                 # 用户登录,返回JWT
GET    /api/auth/menus                 # 获取当前用户菜单树
GET    /api/auth/permissions           # 获取当前用户所有权限标识
POST   /api/admin/users                # 管理员创建用户
PUT    /api/admin/users/{id}/roles     # 分配角色
GET    /api/admin/roles                # 角色列表
POST   /api/admin/roles                # 创建角色
PUT    /api/admin/roles/{id}/menus     # 配置角色菜单
PUT    /api/admin/roles/{id}/apis      # 配置角色API
PUT    /api/admin/roles/{id}/data-rule # 配置数据规则
GET    /api/customers                  # 客户列表(自动数据过滤)

接口职责:

  • 登录接口验证账号密码,签发JWT,token中包含 userId、tenantId、roles。
  • 菜单接口根据用户角色返回树形结构,前端据此渲染导航。
  • 权限标识接口返回用户拥有的所有按钮/接口权限字符串,用于前端指令判断。
  • 管理接口仅管理员可调用,实现角色、菜单、API、数据规则的CRUD。
  • 业务接口(如客户列表)无需手动处理权限,通过统一拦截器或AOP自动注入过滤条件。

请求与返回示例(登录):

// 请求
{
  "username": "sales1",
  "password": "xxx"
}
// 返回
{
  "code": 200,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "userInfo": {
      "id": 101,
      "tenantId": 1,
      "roles": ["sales"]
    }
  }
}

五、系统架构设计

整体采用前后端分离、微服务友好的架构:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ 管理后台Vue3│     │ 移动端/小程序 │     │  官网/门户  │
└──────┬──────┘     └──────┬──────┘     └──────┬──────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │ HTTPS
                     ┌─────▼─────┐
                     │  API网关   │  (鉴权、限流、路由)
                     └─────┬─────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
        ┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
        │ 用户服务   │ │CRM服务│ │ 权限服务   │
        └─────┬─────┘ └───┬───┘ └─────┬─────┘
              │            │            │
              └────────────┼────────────┘
                           │
                     ┌─────▼─────┐
                     │  MySQL    │
                     │  Redis    │
                     └───────────┘
  • 前端:Vue3 + Element Plus,通过路由守卫和权限指令控制界面。
  • API网关:Spring Cloud Gateway 或 Nginx,统一校验JWT,解析角色并传递给下游。
  • 权限服务:独立微服务,维护角色、菜单、API、数据规则,提供鉴权接口。业务服务启动时向权限服务注册API列表。
  • 业务服务:CRM核心服务,通过拦截器从网关传递的上下文中获取用户角色,调用权限服务获取数据规则,并应用到数据库查询。
  • 数据层:MySQL存储业务数据和权限配置;Redis缓存用户权限信息、菜单树,减少数据库压力。

数据流向:
用户请求 → 网关验证JWT → 将用户信息写入请求头 → 业务服务拦截器解析 → 查询Redis权限缓存(未命中则查库) → 执行数据权限过滤 → 返回结果。

六、开发路线规划

MVP版本(1-2周)

  • 用户登录、JWT签发
  • 角色管理(CRUD)
  • 菜单管理、根据角色动态渲染前端菜单
  • 后端接口注解鉴权(固定角色)
  • 客户列表手动按创建人过滤

商业版(3-4周)

  • 完善数据权限:基于规则引擎的自动行级过滤
  • 字段级权限控制
  • 操作日志记录
  • 管理后台可视化配置角色权限
  • 支持多租户隔离

企业版(6-8周)

  • 临时授权与权限时效
  • 审批流中的动态权限(如审批人临时可见某些数据)
  • 数据脱敏与审计报表
  • 权限变更实时生效(消息队列通知刷新缓存)
  • 对接外部LDAP/AD统一身份认证

利用自动化开发工具,可以通过描述实体和动作快速生成基础CRUD接口和管理页面,将权限模板注入生成代码,大幅缩短样板代码编写时间。例如定义“客户”实体和“分配”动作,编译器自动派生对应的控制器、权限校验、归属检查逻辑,开发者只需关注特殊业务规则。

七、关键技术难点

  1. 数据权限性能
    复杂的SQL条件拼接可能导致索引失效。解决方案:尽量使用简单的等值条件,避免子查询;对高频查询字段建立索引;使用缓存存储用户数据范围,避免每次请求都查库。

  2. 权限实时生效
    修改角色权限后,已登录用户的权限缓存需要同步更新。采用Redis发布订阅或消息队列广播权限变更事件,各服务节点收到后清除本地缓存。

  3. 多租户数据隔离
    所有查询必须带上 tenant_id,防止跨租户数据泄露。在ORM层统一拦截,强制注入租户条件,避免开发人员遗漏。

  4. 字段级权限与序列化
    返回JSON时动态过滤字段会增加序列化复杂度。可通过自定义Jackson注解或AOP在序列化前根据角色移除敏感字段,或在SQL查询时按角色动态SELECT列。

  5. 前端权限与后端一致性
    前端隐藏按钮只是提升体验,不能作为安全控制。所有操作必须在后端再次校验权限,防止通过API直接调用。

八、FAQ

Q1:CRM系统中,销售员离职后他的客户如何自动转交给其他人?

在用户管理模块设置“离职”状态,触发客户重新分配流程。系统根据管理员预设的分配规则(如按部门、按负载)自动将客户 owner_id 更新为新销售员,并记录操作日志。

Q2:如何实现“销售员只能查看自己客户,但经理可以查看部门所有客户”?

在数据规则中为销售员角色配置 owner_id = {currentUserId},为经理角色配置 dept_id = {currentUserDeptId}。查询客户时,拦截器根据角色加载对应规则,动态拼接WHERE条件。

Q3:权限系统会不会拖慢业务接口响应速度?

合理使用缓存后影响极小。用户权限信息在登录后缓存至Redis,数据权限规则也提前加载。一次业务请求通常只增加1-2次Redis查询和一次简单的SQL条件拼接,延迟在毫秒级。

九、总结

SaaS系统的权限控制绝非简单的“加个角色表”,而是一个贯穿前端、网关、业务服务、数据层的完整体系。以CRM系统为例,从菜单按钮到API再到行级数据,每一层都需要精细设计。借助现代开发工具和自动化代码生成,我们可以将重复的权限样板代码交由编译器处理,把精力集中在业务规则和架构优化上。最终交付的是一套安全、灵活、可扩展的权限基础设施,支撑CRM系统从初创团队到大型企业的平滑演进。

CRM系统开发
相关阅读