Spring Boot + Vue 企业人事管理系统设计与实战全解析

发布时间:2026/9/9 23:56:57
Spring Boot + Vue 企业人事管理系统设计与实战全解析 去年年中接了一个企业人事管理系统的活技术栈定的是springbootvue典型的web端前后端分离项目。这个系统不算大但涉及的功能模块很全——员工管理、部门管理、考勤、工资、招聘、统计报表基本把人事日常工作的核心流程都覆盖了。做完之后回头复盘发现这种springbootvue组合做企业管理系统确实有它不可替代的优势开发效率高、解耦清晰、后期维护也省心。这篇博文就把整个项目的设计与实现过程拆开讲清楚从架构设计、数据库建模、后端接口、前端页面到部署上线每一步都附上我实际踩过的坑和解决方案。如果你正准备接手类似的web人事管理系统或者在做springbootvue全栈项目的毕设这篇文章应该能帮你少走不少弯路。1. 整体架构设计与技术选型1.1 为什么选springbootvue而不是传统单体方案接手这个企业人事管理系统之前我也考虑过直接用传统的JSPServlet方案毕竟网上相关教程和资料非常多公司里也有老工程师习惯那套技术栈。但仔细评估之后我还是决定用springbootvue做前后端分离原因其实很现实。第一人事管理系统的前端交互相对复杂。员工档案要支持多条件筛选、分页、批量导入导出考勤打卡要实时刷新状态工资模块要展示各种统计图表。如果用JSP配合jQuery做前端逻辑一旦复杂起来代码维护成本会迅速上升。而Vue的组件化开发方式天然适合这种多模块、多交互的管理系统。第二后端接口复用性强。人事系统将来大概率要对接其他系统比如企业微信的审批通知、钉钉的考勤同步、财务系统的薪资发放。springboot提供标准的RESTful API数据格式统一用JSON将来做系统集成时不需要额外开发适配层。第三部署和团队协作更灵活。前后端分离之后前端可以由专门的同学负责后端专注业务逻辑通过接口文档约定联调互不阻塞。而且前端静态资源可以直接丢到Nginx后端Java应用单独部署服务器资源利用更合理。1.2 技术栈全景与版本选型下面这套技术栈是我在这个项目中实际使用的版本组合整体比较稳定网上的参考资料也多出现问题容易搜到解决方案。技术项选型版本选型理由后端框架Spring Boot2.7.x稳定版社区生态成熟避免过高版本带来的兼容性问题持久层框架MyBatis-Plus3.5.x单表CRUD零SQL内置分页插件开发效率高数据库MySQL8.0主流关系型数据库性能稳定管理系统的首选鉴权方案JWT Spring SecurityJJWT 0.9.1无状态认证适合前后端分离架构前端框架Vue2.6.x团队熟悉度高生态组件丰富配合Element UI快速搭建后台界面前端构建工具Vue CLI4.5.x配置简单开箱即用UI组件库Element UI2.15.x后台管理系统最常用的组件库表格、表单、弹窗都齐全接口文档Knife4j3.0.x基于Swagger的增强版生成接口文档方便前后端联调有朋友问我为什么不用Vue 3其实当时考虑到Element UI对Vue 2的支持最稳定团队里其他项目也都是Vue 2统一技术栈对维护更有利。选型这件事没有绝对的对错关键是团队能不能驾驭、生态是否够用。1.3 前后端分离后的项目结构规划项目结构在开发之前就要想清楚不然后面改起来真的会头大。我习惯按照模块分包而不是按层分包这样每个业务模块的自包含性更强改动时影响范围可控。hr-system/ ├── backend/ # springboot后端 │ ├── src/main/java/ │ │ └── com/company/hr/ │ │ ├── config/ # 配置类Security、跨域、MyBatisPlus │ │ ├── controller/ # 控制层 │ │ ├── service/ # 业务层 │ │ ├── mapper/ # 数据访问层 │ │ ├── entity/ # 实体类 │ │ ├── dto/ # 请求响应对象 │ │ ├── common/ # 统一返回结果、异常处理 │ │ └── util/ # 工具类JWT、Excel导入导出 │ └── src/main/resources/ │ ├── mapper/ # MyBatis XML映射文件 │ └── application.yml # 配置文件 ├── frontend/ # vue前端 │ ├── src/ │ │ ├── api/ # 接口请求封装 │ │ ├── router/ # 路由配置 │ │ ├── store/ # Vuex状态管理 │ │ ├── views/ # 页面组件 │ │ ├── components/ # 公共组件 │ │ └── utils/ # 工具函数 │ └── vue.config.js # 前端构建配置 └── sql/ # 数据库初始化脚本后端按业务模块划分的好处是比如开发考勤模块时只需要关注attendance相关的controller、service、mapper不需要在整个项目里到处找文件。前端则按照页面维度组织每个功能页面一个目录目录内包含页面主体和子组件清晰直观。2. 数据库设计人事系统的地基2.1 核心表结构解析人事管理系统听起来功能多但核心数据模型其实就围绕几个主实体。我整理了最核心的几张表从关系上就能看出系统的整体脉络。用户与员工是系统的基础我特意把账户信息和员工档案分离因为并非所有有账号的人都是员工比如外部合作人员可能需要登录系统查看某些信息。员工表保存基本人事信息账户表保存登录凭证。部门表使用了父子关系的树形结构通过parent_id关联父部门这样组织架构可以无限层级扩展前端Tree组件渲染也很方便。考勤记录表是另一个重点我设计了attendance_date用来标记是哪一天的考勤clock_in_time和clock_out_time分别记录上下班打卡时间status字段区分正常、迟到、早退、缺勤。这里有个设计经验不要只存打卡时间最好存原始打卡记录和最终考勤结果两个级别因为迟到、早退、补卡这类逻辑在月底算工资时经常需要人工调整。工资表按月生成记录employee_id关联员工salary_month存储工资所属月份后面的基础工资、绩效工资、各项补贴、扣除项都是独立字段。工资数据属于敏感数据接口层面一定要做权限控制这个后面在后端安全部分详细讲。2.2 树形部门表的查询优化部门表是标准的树形结构直接用递归查询在数据量大的时候性能很差。我的做法是在MyBatis-Plus里利用selectList查出所有部门然后在Java内存中组装成Tree结构一次查询搞定。public ListDeptNodeVO buildDeptTree() { ListDept deptList deptMapper.selectList(null); // 转换为VO节点 ListDeptNodeVO nodeList deptList.stream().map(dept - { DeptNodeVO node new DeptNodeVO(); node.setId(dept.getId()); node.setName(dept.getName()); node.setParentId(dept.getParentId()); return node; }).collect(Collectors.toList()); // 在内存中组装父子关系 MapLong, DeptNodeVO nodeMap nodeList.stream() .collect(Collectors.toMap(DeptNodeVO::getId, item - item)); ListDeptNodeVO rootList new ArrayList(); for (DeptNodeVO node : nodeList) { if (node.getParentId() 0L) { rootList.add(node); } else { DeptNodeVO parent nodeMap.get(node.getParentId()); if (parent ! null) { parent.getChildren().add(node); } } } return rootList; }这个方案在部门数量不超过几百个的前提下完全够用一次查询、内存组装代码简洁易懂。如果部门数量到了几千上万的规模再考虑用物化路径或者嵌套集模型但对于绝大多数企业的组织架构规模这个方案已经绰绰有余了。2.3 员工表和账户表的分离设计很多开发者在设计时会直接把登录账号和员工信息合并成一张表省事。但在人事系统这个场景我强烈建议分离。原因有三一是账号可能禁用、删除但员工档案需要长期保留二是员工换手机号、改密码是高频操作不应该影响人事档案数据三是权限控制的角色字段放在账户表上更安全员工表负责基础信息维护即可。员工表和账户表通过employee_id建立一对一关联具体代码里用TableField和MyBatis-Plus的selectJoin或者手动写联表查询都可以。这个项目里我选择在Service层分两次查询再组装避免联表带来的耦合也方便后续缓存优化。3. 后端Spring Boot核心业务实现3.1 JWT鉴权与Spring Security整合人事管理系统涉及大量员工隐私数据鉴权是安全的第一道门槛。我选用JWT作为无状态令牌方案配合Spring Security做接口级别的权限控制。JWT的流程很简单用户登录成功后后端生成一个包含用户ID、用户名、角色信息的token返回给前端。前端把token存在localStorage每次请求都在请求头加上Authorization: Bearer token。后端通过过滤器解析token将用户上下文写入SecurityContext方便后续接口获取当前登录用户。生成JWT的核心代码public String generateToken(HrUser user) { Date now new Date(); Date expireDate new Date(now.getTime() 24 * 60 * 60 * 1000); // 24小时过期 return Jwts.builder() .setSubject(user.getUsername()) .claim(userId, user.getId()) .claim(role, user.getRole()) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); }Spring Security的配置里注意几个坑放行登录接口和Swagger文档接口其他接口都要走JWT过滤器自定义OncePerRequestFilter实现JWT解析过滤器中不要抛异常影响后续Spring Security的异常处理链密码加密用BCrypt不要用MD5这是红线问题3.2 基于RBAC的权限控制人事系统里不同角色看到的菜单、操作的按钮是不一样的。管理员能看工资数据、能修改员工信息普通人事专员只能看部分模块普通员工只能看自己的信息。这个权限控制我用的是RBAC模型用户-角色-权限三层结构。数据层面设计了三个表sys_user_role、sys_role、sys_role_menu通过中间表关联。后端在登录时把当前用户的权限标识码比如sys:employee:add这样的字符串加载到JWT的claim里前端拿到权限列表后动态渲染菜单和按钮。这里有个实际经验后端接口的安全校验不能只依赖前端隐藏按钮。比如工资导出接口前端没显示导出按钮只能挡住普通操作但如果有人通过接口工具直接调用了导出的URL呢所以后端接口必须也校验权限码。我用Spring Security的PreAuthorize(hasAuthority(sys:salary:export))注解挂在需要权限的接口上注解级别的校验简洁可靠。3.3 统一返回结果与全局异常处理前后端分离后接口返回格式要统一否则前端每位同学各写一套解析逻辑联调时全是灾难。我封装了统一的返回对象ResultT结构固定为code、message、data三个字段。code等于200表示成功其他数值对应不同的错误类型。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new TResult(); result.setCode(code); result.setMessage(message); return result; } }全局异常处理用的是Spring Boot的RestControllerAdvice把业务异常、参数校验异常、系统异常统一拦截避免把堆栈信息直接暴露给前端。我对业务异常做了单独的BusinessException类业务逻辑里判断条件不满足时就抛出全局处理器统一返回错误信息。3.4 Excel批量导入导出的实现人事系统的员工花名册导入导出是高频功能HR手里经常有几百上千人的Excel表格一条条手动录入简直地狱。我用EasyExcel库实现性能和内存占用都比Apache POI优秀得多。导入员工数据时前端上传Excel文件后端用EasyExcel解析成对象列表然后批量插入数据库。这里要特别处理两个问题数据校验Excel里的手机号格式、身份证合法性、入职日期格式都要校验把错误数据和原因汇总起来让前端一次性展示给用户而不是导入到一半报错回滚。批量插入性能500条数据逐条insert和用MyBatis-Plus的saveBatch批量插入性能差距是数量级的。实测500条数据批量插入耗时不到1秒逐条插入可能接近10秒。PostMapping(/import) public ResultString importEmployee(RequestParam(file) MultipartFile file) { ListEmployeeImportVO importList new ArrayList(); // 使用EasyExcel读取监听器里逐条处理 EasyExcel.read(file.getInputStream(), EmployeeImportVO.class, new AnalysisEventListenerEmployeeImportVO() { Override public void invoke(EmployeeImportVO data, AnalysisContext context) { // 校验并收集错误 importList.add(data); } Override public void doAfterAllAnalysed(AnalysisContext context) { } }).sheet().doRead(); return Result.success(成功导入 importList.size() 条数据); }3.5 文件上传下载模块人事系统里经常要上传员工证件照、劳动合同扫描件、离职证明之类的附件。Spring Boot处理文件上传本身很简单但要注意几个细节上传目录要配置成绝对路径不要用相对路径否则不同启动目录会导致文件路径错乱文件名要重命名用UUID加原始文件后缀避免中文文件名和重名覆盖问题上传接口要限制文件大小Spring Boot的spring.servlet.multipart.max-file-size和max-request-size两边都要配置下载文件时设置好Content-Disposition响应头否则文件名中文会乱码spring: servlet: multipart: max-file-size: 20MB max-request-size: 100MB文件存储路径建议按日期分目录比如/data/hr/upload/2024/06/01/uuid_xxx.pdf按月份归档方便后续定期清理和备份。4. 前端Vue实现与核心功能开发4.1 Vue项目初始化与常见配置前端我用Vue CLI脚手架初始化项目选型时勾选了Router、Vuex、axios相关依赖。初始化完成后第一步就是配置vue.config.js主要做两件事开发环境的代理转发和生产环境的打包配置。开发时前端跑在8080端口后端接口跑在9090端口如果前端直接请求后端接口会触发跨域。解决跨域最简单的方式就是让前端开发服务器做代理转发把/api前缀的请求转发到后端的9090端口。// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:9090, changeOrigin: true, pathRewrite: { ^/api: } } } }, productionSourceMap: false };这里有个非常容易踩的坑如果后端接口路径本身就是/api开头那pathRewrite就要留空或者不写否则请求会被二次处理导致404。我的做法是后端所有接口统一不加/api前缀前端代理层统一添加这样前端代码里请求路径就写/api/employee/list后端实际路径是/employee/list职责分离清晰。4.2 axios请求封装与token注入axios封装是前端项目的重中之重。我的做法是创建utils/request.js统一配置axios实例在请求拦截器里注入token在响应拦截器里处理统一错误码和token过期跳转登录。// utils/request.js import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: /api, timeout: 15000 }) // 请求拦截器注入token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器统一处理错误 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { // token过期清除本地凭证并跳转登录页 localStorage.removeItem(token) router.push(/login) } Message.error(error.message || 网络异常) return Promise.reject(error) } ) export default service统一封装的好处非常明显前端页面的业务代码只需要关注res数据本身不需要重复处理token和错误码判断。4.3 Vuex模块化管理用户状态前端状态管理我用Vuex按模块拆分store。用户模块user.js保存当前登录用户信息、角色、权限列表。登录成功后前端存好token的同时通过一个接口拉取用户信息和权限列表存入Vuex并写入本地缓存。权限列表在动态生成路由时要用到这里我采用的方式是后端返回当前用户可访问的菜单树前端先把所有页面路由定义为常量路由再根据权限列表动态router.addRoutes添加权限路由。这样普通员工登录后菜单和路由层面就看不到工资管理、用户管理这种管理模块。// store/modules/user.js const user { state: { token: localStorage.getItem(token) || , userInfo: {}, menus: [] }, mutations: { SET_TOKEN(state, token) { state.token token localStorage.setItem(token, token) }, SET_USERINFO(state, info) { state.userInfo info }, SET_MENUS(state, menus) { state.menus menus } }, actions: { async login({ commit }, loginForm) { const res await loginApi.login(loginForm) commit(SET_TOKEN, res.token) return res }, async getUserInfo({ commit }) { const res await userApi.getInfo() commit(SET_USERINFO, res.userInfo) commit(SET_MENUS, res.menus) return res } } }4.4 Element UI表格与表单的实战运用人事管理系统的前端页面80%以上都是表格加表单的结构。Element UI的el-table和el-form用好了开发效率提升非常明显。员工管理页面是典型的表格页。el-table结合后端分页接口绑定el-pagination组件切换页码和每页条数时重新请求接口。这里我封装了一个分页混入mixin公共的分页数据页码、每页条数、总条数和分页方法都写在混入里员工列表、考勤记录、工资列表等页面直接复用省了不少重复代码。表单页面要注意受控组件不要直接操作v-model绑定的深层次对象。我习惯在打开弹窗时先Object.assign({}, 默认值)创建一份新对象传给表单避免表单输入过程中直接污染了表格行的数据源导致取消弹窗后表格数据已经被改了。4.5 前端考勤打卡的实时交互考勤打卡模块需要实时交互员工点击打卡按钮后后端记录当前时间前端要反馈打卡状态。这里有个容易忽略的点HR希望考勤数据最终是可信的所以员工打卡依赖后端服务器时间而不是前端本地时间。前端只是把打卡请求发给后端由后端生成打卡时间否则员工把本地时间改一下就能规避迟到。前端打卡成功后清理一下当日打卡状态缓存重新拉取当天的打卡记录刷新页面。这个接口我用的是el-button的loading状态来防止重复提交用户连续点多次只会发送一次有效请求。5. 联调、部署与上线实战5.1 前后端联调的体验优化前端和后端各自开发完成后联调阶段往往是问题最多的时候。我在这个项目里总结了一些减少联调摩擦的经验。接口文档用Knife4j自动生成后端同学启动项目后直接访问/doc.html就能看到所有接口的请求参数和返回结构。前端同学根据文档先Mock数据开发页面等后端接口就绪后把Mock的URL切到真实地址。这避免了前后端互相等待的尴尬。联调时最常遇到的问题就是字段名对不上。后端返回createTime前端用了create_time后端返回嵌套对象前端期望的是展开的扁平结构。强烈建议后端在设计DTO返回结构时就把嵌套关系定好不要直接把Entity返给前端比如员工列表接口里前端需要显示部门名称如果返回的Entity里只有deptId前端就得额外调接口查部门列表来转换白白多一次请求。5.2 Spring Boot打包与部署细节Spring Boot项目的打包部署我用的方式是Maven打包成可执行Jar包然后直接用java -jar启动。打包前有几个配置要确认application.yml里的数据源地址要改成生产环境的数据库地址可以使用spring.profiles.active区分开发环境和生产环境打包时跳过测试避免单元测试影响上线流程mvn clean package -DskipTests启动脚本设置JVM参数给足内存java -Xms512m -Xmx1024m -jar hr-system.jar --spring.profiles.activeprodVue前端打包用npm run build生成到dist目录。静态资源和后端接口通过Nginx配置转发。server { listen 80; server_name hr.example.com; # 前端静态资源 location / { root /data/hr/frontend/dist; index index.html; try_files $uri $uri/ /index.html; # 解决vue-router history模式刷新404的问题 } # 后端接口代理 location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有个非常重要的细节Vue Router如果使用history模式刷新页面时Nginx如果找不到对应的静态资源就会报404所以try_files配置一定要加上/index.html回退。如果不想处理这个问题可以直接用hash模式但URL里会多个#不够美观。5.3 服务器环境初始化与安全加固服务器部署前我按照下面的清单逐项检查避免上线后出现低级问题检查项说明JDK版本确认服务器装的JDK版本与本地开发一致我用的是JDK 8MySQL数据库设置utf8mb4字符集关闭远程root登录单独创建应用账号防火墙端口只开放80/443端口后端9090端口只允许内网访问Nginx配置调整client_max_body_size否则上传大文件会提示413HTTPS证书有域名就配 Lets Encrypt 或云厂商免费证书保护传输数据说实话安全这个话题很多做开发的同学容易忽略但人事系统里的员工手机号、身份证号、工资信息都属于高度敏感数据尤其是工资数据一旦泄露会引发严重问题。我在这类系统上线前基本都会反复检查接口越是重要模块越要落实权限校验别想着先上线再补。6. 常见问题与排查技巧实录6.1 跨域问题排查指南前后端分离项目里跨域是最常见的问题尤其是刚接触Vue Spring Boot的同学简直被它折磨到怀疑人生。我的排查思路是分三层递进先看后端是否正确配置了跨域再看前端代理是否生效最后看请求是否真的打到了后端。Spring Boot后端全局配置跨域最简洁的方式是写一个配置类Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }如果后端已经配置了CorsFilter前端开发环境就不应该再通过代理转发该请求因为代理相当于在同源环境下请求CORS根本不会生效。这里有个小细节addAllowedOriginPattern(*)和setAllowCredentials(true)同时使用时要注意有的旧版本Spring Boot不兼容这种组合建议升级到2.7.x或换用具体的来源地址。6.2 JWT过期与刷新机制JWT是无状态的一旦签发就无法主动作废token过期时间设置太短用户会频繁被踢出登录设置太长又带来安全风险。这个项目中我把token有效期设为24小时web管理系统一般没有移动端那么频繁的会话要求24小时是相对平衡的选择。但实际使用中用户可能在快下班时登录第二天上午token就过期了。为了优化体验我增加了一个token自动刷新的机制每次前端发送请求时响应拦截器检查后端返回的响应头中是否携带了新的token如果带了就用新token替换本地的旧token。后端则在每次请求的过滤器里判断token剩余有效期是否存在5天内如果低于这个阈值就签发一个新token放到响应头。// JWT过滤器中的续期逻辑 int remainMinutes (int) ((expireDate.getTime() - System.currentTimeMillis()) / 60000); if (remainMinutes 5) { String newToken jwtUtil.generateToken(user); response.setHeader(refresh_token, newToken); }6.3 大文件上传失败的现场处理有次客户反馈上传合同PDF文件一直失败文件也就10MB左右。排查后发现是Nginx在转发请求时没有调整client_max_body_size默认值是1MB大文件请求直接被Nginx拦截了后端日志根本看不到请求进来。这类问题的排查思路是逐层确认请求到了哪一层。我习惯先用curl直接请求后端接口绕开Nginx如果curl能成功说明问题出在Nginx这层如果curl也失败再检查Spring Boot的multipart配置。那次最终解决的方案是在Nginx的server块里加上client_max_body_size 50m;同时确认了Spring Boot侧的max-file-size也调到了对应的限制。6.4 Vue项目构建后首屏加载优化Vue项目打包后体积偏大首屏加载白屏时间长是常见的体验问题。分析了下构建产物发现主要原因是所有页面打成了一个chunk再加Element UI全量引入体积奔着几MB去了。做了两个优化后首屏时间从5秒多降到了2秒内按需引入Element UI组件用babel-plugin-component插件实现使用路由懒加载每个页面组件单独打包成chunk访问时才加载对应JSNginx开启gzip压缩对JS/CSS文件压缩率能达到60%以上// 路由懒加载示例 const EmployeeManage () import(/views/employee/EmployeeManage.vue)这里还有个细节如果懒加载后的chunk数量很多要考虑浏览器并发加载限制的问题。如果项目页面特别多可以手动将关联性强的页面分到同一个chunk里比如把员工管理的列表页和详情页放在一个模块下。6.5 数据库连接池与慢查询调优系统上线运行一段时间后发现考勤统计模块的接口响应很慢特别是月底统计当月考勤汇总的时候能明显感觉到卡顿。排查后发现是SQL语句存在性能问题考勤统计时用了COUNT加GROUP BY的复杂查询在几万条考勤记录上全表扫描而且没有走索引。优化思路很简单在attendance表的employee_id和attendance_date上加联合索引。索引加好之后同样的查询从几百毫秒降到了几十毫秒。这个优化方案虽然基础但效果立竿见影。生产环境的数据库性能问题绝大多数是索引设计不到位导致的。在人事系统这种数据量不算特别大的场景下只要表里数据量不是百万级加好索引后性能基本不会有问题。如果数据量真的大起来了再考虑分表或者引入Elasticsearch但对于这个体量的系统没必要过度设计。写在最后一点个人经验这套springbootvue的企业人事管理系统做完前后花了大概一个半月时间。回过头看最耗精力的反而不是技术本身而是需求边界和数据结构的设计。比如HR说的“考勤”这两个字背后可能包含排班、调休、加班、请假、补卡、外勤打卡各种场景如果前期不把这些场景理清楚开发过程中反复改接口是必然的事。我的体会是做这类管理系统的核心能力不是写代码而是把业务概念翻译成数据结构的能力。如果你正在做类似的项目我建议不要急着写页面先把核心流程画一遍把涉及的表和字段定义清楚再动工写代码。代码部分用springbootvue这套组合真的没什么特别难的难的是对业务的理解业务模型一旦歪了后面每一步都是在错的地基上打补丁。这套技术栈很适合这种管理系统的原因也在这里它把你从重复的模板代码里解放出来让你有更多精力去思考业务本身。