服务与架构
6 个 Spring Boot 3 / Java 17 服务:一个网关、一个用户中心、四个业务域。 Spring Cloud Gateway 统一入口 + Nacos 注册与配置 + MySQL 8(6 个库)+ Redis 7。
服务一览
| 服务 | 路由前缀 | 职责 | 库 |
|---|---|---|---|
gateway | /api/**(公网 8080) |
路由转发、令牌校验、身份与租户头注入、限流;不碰数据库 | — |
user-center | /api/auth/**、/api/users/**、/api/admin/** |
注册登录、用户与租户、权限体系、审批、团队与组织、运营看板 | user_center |
crm | /api/crm/** |
客户、联系人、商机、跟进记录;工作台与团队看板 | crm |
tms | /api/tms/** |
运输订单、车辆、司机;成本统计与派车推荐 | tms |
wms | /api/wms/** |
仓库、库存、出入库登记 | wms |
oms | /api/oms/** |
款号商品、多渠道订单(含明细行)、销售统计 | oms |
业务服务的统一约定
- 权限与租户在服务内做:
require(roles, permission)校验权限点;TenantFilter → TenantContext把当前租户塞进每一条 SQL 的tenant_id(行级隔离)。 - 错误体统一
{code, message, traceId},缺权限点返回 403 并写明「缺少权限点: xxx」。 - 团队负责人继承:有效权限 = 自身角色权限 ∪ 所带队子树在职成员权限并集(
PermissionService递归求并)。
令牌
| 令牌 | 形态 | 有效期 | 存储 |
|---|---|---|---|
| Access Token | JWT(jti/sub/roles/tid) |
15 分钟 | 无状态,网关本地验签 |
| Refresh Token | 随机串 | 15 天 | Redis,用后即焚(rotate 先删再签) |
登出 / 改密把 jti 写进吊销黑名单,TTL = 令牌剩余寿命,过期自动清理。
权限模型
permissions(权限点)→ role_permissions(角色)→ user_roles(账号)
三层全是数据,在管理台的授权矩阵里勾选即生效。
ADMIN 在代码里硬兜底为全部权限点,配置页把管理员勾光也锁不死自己。
派生权限(谁都没在矩阵里勾过)
- 花名审批:提交申请要
nickname:request,批准/驳回要nickname:review。 直接改名的PUT /api/users/me/nickname永久返回 403nickname_approval_required—— 保留接口只为让还缓存着旧 bundle 的浏览器拿到一句能操作的提示。 - 团队负责人:权限沿
teams.parent_id的整棵子树并集继承。 递归只在读时发生,写时由TeamService挡住四条不变量(负责人必须是成员、 同队仅一个负责人、只统计在职、父子不成环)。 team:view:只在「该用户是某个团队负责人」时出现在permissionCodes,且刻意不进permissions表—— 一旦入表就会出现在授权矩阵里被勾给 USER,于是人人多个空入口。 它管「能不能进」,进去之后能「看见谁」由GET /api/users/me/teams单独把关。
多租户
tenants 字典(默认 alibaba),
users.tenant_id 进 JWT 的 tid;业务表统一
tenant_id NOT NULL DEFAULT 'alibaba',
并按「顶层列表按租户扫 + 按 id 倒序分页」的查询模式补 idx_tenant(tenant_id, id)。
已知边界
team_members.is_lead 目前是单层标记,未做成父子链;
CRM 客户/商机的「领用限制」只有设计稿(workbench-antd/docs/crm-visibility-design.md),
尚未落代码。