首页 / 资讯中心 / 文章详情

基于Spring Boot与Spring Security的养老系统全链路解析:从RBAC权限到Xxl-job定时任务

基于Spring Boot与Spring Security的养老系统全链路解析:从RBAC权限到Xxl-job定时任务 ★ FEATURED ARTICLE
简介这份PDF资料面向正在准备求职面试的开发者尤其是需要梳理项目经历、优化简历中项目描述的初中级程序员。内容以中州养老系统为完整案例围绕面试中如何表达项目、简历中如何编写项目展开涵盖项目概述、技术栈选型、微信小程序登录与参观预约模块、基于RBAC的权限控制、Spring Security认证授权机制以及服务设计与开发流程并附有可直接参考的简历描述示例。资源包共1个PDF文件大小约130KB篇幅精炼便于快速通读和对照修改。目前已有546人学习下载适合需要把项目讲清楚、写明白的求职者参考读者可从中获取项目亮点的提炼思路、技术细节的表达方式以及简历条目的组织方法用于面试复盘与简历打磨。1. 面试官追问项目细节时我靠这套养老系统讲清了全链路去年帮一位朋友复盘面试他被问到“你做的项目里权限是怎么落地的”结果只憋出一句“用了 Spring Security”。面试官接着问 token 怎么生成、过期怎么处理、多端登录怎么控制他当场卡壳。问题不在技术不会而在于没把项目拆成能讲清楚的链路。这份资源里的中州养老系统恰好是一个适合拿来练“项目表达”的完整单体项目小程序端做参观预约、老人绑定、账单缴纳后台做来访、入退、在住、服务、财务管理技术栈覆盖 Spring Boot、Spring Security、Redis、Xxl-job、MyBatis、Swagger。它不只是一个能跑的业务系统更是一份可以逐层拆解、在简历和面试里讲出深度的素材。下面按“项目是什么 → 怎么跑 → 怎么改”的顺序把它拆开讲。2. 项目定位与技术选型一个单体项目为什么敢用这么多中间件2.1 业务边界与整体流程中州养老系统的业务主线很清晰家属通过微信小程序预约参观到院后由后台完成来访登记老人入住后进入在住管理期间可以下单护理服务、缴纳账单最终办理退住。整条链路从“参观预约”一直走到“退住办理”后台管理模块覆盖来访管理、入退管理、在住管理、服务管理、财务管理小程序端覆盖参观预约、老人绑定、账单缴纳、服务下单。这个业务边界决定了它是一个典型的单体项目前后端分离开发。单体不等于简单它把权限、缓存、定时任务、对象存储这些企业级能力都塞进了一个工程里正好适合拿来练“在一个项目里讲清多个技术点”的表达能力。面试时如果只说“我做了个养老系统”信息量为零如果能说清“参观预约的过期状态由 Xxl-job 轮询修改权限走 RBAC 五张表认证用 Spring Security 加 Redis 存 token”面试官才会继续追问。2.2 技术栈逐项拆解与选型理由技术在项目中的角色选它的实际理由Spring Boot应用基础框架单体项目快速起服务自动装配省去大量 XMLSpring MVCHTTP 请求处理前后端分离接口层统一走 ControllerSpring Security认证与授权过滤器链天然适合做 token 校验和资源鉴权Spring Cache缓存抽象热点数据走缓存减少数据库压力Redistoken 存储与热点信息存 JWT、存 UUID支撑多端登录控制Xxl-job分布式任务调度可视化界面管理定时任务支持轮询、分片广播、故障转移MyBatis数据访问SQL 可控复杂查询比全自动 ORM 好调Swagger在线接口文档前后端联调时接口契约清晰OSS非结构化存储老人照片、证件等文件不塞数据库MySQL关系型数据用户、角色、资源、预约、账单等核心数据这里有个容易被忽略的表达点为什么定时任务选 Xxl-job 而不是 Spring 自带的Scheduled。Scheduled在单机下够用但项目里预约过期状态需要按集群部署Xxl-job 提供三种调度模式——轮询、分片广播、故障转移。项目实际用的是轮询依次把过期预约从“已预约”改成“已过期”分片广播是一次调度广播到集群所有执行器故障转移是某个执行器挂了任务转移到可用节点继续跑。面试时能把这三者的区别和项目实际选型说清楚比背概念强得多。2.3 目录结构速览前后端分离的单体项目常见目录划分是这样的以 Maven 工程为例zhongzhou-pension/ ├── pension-admin/ # 后台管理端接口 │ ├── controller/ # 来访、入退、在住、服务、财务 │ ├── service/ # 业务逻辑 │ └── security/ # Spring Security 配置、过滤器 ├── pension-miniapp/ # 微信小程序端接口 │ ├── controller/ # 登录、参观预约、账单、服务下单 │ └── service/ ├── pension-common/ # 公共模块工具类、统一返回、异常 ├── pension-dao/ # MyBatis Mapper 与实体 ├── pension-job/ # Xxl-job 执行器与任务处理器 └── pension-resource/ # 静态资源、Swagger 配置实际工程里模块名可能略有差异但分层逻辑一致接口层、业务层、数据层、任务层分开公共能力下沉到 common。二次开发时先认准这个分层改功能就不会到处乱窜。3. 本地跑起来从环境准备到第一个接口通3.1 环境与依赖清单跑这个项目之前本机需要准备的东西不多但版本要对齐否则启动阶段就会翻车JDK 8 或 11Spring Boot 2.x 常见搭配具体看工程pom.xml里的 parent 版本Maven 3.6MySQL 5.7 或 8.0Redis 5Xxl-job 调度中心可选不跑定时任务可以先不启微信开发者工具调小程序端时需要数据库先建库再导入工程里的 SQL 脚本。常见做法是工程根目录下有个sql/文件夹里面分表结构和初始化数据两个文件按顺序执行。执行完检查用户表、角色表、资源表、角色资源关联表、用户角色关联表这五张权限表是否有初始数据没有的话后台登录会直接 403。3.2 配置文件怎么改配置文件一般落在application.yml或application-dev.yml需要改的核心项如下spring: datasource: url: jdbc:mysql://localhost:3306/pension?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password redis: host: localhost port: 6379 database: 0 cache: type: redis xxl: job: admin: addresses: http://localhost:8080/xxl-job-admin executor: appname: pension-job-executor port: 9999datasource.url里的serverTimezone必须显式指定否则 MySQL 8 驱动启动时报时区错误。redis.database建议单独用一个库避免和本机其他项目串数据。xxl.job.admin.addresses如果本地没起调度中心可以先注释掉任务模块的依赖或者把执行器注册开关关掉不然启动日志里会一直刷注册失败。3.3 启动顺序与验证启动顺序建议是MySQL → Redis → Xxl-job 调度中心可选→ 后端主应用 → 小程序端。后端启动成功后先访问 Swagger 页面确认接口文档能打开再用后台默认账号登录拿到 token 后调一个查询接口验证链路。# 启动后端在工程根目录 mvn clean package -DskipTests java -jar pension-admin/target/pension-admin.jar --spring.profiles.activedev # 验证 Swagger 是否可访问 curl http://localhost:8080/swagger-ui/index.html如果 Swagger 打不开先看pension-resource模块里的 Swagger 配置类是否被扫描到再看 Spring Security 是否把文档路径拦截了。常见做法是在安全配置里对/swagger-ui/**、/v3/api-docs/**放行漏了这一步就会一直 403。4. 核心模块拆解登录、权限、定时任务三条链路4.1 小程序登录从 code 到 JWT 的完整链路小程序登录是面试里最容易被追问的模块因为它串起了微信接口、数据库、JWT、Redis 四样东西。前端调wx.login()拿到微信身份校验码调手机号授权拿到手机号校验码两个参数一起发给后端。后端先拿身份校验码加开发者测试 ID 和校验码去换openid再用手机号校验码换手机号然后查本地用户表的openid字段判断新老用户。// 小程序登录核心逻辑示意 public String login(String wxCode, String phoneCode) { // 1. 用 wxCode 换 openid WxSession session wxClient.code2Session(wxCode); String openid session.getOpenid(); // 2. 用 phoneCode 换手机号 String phone wxClient.getPhoneNumber(phoneCode); // 3. 查本地用户 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setPhone(phone); userMapper.insert(user); // 新用户入库 } else if (!phone.equals(user.getPhone())) { user.setPhone(phone); userMapper.updateById(user); // 手机号变更则更新 } // 4. 生成 JWT 返回 return jwtUtil.generateToken(user.getId(), openid); }这段代码的关键参数有三个wxCode是一次性身份校验码phoneCode是一次性手机号校验码openid是微信侧唯一标识。逻辑说明上要强调两点一是openid是判断新老用户的唯一依据不能拿手机号判断因为用户可能换手机号二是 JWT 里放的是用户 ID 和 openid不放敏感信息后续请求靠请求头携带 token 做身份识别。面试时把“为什么用 openid 而不是手机号做主键判断”讲出来就是一个加分点。4.2 权限控制RBAC 五张表怎么落到代码里后台权限走的是标准 RBAC 模型用户、角色、资源各一张表角色资源关联表、用户角色关联表各一张一共五张。一个角色可以拥有多种资源一种资源也可以被多个角色拥有所以资源和角色是多对多角色和用户同样多对多。权限最终落到“某个用户能不能访问某个资源”这个判断上。-- 查询某用户拥有的资源权限授权时用 SELECT r.permission FROM sys_resource r JOIN sys_role_resource rr ON rr.resource_id r.id JOIN sys_user_role ur ON ur.role_id rr.role_id WHERE ur.user_id #{userId} AND r.status 1;这条 SQL 是授权链路的底座从用户 ID 出发经用户角色关联表找到角色再经角色资源关联表找到资源最后取出资源上的权限标识。实际代码里这层查询结果会缓存到 Redis避免每次请求都打数据库。参数userId来自 token 解析后的用户信息status 1过滤掉已停用的资源。二次开发时如果要加新菜单只需要在资源表插一条记录、在角色资源关联表给对应角色授权不用改代码。4.3 认证与授权UUID 加 JWT 的双保险认证用 Spring Security 的AuthenticationManager核心是实现UserDetailsService接口从数据库加载用户、角色、资源列表。认证通过后生成 UUID 和 JWT两者一起存 Redis返回给前端的也是这个 UUID。// 认证成功后生成 UUID JWT 并写入 Redis示意 String uuid UUID.randomUUID().toString(); String jwt jwtUtil.generateToken(userId); redisTemplate.opsForValue().set( login:token: uuid, jwt, 30, TimeUnit.MINUTES ); return uuid;授权阶段请求进来先取请求头里的 UUID为空直接 403再用 UUID 去 Redis 取 JWT取不到或解析失败也 403解析成功后比对 Redis 里的 UUID 和前端传的是否一致不一致说明同一账号在别处登录过按“后登录优先”原则让旧会话失效一致则检查 JWT 剩余存活时间低于阈值自动续期最后判断用户是否拥有目标资源权限有则放行。这套设计里 UUID 的作用是保证 JWT 唯一性、防重放攻击JWT 负责携带用户身份Redis 负责会话状态。面试时如果被问“为什么 JWT 还要配 Redis”答案就是纯 JWT 无法主动失效加了 Redis 才能实现踢下线、续期、多端登录控制。这是这个项目最值得讲的技术点之一。4.4 参观预约与 Xxl-job 定时任务参观预约模块本身不复杂难在过期状态的处理。用户预约后状态是“已预约”过了预约时间要自动改成“已过期”。项目用 Xxl-job 做定时调度任务处理器里按轮询模式扫描过期预约并批量更新。// Xxl-job 任务处理器扫描并更新过期预约示意 XxlJob(expireReservationHandler) public void expireReservation() { // 查询预约时间已过且状态仍为“已预约”的记录 ListReservation list reservationMapper.selectExpired(); for (Reservation r : list) { r.setStatus(ReservationStatus.EXPIRED); reservationMapper.updateById(r); } XxlJobHelper.log(本次处理过期预约 {} 条, list.size()); }XxlJob注解里的名字要和调度中心配置的任务名一致否则调度中心触发不到。selectExpired的查询条件通常是“预约时间 当前时间 且 状态 已预约”批量更新时注意加状态条件防止并发重复处理。轮询模式适合这种无状态、可重复执行的任务如果任务要分摊到多台机器并行处理才考虑分片广播。这个选型理由在面试里讲出来比单纯说“用了 Xxl-job”有说服力。5. 避坑与排查启动和联调阶段最容易翻车的几处5.1 启动报时区或驱动错误现象是应用启动直接抛The server time zone value is unrecognized。原因是 MySQL 8 驱动要求显式指定时区而连接串里没写。解决办法是在datasource.url末尾加serverTimezoneAsia/Shanghai同时确认驱动版本和数据库版本匹配MySQL 8 用com.mysql.cj.jdbc.Driver。5.2 后台登录一直 403现象是账号密码正确但登录接口返回 403。原因通常是权限表初始化数据缺失或者 Spring Security 配置类把登录接口也拦截了。排查顺序先查五张权限表是否有数据再查安全配置里登录路径、Swagger 路径是否放行最后看UserDetailsService是否真的查到了用户资源列表。漏任何一环都会 403。5.3 Redis 连不上导致 token 校验全挂现象是所有需要登录的接口都返回 403日志里能看到 Redis 连接超时。原因是 Redis 没启动或redis.database配错库。解决是先redis-cli ping确认服务活着再核对配置文件里的 host、port、database 三项。注意 token 是存在指定库里的换库等于所有会话失效。5.4 Xxl-job 任务不触发现象是调度中心显示任务存在但执行器日志里没有执行记录。原因多半是执行器appname和调度中心配置的不一致或者执行器端口被占用。解决是核对xxl.job.executor.appname与调度中心新增执行器时填的名称完全一致再确认port没冲突。本地没起调度中心时任务模块启动报注册失败属于正常现象不影响主业务。5.5 小程序端请求跨域或 token 丢失现象是小程序调后端接口报跨域或者登录后下一个请求又提示未认证。跨域问题在开发阶段常见做法是后端加 CORS 配置或在开发者工具里关掉域名校验token 丢失多半是前端没把登录返回的 UUID 放进请求头。排查时先看请求头里有没有携带再看后端过滤器取的是哪个 header 名两边必须对齐。6. 二次开发与面试表达把项目改出差异化的几个抓手6.1 从“能跑”到“能讲”的改造思路拿到这个项目后最忌讳的是原样跑通就去面试因为面试官一听就知道是模板项目。真正有效的做法是挑一两个模块做可验证的改造让项目带上自己的痕迹。比如把参观预约的轮询模式改成分片广播在任务处理器里按预约 ID 取模分片观察多执行器下的处理分布或者给权限模块加一个“数据权限”维度在资源表上增加部门字段让授权 SQL 多一层过滤。改造不需要大但要有前后对比面试时能说出“原来是什么问题、我改了什么、验证结果如何”。// 分片广播改造示意按分片参数取模处理 XxlJob(expireReservationShardingHandler) public void expireReservationSharding() { int shardIndex XxlJobHelper.getShardIndex(); // 当前分片序号 int shardTotal XxlJobHelper.getShardTotal(); // 分片总数 ListReservation list reservationMapper.selectExpiredByShard(shardIndex, shardTotal); // selectExpiredByShard 内部用 id % shardTotal shardIndex 过滤 for (Reservation r : list) { r.setStatus(ReservationStatus.EXPIRED); reservationMapper.updateById(r); } }这段改造的价值在于getShardIndex和getShardTotal是 Xxl-job 分片广播模式提供的上下文参数改造后同一个任务在多台执行器上各处理一部分数据不会重复也不会遗漏。面试时把轮询和分片广播的差异、改造前后的处理量对比讲清楚比背调度模式定义强得多。6.2 简历描述怎么从流水账变成技术叙事原简历里“负责微信小程序登录、权限分配、参观预约、护理项目等模块的开发与设计”这句话本身没错但太像任务清单。改写方向是每个模块带一个技术决策和一个可量化结果。比如登录模块写成“基于微信 code2Session 实现小程序静默登录用 openid 做新老用户判定JWT 配合 Redis 实现会话续期与多端登录互斥”权限模块写成“基于 RBAC 五表模型落地资源级鉴权认证后生成 UUID 与 JWT 双凭证存 Redis解决纯 JWT 无法主动失效的问题”。每句话都对应代码里真实存在的链路面试官顺着问也问不倒。原表述改写方向面试可展开点负责小程序登录模块微信登录链路 JWT Redis 会话openid 判定、token 续期、多端互斥负责权限分配RBAC 五表 资源级鉴权多对多关联、权限缓存、动态菜单负责参观预约Xxl-job 轮询改分片广播三种调度模式差异、分片参数配合测试调优缓存热点数据、接口压测Spring Cache 命中率、慢 SQL6.3 一个我踩过的表达坑早些年我讲项目喜欢从技术栈开始背Spring Boot、Redis、MyBatis 一路报菜名面试官听到第三个就走神了。后来改成从一条业务链路切入——比如“用户预约参观后如果没来处理系统怎么自动把状态改成过期”顺着这条线讲定时任务、讲调度模式、讲为什么不用Scheduled面试官反而会追着问细节。从那以后我每次准备项目表达都强制自己先用一句话说清“这个功能解决什么问题”再往下拆技术讲不通就说明自己没真吃透。这份养老系统资源的价值也在这里它给了一条完整可拆的链路剩下的就看你愿不愿意动手改一处、讲透一处。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站