Mole这个示例工程,说白了就是一套“用户中心”的落地样板间。我在企业里做内部系统时,最头疼的就是每个系统都有一套自己的账号体系,离职了老系统还在飘着,新员工入职要挨个系统开账号,光想想就头大。Mole要解决的就是这个通病,把用户、权限、认证、审计这些公共能力抽出来做成底座,业务系统只专注自己的业务。这篇内容我基于常见企业实践,讲讲这个示例工程怎么把底座和业务衔接起来,涉及多仓、多组织这类典型场景时,又会暴露哪些坑。
1. 项目背景与示例工程的整体定位
1.1 为什么企业需要一个用户中心底座
业务系统多了以后,没有统一用户中心的日子你是体会不到的。一个最简单的场景:员工入职,HR系统开了账号,但OA、项目管理系统、BI报表平台、知识库,全是独立的账号,IT得挨个去创建,光第一批账号开通就得折腾大半天。如果中途还要调部门、转岗,各系统的权限要手动同步一遍,漏掉一个,要么是权限收不回来,要么是数据看不了,都是事。
Mole用户中心就是要把账号、组织、角色、权限、单点登录这些“公用模块”从各个业务系统里拆出来,沉淀成一个独立底座。业务系统只需要对接用户中心,注册应用,接入认证,剩下的用户管理、权限校验、审计日志,统统交给底座处理。示例工程的意义就在于,它提供了一个标准的“接法”:业务系统该怎么对接、user-center该提供哪些接口、数据模型怎么设计,都给你一个可以跑的样板,而不是一套PPT。
1.2 示例工程与业务系统的边界怎么划
对接Mole之前,先把边界划清楚,这是我在实际项目里强调最多的一件事。用户中心管什么?管“你是谁”“你能进哪个系统”“你能干什么”。业务系统管什么?管“业务单据怎么流转”“业务规则怎么执行”“业务数据怎么存储”。听起来很清晰,但实际做起来很容易越界。
拿这个示例工程里的场景来说,它模拟的是一个“多组织多仓业务系统”。用户中心负责定义用户、部门、角色的顶层结构,业务系统负责仓内的收货、上架、库存调整这些业务动作。但“这个用户能不能操作华东仓的波次计划”,这个问题是横跨两边的,用户中心管“能进波次计划这个菜单”,业务系统管“数据范围限制在华东仓”。所以我在实际落地时,通常建议权限模型拆成两块:功能权限(菜单/按钮)归用户中心管,数据权限(哪几个仓、哪几个组织)在业务系统内部用用户中心的组织上下文来过滤。这个切入点不做好,后面越做越乱。
1.3 示例工程适合谁来看
不管你是后端开发、架构师,还是企业内部的IT负责人,这个示例工程都值得花点时间捋一遍。后端开发可以直接把它当成一个接用户中心的参考实现,接口怎么调、鉴权怎么接、用户上下文怎么获取都有现成代码。架构师更需要关注的是它的数据模型设计和API边界划分,这决定了未来你企业里几十套系统对接时的复杂度。IT负责人如果不懂技术细节,重点看它能解决什么业务痛点、上线时业务系统要改多少地方就行。
我见过不少团队自己从零写用户体系,结果光权限模型就改了三版,越改越复杂,最后整个系统都捆绑在登录模块上动弹不得。Mole的价值就是先给你一个经过验证的模型,省掉自主探索的时间。
2. 用户中心的核心模块拆解与设计逻辑
2.1 账号体系:主账号与应用账号需要分离
真实企业里,一个员工会在多个业务系统里有身份,而且不同系统里的用户ID可能还不一致。示例工程里把账号体系设计成两个层面:主账号(全局唯一)和应用账号(系统内唯一)。主账号就是员工在企业内的唯一身份,绑定手机号、邮箱、姓名这些基本属性。应用账号则是主账号在每个业务系统里的“分身”。
为什么非要拆两层?直接用一个用户ID不行吗?说个实际的例子:你有一个供应商的外部人员,他在供应商协同系统里是“供应商操作员”,在内部OA里是“外部访客”,如果只用一个身份,他的权限就得两套合并,安全上很难控制。拆开后,每个应用账号可以独立分配该应用内的角色,主账号统一管控密码策略和启用状态。离职时,直接锁主账号,所有应用账号同步失效。这个模型看起来简单,但能解决非常多真实管理问题。
2.2 组织模型:部门树、职务、职级不能全揉在一起
组织模型是用户中心最容易做烂的地方。很多系统把部门、岗位、职级全塞到一张表里,最后维护成本极高。示例工程里把组织维度拆成了三个独立概念:
- 部门树:解决汇报关系和数据归属,比如“供应链中心-华东仓储部”。
- 岗位:解决职责分工,比如“仓库主管”“上架员”。
- 职级:解决职级序列,比如P6/M2,和技术权限无关。
为什么要分开?我给你讲个真实踩坑经历。早期我们系统里把“主管”这个岗位做成了角色,结果公司组织调整,华东仓储部的主管换了人,但新主管还没到岗,老主管的权限又不能立刻回收,业务就出现了真空期。如果把“岗位”和“人”的绑定关系由组织架构模块负责,主管离职自动解除绑定,再配合备用审批人机制,就不会出现这种情况。
2.3 RBAC权限模型:解决角色爆炸要靠权限点粒度设计
用户中心一般都会用RBAC模型,也就是用户-角色-权限三层关系。但RBAC不是万能的,你授权给“仓库主管”的角色,这个角色到底能干什么?示例工程里的做法是:权限点划分到“按钮级”,比如“查看库存”“导出库存报表”“审核盘点差异”。
这里有一个非常实用的经验:权限点命名一定要用“资源+动作”的格式,比如inventory:query、inventory:export、inventory:audit。我见过用canEditStock这种动词开头的,后期加权限点的时候能把你纠结死。用资源开头,后面扩展资源也好排序,代码里做权限判断也好写。
角色建议分两层:通用角色和业务角色。通用角色比如“系统管理员”“审计员”,跨系统复用;业务角色比如“华东仓收货员”,只在特定应用内有效。这两层分开,管理员在给业务角色配权限时才不会一不小心把审计权限也授出去。
2.4 认证机制与令牌生命周期管理
示例工程里的认证,推荐走标准OAuth2授权码流程。业务系统引导用户跳转到用户中心登录页,登录成功后换token,再带着token访问业务系统。JWT用不用?我用下来建议是accessToken用JWT、refreshToken用不透明字符串,理由下面讲。
JWT可以无状态校验,业务系统拿到token后本地解析就能拿到用户ID和权限,不用每次请求都去用户中心查一次数据库,性能好。但JWT有一个致命的缺点:没法主动失效。员工离职后,他的token只要没过期,理论上还能继续访问业务系统。所以refreshToken必须做成有状态、可以吊销的,它的有效期更长,专门存在用户中心redis里,调注销接口时直接删掉。accessToken有效期设短一些,比如30分钟,就算泄露,影响窗口也小。
注意:这个项目里有个我强烈推荐的细节,accessToken里只放用户主账号ID和会话ID,不要放一堆权限和用户信息进去。权限是高频变化的,你前脚把权限点加到token里,后脚管理员改了角色,token里的旧权限得等到刷新才更新,安全上容易出问题。
3. 示例工程的实操搭建与核心实现
3.1 目录结构与关键模块初始化
拿到示例工程,先把目录结构搞清楚。我的建议是从下往上读,先看common再看model,最后看controller,这样思路最顺。工程目录一般长这样:
mole-sample-system ├── mole-user-center-client // 用户中心SDK客户端 ├── mole-business-module // 业务模块(示例业务) │ ├── controller // 接口层 │ ├── service // 业务逻辑层 │ ├── repository // 数据访问层 │ └── model // 实体/DTO/VO ├── mole-common // 公共工具、常量、异常 └── config // 应用配置业务模块和用户中心client分开,是防止业务代码直接和用户中心内部接口耦合。业务系统只需要依赖client,内部怎么实现不管,后续用户中心升级接口,业务系统改个client依赖就完事了。
3.2 对接用户中心:SDK接入与单点登录配置
SDK接入一般分两步:配置认证参数、接入用户上下文解析。配置参数主要是这些:
mole: user-center: base-url: http://user-center.mole.internal client-id: mole-sample-system client-secret: xxxxxx redirect-uri: https://sample.mole.com/callback注意几个坑:redirect-uri必须和用户中心后台登记的回调地址完全一致,多一个斜杠都不行,报错的时候第一优先级检查这里。client-secret属于敏感信息,不要写进代码仓库,建议配置中心管理。
用户上下文解析这一步,示例工程里一般在拦截器中实现。用户带着token访问业务系统,拦截器先解析token,拿到用户ID,然后再从Redis或本地缓存拿用户的组织、角色、权限明细,封装成CurrentUser对象,放到ThreadLocal里供后续业务逻辑使用。我在实际项目里,会在过滤器里做一次全局的token格式校验,如果Authorization头不存在或格式不对,直接返回401,别等到controller里再判断,避免业务代码里到处写校验逻辑。
3.3 数据权限落地:多仓场景下如何用组织上下文过滤
很多示例工程往往只做功能权限,但真实业务系统真正难的是数据权限。这个示例工程特别好的点在于,它演示了多仓业务场景下如何用用户中心返回的组织上下文做数据隔离。
用户的组织上下文从用户中心接口拿到后,大概是这样的结构:
{ "userId": "u-1024", "tenantId": "t-81", "defaultWarehouse": "WH-EAST-01", "authorizedWarehouses": [ "WH-EAST-01", "WH-EAST-02", "WH-NORTH-01" ] }业务系统查库存数据时,强制拼上仓库过滤条件,而不是让前端传仓库参数。
List<InventoryBO> inventoryList = inventoryRepository.selectByWarehouses( currentUser.getAuthorizedWarehouses() );这里有个必须警惕的细节:数据权限过滤必须在SQL层做,不能只靠前端隐藏。反例是很多系统在前端根据权限隐藏仓库选项,然后后端接口只接收仓库ID,恶意用户直接构造请求传其他仓库ID,数据就泄露出去了。凡是涉及数据权限控制,必须“后端强制过滤”,前端只是提供更友好的交互。
3.4 多租户与多组织的处理
示例工程里如果涉及多组织,一般会有一个tenant_id字段贯穿所有业务表。实现上可以在MyBatis-Plus的拦截器里自动填充,也可以在Mapper层手动拼接条件。
但注意,多租户不能一刀切做一个全局拦截器就完事。有些表你想让租户间共享,比如区域字典表、公共配置表,拦截器一律自动拼tenant_id反而麻烦。所以在实现上,我一般建一个注解@IgnoreTenant标注在白名单表上,让拦截器跳过。这个细节虽然不起眼,但做过多租户的人都会懂这是个多重要的处理。
3.5 示例工程的部署配置与环境要求
示例工程要跑起来,依赖三样东西:MySQL、Redis、用户中心实例。Mysql主要存业务表和用户中心SDK缓存表,Redis存会话token和验证码。
部署时的配置建议:
| 配置项 | 建议值 | 说明 |
|---|---|---|
spring.redis.timeout | 3000ms | 超时设短点,宁可让接口报错也不能无限等 |
mole.user-center.token-expire | 30m | accessToken有效期,示例工程建议值 |
mole.user-center.refresh-expire | 7d | refreshToken有效期 |
server.servlet.session.timeout | 30m | 会话超时兜底配置 |
| 数据库连接池 | maximum-pool-size: 20 | 控制连接数,防止压垮DB |
全套工程跑起来的内存占用一般控制在1GB内,单体环境完全够用。
4. 多仓业务接入用户中心的实战要点
4.1 多仓业务的数据模型与仓库业务归属设计
多仓业务系统接入用户中心,除了标准RBAC权限外,仓库本身也有自己的业务归属。仓库在组织架构里挂到哪个部门下、仓库管理员归属哪个岗位、各地区仓的业代归属哪个销售大区,这些归属关系,我还是建议直接复用用户中心的部门树,而不是业务系统单独建一张仓库人员关系表。否则后续“人员异动调仓”时,两边数据同步的一致性很难保障。
WMS/LMS这类系统现在也讲究多国多仓部署,这其实是和用户中心联动性很强的场景。海外仓的库内作业人员、报关人员、第三方仓储人员,都需要以外部成员的形式在主账号体系里管理,Mole这种主账号与应用账号分离的模型,天然适合这种场景。海外仓系统选型时,如果底层没有一个稳定的账号权限底座,多国合规、人员隔离、操作留痕都会成为很大的隐患。
4.2 多组织、多仓下的仓库级权限控制最佳实践
仓库级权限控制在WMS系统里是业务刚需。华东仓的仓管员,不应该看得到华南仓的库存,更不能操作华南仓的波次。这个就是典型的数据权限,不是功能权限。
实现时,我倾向于在业务系统内建一张warehouse_permission表,字段包括:user_id、warehouse_id、permission_level。这张表的数据不自己维护,哪来的?从用户中心拿到用户所属组织和岗位后,通过匹配岗位对应的仓库归属范围自动算出来。有人可能会问,为什么不在用户中心里直接维护?因为仓库是业务系统特有的实体,用户中心不应该成了一个业务模型的超级大杂烩。用户中心给的是“组织关系”,业务系统翻译成“仓库权限范围”,职责清晰,谁也替代不了谁。
4.3 用户中心与业务系统协作的接口设计模式
两者的接口协作模式,我总结为三类:
- 认证类:登录、注销、校验token、刷新token。这类接口频率高,要求低延迟,一般走网关加上限流。
- 查询类:获取用户信息、获取组织架构、获取角色权限。这类接口适合加缓存,比如本地缓存5分钟,或Redis缓存15分钟。组织架构变了,最多延迟15分钟生效,业务可以接受。
- 事件类:用户离职、角色变更、组织调整,用户中心通过消息队列发事件,业务系统监听后更新自己的本地读模型。
实战经验:不要频繁调用用户中心的用户详情接口。业务系统在本地务数据库里冗余一份
sys_user_snapshot表,用户在用户中心变了,通过事件更新快照。查询性能提升一个数量级,还避免了对用户中心接口的冲击。
4.4 WMS多仓系统选型与账号体系的联动
回到前面提的多国多仓海外仓WMS选型,有些团队把账号权限放在最后考虑,觉得“后面接一个统一登录就行了”,这是最容易翻车的地方。我测评4款主流WMS系统时,专门做了一个对比表格,判断维度里数据权限和组织模型这两项权重非常高:
| 系统 | 组织模型 | 数据权限控制粒度 | 账号体系对接能力 |
|---|---|---|---|
| 系统A | 单层公司+仓库 | 仅仓库级 | 支持标准OAuth2,需二次开发 |
| 系统B | 多级组织树 | 仓库+库区+货主 | 预置接口,支持自定义映射 |
| 系统C | 公司+部门+仓库 | 仓库级 | 仅支持账号密码同步 |
| 系统D | 集团+公司+仓库 | 自定义数据权限脚本 | 对接灵活,但门槛高 |
结论是:选WMS,不要光看功能列表,要看它底层账号体系是否能支撑组织合并、人员调岗、外部供应商/承运商账号管理。否则业务发展到多国多仓时,WMS会让你卡在人员权限管理上寸步难行。
5. 模拟业务:从登录到业务单据入库的完整链路
5.1 登录与令牌获取流程
我用示例工程模拟一个“华东仓收货员小张”从登录到做收货业务的完整流程。小张在浏览器打开业务系统,未登录,跳转到用户中心登录页。输入账号密码,用户中心校验通过后,生成accessToken和refreshToken,accessToken 30分钟有效,refreshToken保存到Redis,7天有效。用户中心带着accessToken回调到业务系统的/callback接口,业务系统用授权码换token,然后获取用户信息。
业务系统拿到用户信息后,不再走用户中心数据库,而是落地到本地快照表,并初始化一个CurrentUser上下文。
5.2 业务请求的权限链路处理
小张点开“收货单创建”页面,前端请求POST /api/receipts。请求拦截器解析accessToken,取出用户ID,从本地缓存加载用户角色和权限。权限判断发现小张拥有receipt:create权限点,放行。Controller层拿到当前用户上下文中的归属仓库列表,校验请求体中的warehouseId是否在授权范围内,不是则返回403。通过后创建收货单,SQL写入时强制带上tenantId和warehouseId。
5.3 前端按钮级别控制与后端数据过滤
小张能看到哪些页面、能点哪些按钮,依赖用户中心返回的权限列表,前端根据权限进行菜单渲染。但是注意,前端隐藏只是用户体验优化,后端的权限判断和仓库过滤才是安全的关键。
完整流程里还要注意一个点,小张浏览器里的登录态过期了怎么办?前端发现访问接口返回401,需要拿着refreshToken去用户中心换新的accessToken,这个过程对用户无感知。如果refreshToken也过期了,则跳到登录页。这个token刷新逻辑建议做成前端全局的,不要在每个接口里单独写。
5.4 审计日志与用户行为记录
审计日志是示例工程里容易被新手忽略的模块。合规要求越来越严格的今天,谁在什么时间对什么数据做了什么操作,一定要能追溯。用户中心提供的审计能力包括登录日志、权限变更日志、异常操作日志。
业务系统也要记录业务操作日志,比如收货单创建、状态流转、审核操作。日志字段至少包含:用户主账号ID、应用账号、IP、操作时间、操作类型、操作对象、操作前后数据快照(可选)。这些数据我建议异步写入,不要阻塞主业务流程。有次我们一个客户要求审计日志必须落库覆盖关键业务动作,当时异步消息队列扛不住高峰流量,后来把日志批量写入改成Redis队列缓冲加批量消费,才把性能问题解决。
注意:业务操作日志不建议用Log4j直接打印到应用日志文件里,查询审计记录时没法查。要独立落在审计表或独立的日志存储中。
6. 常见问题排查与踩坑心得
6.1 Token过期导致调用用户中心接口401
最常见的问题,业务系统拿着accessToken调用户中心接口,用户中心返回401。排查思路:先确认accessToken是否在有效期,再看业务系统的时钟和用户中心时钟是否NTP同步。JWT校验如果依赖系统当前时间和签发时间,时钟漂移会导致大量验证失败,我当时遇到过一次,排查了老半天最后发现是运维没有配置时钟同步。
6.2 回调地址不一致导致登录失败
业务系统把redirect-uri配置成https://sample.mole.com/callback,但用户中心后台登记的是https://sample.mole.com:8443/callback,端口不一样,也会报错。这种问题日志里报的还是通用OAuth错误,非常容易让人绕弯。直接对比两边的回调地址,一个字符都不能差。
6.3 权限变更后前端不生效
管理员在用户中心修改了小张的角色,把小张的“收货单删除”权限去掉了,但小张刷新页面还是能看得见删除按钮。原因大概率是前端的权限列表缓存在本地,没有实时刷新。解决方法是登录时拉取一次,前端会话启动时再校验一次,或者用户中心在权限变更事件里发一个消息,前端收到后主动刷新。
6.4 用户离职后仍能访问业务系统
这个场景最可怕。系统A的token有效期设置为7天,小张离职了,管理员把主账号停了,但小张的token还没到期,他依然能访问业务系统。用户中心能控制主账号登录,但已经签发的JWT很难主动作废。所以一定要配置短时效的accessToken加可吊销的refreshToken,并确保业务系统每次收到请求都去校验用户主账号的启用状态,至少用Redis缓存用户状态并设置很短的时间,比如1分钟。
有个更稳妥的做法,用户中心在“用户禁用事件”中发消息,业务系统消费事件后,把该用户的本地session全部强制失效。这样哪怕token还有效,业务系统在拦截器阶段发现本地session不存在,直接拒绝。
6.5 组织调整导致数据权限变化延迟
小张从华东仓调到华南仓,用户中心里组织关系变了,但业务系统的CurrentUser还缓存了旧的仓库授权范围。结果他去操作华南仓的单据说没有权限,操作华东仓的单据又还能操作。解决方案:组织变更时,用户中心发一个org-change事件,业务系统监听后,立刻刷新用户上下文,并同步更新数据权限缓存。
6.6 性能优化与缓存策略
用户中心相关查询如果每次都打DB,系统压力一定扛不住。示例工程的优化方向:用户基础信息缓存30分钟,角色权限缓存10分钟,组织树缓存1小时并加版本号。同时,用本地Caffeine + Redis二级缓存,先查本地,本地没有再去Redis,Redis没有再去DB。缓存更新采用主动失效加定时兜底刷新。
7. 扩展建议:把示例工程改造成产品级底座
7.1 从示例工程到生产环境需要补强的点
示例工程毕竟是示例,直接上生产的话有几个点一定要补。第一个是网关层统一认证与鉴权,业务系统的所有请求入口建议经过API网关,网关做token校验和IP白名单,业务系统内再配合本地拦截器做权限细节校验。第二个是操作审计字段要全,出现合规纠纷时,数据不全是最大的坑。第三个是密钥管理的规范化,client-secret要放在配置中心或密钥管理服务中,并定期轮换。
7.2 多租户与级联组织扩展
示例工程如果只做了单租户,那扩展成SaaS底座时,需要考虑级联组织。一个集团下有多个公司,每个公司下有多个仓库,租户下挂着完整组织树。用户中心最好能支持租户维度配置“数据级联可见”,比如:集团管理员看全集团数据,公司管理员看本公司数据,仓库管理员看本仓数据。这个规则不用写死在代码里,可以做成数据字典。
7.3 向业务系统输出组织架构同步能力
产品级的用户中心除了账号权限,还应该提供组织架构的“事件流”输出能力,比如企业微信、钉钉、飞书的组织架构实时同步。这块的异步消费和幂等处理比较关键,重复同步消息不能造成脏数据。
8. 写在最后的实操体会
Mole这个示例工程,我认为最大的价值,不在于代码本身有多惊艳,而在于它对用户中心和业务系统的边界拿捏得很清楚。它告诉后来者,什么东西该放底座,什么东西该放业务侧,一条一条理得清清爽爽。我在实际项目中踩过最多的坑,恰恰就是边界不清晰,用户中心功能越做越宽,最后变成一个啥都管却啥都管不好的巨型系统。
建议所有准备对接近似底座的同学,拿到示例工程后不要急着敲代码,先花半天时间把它的数据模型梳理一遍,再对照自己业务系统的组织模型,想想哪些要改、哪些直接复用。磨刀不误砍柴工,这一步想透了,后面的对接就是填表和调接口的事。
另外我分享一个小技巧,排查权限问题时,不要凭肉眼去核对角色和权限点,做一个SQL直接把用户、角色、权限点、资源范围四张表join出来,一眼看全链路。别看这个办法土,真能帮你省下大量排查时间。