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

微信小程序求职招聘系统:SSM到SpringBoot与投递状态机

微信小程序求职招聘系统:SSM到SpringBoot与投递状态机 ★ FEATURED ARTICLE
简介面向高校课程设计、毕业设计及小程序全栈练手场景的微信小程序求职招聘系统源码包采用微信小程序客户端、Java后台与MySQL数据库后端基于SSM并可升级至SpringBoot。系统划分求职者、商家、管理员三类角色求职者和商家在小程序端完成微信授权登录、个人资料完善、兼职分类浏览、岗位联系、面试预约商家经学校认证后可发布兼职、处理面试请求并配合互评评分与客服系统形成闭环管理员在Web端后台维护数据。压缩包共1222个文件、约4.09MB涵盖html、js、css等Web页面与脚本java、jsp、sql等后端与数据库文件以及wxml、wxss、json等小程序页面结构和配置文件另有png、gif等界面素材便于对照前后端实现与部署。已有312人学习适合需要完整业务闭环、数据库脚本和可扩展后端架构的读者参考二次开发。1. 从一份“能跑起来”的求职小程序说起需求边界与选型判断做微信小程序求职招聘系统的第一步不是画页面而是先想清楚一件事这个系统的核心是把“投递之后的状态”追踪清楚而不仅仅是“发布职位”。很多人上来就把小程序首页做成职位瀑布流结果企业端连“谁投了我、投了几次、简历看到哪一步”都查不到答辩时被追问一句就卡住。这套方案要解决的是求职者、企业HR、平台管理员三端在同一个岗位池上协作的问题后端用 SSM 打底、预留 SpringBoot 升级路线小程序端只做轻量渲染和状态展示。它适合正在做毕设或课程设计、需要一套能跑通、也能讲清分层结构的人也适合想拿现成后端骨架改造成招聘类业务的开发者。2. 需求拆解与角色建模三端职责、功能矩阵与投递状态机招聘类系统和普通信息发布系统的分水岭就在“角色”这两个字。同一个职位求职者看到的是薪资、城市、学历门槛和“投递”按钮企业HR看到的是投递人数、简历附件和“邀面试”按钮管理员看到的是企业资质审核队列和违规岗位下架入口。如果一开始不把这三端的权限边界画出来后面所有接口都会写成“谁都能调”最后不得不在 Controller 里堆一堆 if 判断改一次崩一次。所以方案落地的顺序是先出功能矩阵再定投递状态机最后补非功能性约束。2.1 三类角色的功能矩阵与权限边界我把三端的职责拆成一张表每个功能点都对应一个后端接口前缀这样后面写 Controller 时不会互相污染。这张表的用法很直接做毕设的话它就是你需求分析章节的骨架做交付的话它就是你和需求方对齐范围的依据。角色核心功能接口前缀数据可见范围求职者登录、简历维护、职位检索、投递、查看进度、撤回投递/api/seeker仅自己的简历与投递记录企业HR企业认证、职位发布/上下架、简历筛选、发起面试、录用/拒绝/api/hr仅本企业职位与投递到本企业的简历平台管理员企业资质审核、职位审核、举报处理、数据统计/api/admin全量但简历正文脱敏这张表背后有一条硬规则任何一端的接口都不能靠前端传 enterpriseId 或 seekerId 来限定范围必须从登录态里取。小程序端的参数是用户能随手改的前端传什么后端都信等于没做权限。通常的做法是登录时把角色和主体 ID 一起写进 Redis 的登录态拦截器解析 token 后放进 ThreadLocalService 层只认 ThreadLocal 里的值。另一条容易忽略的是企业认证状态。企业注册后是“待审核”这时候可以发职位草稿但职位不能进求职者的搜索结果。把这个状态位做进职位表而不是只放企业表是为了查询时少一次 join 判断——虽然我们后面还是 join 了企业表但状态字段冗余一份能让你在只查职位表做统计时少写一堆关联。2.2 投递流程的状态机设计投递是整个系统里唯一有“生命周期”的实体也是最容易写乱的地方。如果只用一个 status 字段随便 update很快就会出现“已拒绝的又被改成录用”“求职者自己把状态改成已录用”这种事故。我的做法是把合法流转关系写死成一张表0 已投递 → 1 已查看 / 5 已撤回求职者操作1 已查看 → 2 邀面试 / 4 已拒绝HR 操作2 邀面试 → 3 已录用 / 4 已拒绝HR 操作3、4、5 为终态不再流转这张表的价值在于所有状态变更走同一个transfer方法非法流转直接抛业务异常。答辩时被问“如果 HR 误操作把候选人拒了怎么办”你可以答终态不可逆但保留了重新发起投递的入口且同岗位同求职者只允许一条有效投递记录靠唯一索引兜底。这个回答比“加个撤销按钮”专业得多。状态机还要考虑并发。HR 在后台点“邀面试”的同时求职者在小程序点“撤回”两个请求几乎同时到达。如果只是先 select 再 update后提交的那个会覆盖前一个。后面第 4 章会给带版本号条件更新的写法这里先记住结论状态流转必须做条件更新update ... where id ? and status ?影响行数为 0 就说明被别人改过了。2.3 非功能性需求审核、时效与数据留存招聘系统有三个躲不掉的非功能点写方案时最好显式列出来否则验收阶段会被追问。第一是审核链路。企业资质、职位内容都要过一遍人工审核审核不通过的职位不能出现在列表里但数据要保留方便企业修改后重新提交。实现上就是职位表一个 status审核记录单独一张表存操作人和时间别把审核意见塞进职位表的备注字段后面查历史根本查不出来。第二是职位时效。岗位发布 30 天自动过期过期后状态置为 3不再进列表但求职者的历史投递记录里还能看到这个岗位的快照信息。用一个Scheduled每天凌晨扫一遍即可量小的时候不需要引入消息队列。这里有个细节过期任务要按publish_time建索引否则全表扫描在数据量上来后会把数据库拖慢。第三是简历数据留存。简历附件一般存对象存储或者本地磁盘数据库只存 URL 和文件指纹。求职者删除简历时数据库做逻辑删除文件保留 90 天再物理清理避免企业已经下载的附件突然 404。这类约定写进方案里会让整份设计看起来是有人真正维护过的。3. 总体架构设计SSM 打底、SpringBoot 平滑接管的双层结构标题里“SSM 可升级 SpringBoot”不是一句噱头它决定了整个后端的组织方式。SSM 阶段用 web.xml 装配 DispatcherServlet、用 spring-*.xml 声明 Bean、用 MyBatis 的 SqlSessionFactoryBean 手动配置升级到 SpringBoot 之后这些全部交给 starter 和自动配置业务代码一行不动。要做到“一行不动”关键是在 SSM 阶段就按 SpringBoot 的包结构写代码别把配置和业务搅在一起。3.1 分层结构与小程序端的交互链路后端分四层Controller 只做参数校验和响应包装Service 承载业务规则和事务边界Mapper 只做 SQLDTO/VO 负责内外隔离。实体类不直接返回给小程序原因是数据库字段一变前端就崩而且会把password、openid这类不该露的字段带出去。VO 多写一个类换来的是接口稳定。小程序端到后端的链路是wx.login拿 code → 调/api/auth/login→ 后端用 code 换 openid 并签发 token → 小程序把 token 存 Storage → 后续请求统一带Authorization头。整条链路里session_key和secret必须留在服务端一旦下发到小程序包体里反编译就能拿到这是硬性红线。小程序页面 │ wx.request统一封装带 token ▼ NginxHTTPS 终止、静态资源、限流 ▼ Spring MVC DispatcherServlet ▼ AuthInterceptor解析 token → ThreadLocal 上下文 ▼ Controller → Service事务 → Mapper → MySQL │ └── Redis登录态、热门职位缓存上面这条链路里拦截器是必须的但要注意放行白名单登录接口、职位列表接口未登录也能浏览、静态资源。白名单写成配置文件里的数组别硬编码在拦截器里升级 SpringBoot 后可以直接挪到 yml。分层的另一个好处是可测试。Service 层不依赖HttpServletRequest单元测试可以直接 new 出来跑Mock 掉 Mapper 就行。很多课设的通病是所有逻辑写在 Controller 里一测就得启动整个容器最后干脆不测。3.2 从 SSM 到 SpringBoot 的升级路径升级这件事最怕的是“重写一遍”。正确姿势是先让 SSM 版本跑通再按下面四步换装配方式业务代码零改动。# 第 1 步pom 依赖替换把 spring-webmvc、mybatis、druid 换成 starter 版本 # spring-boot-starter-web / mybatis-spring-boot-starter / druid-spring-boot-starter # 第 2 步新增启动类放在原 com.demo 包的根目录下自动扫描原有 Controller/Service # 第 3 步web.xml 删除DispatcherServlet 由容器自动注册 # 第 4 步spring-dao.xml / spring-service.xml 里的 dataSource、事务管理器、MapperScannerConfigurer 全部删除 # 改由 application.yml 自动配置接管SqlSessionFactory 不再手写 mvn -q clean package -DskipTests java -jar target/job-app.jar --spring.profiles.activedev配置迁移是最容易出错的一步给一张对照表照着替换基本不会翻车SSM 阶段SpringBoot 阶段注意点jdbc.propertiesapplication.yml的spring.datasource密码走环境变量别提交到仓库spring-dao.xml里的MapperScannerConfigurerMapperScan(com.demo.mapper)加在启动类上只扫 Mapper 包spring-service.xml的DataSourceTransactionManager自动配置提供自定义事务管理器要显式声明Beanweb.xml的CharacterEncodingFilterserver.servlet.encoding默认就是 UTF-8手动SqlSessionFactoryBeanmybatis.mapper-locationsxml 路径用classpath:mapper/*.xml升级后还会多出两个白送的能力一是SpringBootTest直接起容器做集成测试二是actuator的健康检查端点答辩演示时打开/actuator/health显示UP比嘴上说“服务正常”有说服力。但 actuator 端点记得加权限或改端口别和生产接口混在一起暴露。3.3 登录态与鉴权方案选型小程序端的登录态有两条常见路线JWT 和 Redis Token。JWT 无状态、不用查库但没法主动失效Redis Token 每次请求查一次缓存但能踢人、能续期、能存角色。这个项目里我选 Redis Token原因是招聘系统有“企业被冻结立即生效”的需求JWT 签发后到过期前都拦不住只能再加一层黑名单反而更复杂。Redis 方案下token 就是随机串值里存用户ID:角色过期时间 7 天每次请求命中后把 TTL 续到 7 天实现“活跃用户不掉线”。// 拦截器核心逻辑解析 token → 续期 → 写入上下文 String token request.getHeader(Authorization); String val redis.opsForValue().get(login: token); if (val null) { response.setStatus(401); return false; // 前端收到 401 后静默重登一次 } String[] parts val.split(:); // 格式 userId:role AuthContext.set(Long.valueOf(parts[0]), parts[1]); redis.expire(login: token, 7, TimeUnit.DAYS); // 滑动续期 return true;参数说明AuthContext用 ThreadLocal 实现请求结束必须在afterCompletion里remove()否则线程池复用会串号——这是 Tomcat 下最典型的“用户 A 看到用户 B 数据”事故来源。角色只放一个字段是因为本系统里一个账号只属于一个角色企业 HR 和求职者不共用账号如果业务要求一人多角色这里要存成逗号分隔的集合拦截器判断时改成contains。4. 数据与核心模块落地六张主表撑起投递闭环表设计不用贪多招聘系统的骨架就是六张用户、企业、职位、简历、投递、消息。其余如面试记录、举报、审核日志都是围绕这六张挂上去的。下面按“建表 → 登录 → 检索 → 状态流转”的顺序落地每一段都能直接抄进项目里改。4.1 核心表结构与索引策略先看职位表和投递表这两张表决定了系统的查询性能和数据一致性。-- 职位表列表页的过滤条件全部落在一条联合索引上 CREATE TABLE job ( id BIGINT NOT NULL AUTO_INCREMENT, enterprise_id BIGINT NOT NULL COMMENT 所属企业, title VARCHAR(64) NOT NULL COMMENT 职位名称, city VARCHAR(32) NOT NULL COMMENT 工作城市, salary_min INT NOT NULL COMMENT 薪资下限单位千元, salary_max INT NOT NULL COMMENT 薪资上限单位千元, education TINYINT NOT NULL DEFAULT 0 COMMENT 0不限 1大专 2本科 3硕士, status TINYINT NOT NULL DEFAULT 1 COMMENT 0草稿 1招聘中 2已下架 3已过期, publish_time DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_city_status_time (city, status, publish_time), KEY idx_ent (enterprise_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 投递表唯一索引是防重复投递的最后一道闸 CREATE TABLE delivery ( id BIGINT NOT NULL AUTO_INCREMENT, job_id BIGINT NOT NULL, seeker_id BIGINT NOT NULL, resume_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0已投递 1已查看 2邀面试 3已录用 4已拒绝 5已撤回, create_time DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_job_seeker (job_id, seeker_id), KEY idx_seeker_status (seeker_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;两个设计点值得讲清。第一idx_city_status_time的字段顺序不是随便排的列表页默认按城市筛、只查招聘中、按发布时间倒序把等值条件放前面、范围排序放最后才能让ORDER BY publish_time DESC走上索引而不触发 filesort。如果你的检索还带薪资下限可以再加一个(city, status, salary_max)的索引但别超过三个联合索引写入会变慢。第二uk_job_seeker唯一索引承担了“同一岗位不能重复投递”的职责。业务层也会先查一次给出友好提示但真正兜底的是索引并发下两个请求同时通过业务校验时第二个会抛DuplicateKeyException捕获后转成“你已经投过这个岗位了”。撤回后再投的场景用status 5的记录更新回 0而不是插入新行避免历史记录分裂。4.2 微信登录换 token 的实现登录接口是小程序端唯一不需要 token 的接口也是最需要小心的地方。RestController RequestMapping(/api/auth) public class WxAuthController { Value(${wx.appid}) private String appid; Value(${wx.secret}) private String secret; // 只从配置读绝不写进小程序 Autowired private RestTemplate restTemplate; // SpringBoot 下由容器注入 Autowired private StringRedisTemplate redis; Autowired private UserMapper userMapper; PostMapping(/login) public ResultString login(RequestBody LoginDTO dto) { // 1. code 换 openidcode 一次性有效有效期 5 分钟 String url https://api.weixin.qq.com/sns/jscode2session ?appid appid secret secret js_code dto.getCode() grant_typeauthorization_code; JSONObject session restTemplate.getForObject(url, JSONObject.class); String openid session null ? null : session.getString(openid); if (openid null) { return Result.fail(401, 登录凭证已失效请重试); } // 2. 首次登录自动建号角色默认求职者HR 走企业认证流程升级 User user userMapper.selectByOpenid(openid); if (user null) { user User.newSeeker(openid, dto.getNickname()); userMapper.insert(user); } // 3. 签发 token 写 Redis值为 userId:role String token UUID.randomUUID().toString().replace(-, ); redis.opsForValue().set(login: token, user.getId() : user.getRole(), 7, TimeUnit.DAYS); return Result.ok(token); } }code只能用一次、5 分钟过期这是 wx.login 的固有约束所以小程序端每次调登录都要重新wx.login不能缓存 code 复用。session_key拿到后只在服务端做解密用比如获取手机号不要返回前端。RestTemplate在 SSM 阶段是手动 new 的升级 SpringBoot 后改成注入这也是“可升级”设计的一个具体体现——类里的写法不变只是装配方式变了。小程序端的请求封装决定了后面几十个页面写起来累不累// utils/request.js —— 统一带 token、统一错误提示、401 静默重登 const BASE_URL https://api.example.com; // 换成自己的已备案域名 function request(options) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { content-type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 401 !options._retried) { // 只重放一次避免 token 一直失效导致死循环 reLogin().then(() request(Object.assign({}, options, { _retried: true }))) .then(resolve).catch(reject); return; } if (res.data res.data.code 0) resolve(res.data.data); else { wx.showToast({ title: (res.data res.data.msg) || 请求失败, icon: none }); reject(res.data); } }, fail: reject }); }); }_retried这个标记是必须的。没有它token 失效 重登失败会形成递归调用小程序直接卡死这是玄学问题里最常见的一种——表面上“偶尔白屏”实际是请求风暴。reLogin内部就是再调一次wx.login拿新 code 换 token 写 Storage代码很短但一定要包 try/catch失败就跳登录页。4.3 职位检索与分页查询检索接口的坑主要在两个地方分页插件的位置和 SQL 里条件的拼装顺序。Service public class JobQueryService { Autowired private JobMapper jobMapper; public PageInfoJobVO search(JobQuery q) { // PageHelper 必须紧贴查询语句中间不能插别的 SQL否则分页会作用到错误的语句上 PageHelper.startPage(q.getPageNum(), q.getPageSize()); ListJobVO list jobMapper.searchByCondition( q.getKeyword(), q.getCity(), q.getSalaryFloor(), q.getEducation()); return new PageInfo(list); } }select idsearchByCondition resultTypecom.demo.vo.JobVO SELECT j.id, j.title, j.city, j.salary_min, j.salary_max, j.education, e.name AS enterprise_name FROM job j JOIN enterprise e ON e.id j.enterprise_id WHERE j.status 1 AND e.verify_status 1 !-- 未过审企业的岗位不进列表 -- if testcity ! null and city ! AND j.city #{city}/if if testsalaryFloor ! nullAND j.salary_max gt; #{salaryFloor}/if if testeducation ! nullAND j.education lt; #{education}/if if testkeyword ! null and keyword ! AND (j.title LIKE CONCAT(%, #{keyword}, %) OR e.name LIKE CONCAT(%, #{keyword}, %)) /if ORDER BY j.publish_time DESC /select参数说明salaryFloor是求职者期望的薪资下限用salary_max salaryFloor匹配语义是“岗位上限够得着我的期望”比salary_min 更符合真实筛选习惯。education用是因为学历门槛是上限约束本科岗位大专生看不到但硕士能看到本科岗位。LIKE %关键词%前置通配符走不了索引这是明摆着的性能债。数据量小的时候无所谓超过十万条就要换成全文索引或者把关键词拆词后走倒排。方案里我会写清楚这条边界答辩被问“数据量大了怎么办”答“当前方案适用万级数据十万级以上引入 ES 或 MySQL 全文索引”比硬说性能很好可信。4.4 投递状态流转的落地状态机落地就两个要点合法流转校验和条件更新。private static final MapInteger, SetInteger FLOW new HashMap(); static { FLOW.put(0, new HashSet(Arrays.asList(1, 5))); // 已投递 - 已查看 / 已撤回 FLOW.put(1, new HashSet(Arrays.asList(2, 4))); // 已查看 - 邀面试 / 已拒绝 FLOW.put(2, new HashSet(Arrays.asList(3, 4))); // 邀面试 - 已录用 / 已拒绝 FLOW.put(3, Collections.emptySet()); // 终态 FLOW.put(4, Collections.emptySet()); FLOW.put(5, Collections.emptySet()); } Transactional(rollbackFor Exception.class) public void transfer(Long deliveryId, int target, String role) { Delivery d deliveryMapper.selectById(deliveryId); if (d null) throw new BizException(投递记录不存在); if (!FLOW.getOrDefault(d.getStatus(), Collections.emptySet()).contains(target)) { throw new BizException(非法状态流转 d.getStatus() - target); } if (SEEKER.equals(role) target ! 5) { throw new BizException(求职者只能撤回投递); } // 条件更新status 作为乐观锁条件影响行数为 0 说明并发冲突 int rows deliveryMapper.updateStatus(deliveryId, d.getStatus(), target); if (rows 0) throw new BizException(状态已被更新请刷新后重试); messageService.pushAsync(d.getSeekerId(), target); // 站内信异步推不阻塞主流程 }update idupdateStatus UPDATE delivery SET status #{target} WHERE id #{id} AND status #{expect} /updateupdateStatus的status #{expect}就是乐观锁不需要额外加 version 字段因为状态本身就是版本。这比select ... for update轻缺点是冲突时要让用户重试但招聘场景的并发冲突概率极低完全够用。pushAsync用Async或简单的线程池提交别在事务里同步发消息——消息服务一慢整个投递接口就超时。5. 避坑与常见问题排查五个最容易翻车的点这套结构跑通不难难的是跑稳。下面五条是同类项目里出现频率最高的问题每条都按“现象 → 原因 → 解决”写出问题时可以当排查清单用。5.1 登录态类问题现象一用户反馈“明明登录了过一会儿又让我重新登录”。原因通常是 token 的续期逻辑写在了登录接口里而不是拦截器里导致只有主动调登录才会续期另一种是 Redis 的 key 设了固定过期时间但没做滑动续期。还有一种隐蔽情况小程序切后台超过一定时间Storage 还在但 Redis 里的 key 已经过期。解决是把续期放进拦截器命中之后用expire重置 TTL前端在收到 401 时先静默重登一次成功就重放原请求失败才跳登录页。同时给 token 加一个“最后活跃时间”字段超过 30 天未活跃直接失效避免长期不用还留着有效凭证。现象二两个用户看到对方的数据。原因几乎都是 ThreadLocal 没清理。线程池复用线程上一个请求的AuthContext残留下来下一个请求如果没解析出 token比如走了白名单分支就会读到脏值。解决是在拦截器的afterCompletion里无条件remove()并且AuthContext.get()在取不到值时直接抛异常而不是返回 null让问题在开发阶段就暴露出来而不是等线上偶发。5.2 数据与并发类问题现象三同一个岗位出现两条投递记录。原因是业务层“先查再插”没有原子性两个请求同时通过查询校验后都执行了插入。只靠业务校验是挡不住的。解决是给(job_id, seeker_id)加唯一索引插入时捕获DuplicateKeyException转成业务提示撤回后的重新投递走更新而非插入。这条规则在任何“防重复”场景都通用数据库约束是最后一道闸业务校验只是为了让提示更友好。现象四列表页越翻越慢最后一页卡十几秒。原因通常是ORDER BY字段没有索引或者分页参数被前端传成了负数、超大值。还有一种高频原因是 PageHelper 的startPage和查询语句之间隔了别的查询导致分页作用到了错误的 SQL 上返回的数据量异常。解决是给排序字段建联合索引Service 入口对pageNum、pageSize做上下限校验pageSize最大 50并检查startPage是否紧贴查询。翻到深页仍然慢的话改成基于publish_time的游标分页别死磕 offset。5.3 升级与部署类问题现象五本地跑得好好的打成 jar 之后所有 Mapper 都找不到。原因是 MyBatis 的 xml 放在src/main/java目录下Maven 默认不会把 java 目录里的非 class 文件打进包SSM 阶段用 IDE 直接跑所以没暴露。解决是把 xml 统一放到src/main/resources/mapper/并在配置里写mybatis.mapper-locationsclasspath:mapper/*.xml如果坚持放在 java 目录就得在 pom 里配resources的 include。同类问题还有application.yml放在错误位置导致配置不生效现象是“连的库还是本地库”排查时先看启动日志里打印的 profiles 和数据源 URL别靠猜。6. 验收与答辩怎么证明这套系统真的能用验收这一步很多人栽在“功能都点了但说不清边界”。我的建议是准备两张表一张功能验收清单一张接口用例表现场演示前先自己走一遍。验收项判定标准演示方式登录与身份openid 唯一重复登录不建重复账号两个小程序账号分别登录后台查 user 表权限隔离HR 调求职者接口返回 403用 HR token 请求/api/seeker/resume检索准确性城市 学历 薪资三条件组合结果正确三种条件组合各查一次比对总数投递闭环投递→查看→邀面试→录用每步状态可见双端同时打开实时刷新进度非法流转已拒绝的投递不能再改录用直接调接口传目标状态看是否报错过期任务改系统时间或手动触发过期岗位不再出现手动执行定时任务后刷新列表答辩被追问最多的三个点提前准备好答案。第一个是“为什么用 SSM 还要升级 SpringBoot”答SSM 阶段是为了把分层和装配关系讲清楚升级后业务代码零改动体现的是可维护性而不是追新。第二个是“并发投递怎么保证不重复”答唯一索引兜底 捕获异常转业务提示不要说“我加了锁”锁的粒度和释放时机一问就露馅。第三个是“数据量大了怎么办”答给出当前方案的数据量边界和下一步演进方向比如检索换全文索引、列表加 Redis 缓存别硬撑说性能没问题。我自己的习惯是不管功能多简单验收前一定用两个不同角色的账号把关键路径各走一遍因为单账号测试永远发现不了权限越界。这个习惯是被坑出来的——有次演示时用管理员账号点了一圈全部正常换成普通用户立刻 500原因就是拦截器里漏了一个角色判断。从那以后我每次交付前都强制走一遍“双角色交叉验证”宁可多花二十分钟也不在上台时翻车。希望这套结构和清单能帮到你需要对照源码和建表脚本改的话可以直接拿去用。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站