SpringBoot+Vue 这套组合在 Java Web 毕业设计里,可以说是现象级的存在。但绝大多数同学的问题不是不会写代码,而是不知道一个“完整项目”到底该装进哪些东西:后端怎么写才能让代码看起来不像增删改查练习册?数据库脚本怎么设计才能体现“业务逻辑”?接口文档怎么组织才能让答辩老师觉得你真的懂分层架构?这篇我结合自己在某公司做的内部绩效量化管理系统,把这套从零到一拆清楚,顺便把源码结构、SQL设计、接口规范和部署踩坑全放在一起聊,给准备做毕设或者正在做企业级项目的同学一个能直接抄的路线图。1. 项目开篇与整体画像1.1 为什么选“绩效量化”作为业务场景企业内部人员绩效量化管理系统,核心不在“管理”两个字,而在“量化”。做员工信息增删改查的系统满大街都是,但把考勤、KPI、360度互评、项目贡献这些碎片化数据统一折算成分数、排名、等级和趋势图,这就把 CRUD 变成了有逻辑的评分体系。毕设选题如果想体现业务理解能力,这是性价比最高的方向之一。另一个很实际的原因是,这个场景功能边界清晰,可以横向拆模块,也可以纵向做深。横向有组织架构、员工档案、考核周期、指标配置、评分录入、结果审核、报表导出一整套;纵向可以把权重算法、等级规则、部门排名做到有细节可讲。你不需要凭空发明复杂规则,业务逻辑本身就足够撑起前端十几个页面和后端几十个接口。我之前做这个项目是在一家做供应链的某公司,出发点很朴素:HR 还在用 Excel 收集各部门打分表,月底汇总全靠人手算。做得过程中我发现,绩效量化系统的难点不是写接口,而是把人力资源的评分逻辑转成数据结构。比如 KPI 结果怎么跟权重挂钩、部门负责人打分和同事互评怎么合并、最终等级是强制分布还是分数线划分,这些都需要在数据库和代码层面给出可落地的方案。这才是真正能写进毕业论文或者项目文档里的东西。1.2 技术选型背后的取舍逻辑主框架不用犹豫,SpringBoot + Vue 就是当前 Java Web 毕业设计的最优解。SpringBoot 解决的是后端“配置地狱”问题,内嵌 Tomcat 之后打包成 jar 直接跑,省掉一堆 XML 配置,把精力留给业务代码。Vue 配合 Element UI 或者 Vue Element Admin,页面组件现成,前端开发速度非常快。做出来的东西界面干净,答辩演示不丢分。选型上我有几个建议,都是实际项目里验证过的:后端基础组件用 SpringBoot 2.x + MyBatis-Plus,避免在通用 mapper 上浪费时间权限用 JWT,不做 Session,前后端分离部署的时候少很多麻烦数据库用 MySQL 5.7 或 8.0,表结构尽量做第三范式,但评分结果表可以适当冗余前端用 Vue2 + Element UI,稳定性最好,网上踩坑文章最多,遇到问题搜得到接口调试用 Apifox,生成文档的同事后端调试、前端联调、答辩演示都能用这里很多同学会纠结要不要用 Spring Cloud 微服务,或者加一个 Redis 缓存,甚至把前端换成 Vue3 + TypeScript。我的态度很明确:毕业设计不是追求技术栈新,而是追求逻辑自洽、功能完整、能现场跑通。分布式和微服务在单机部署下反而会掩盖你核心业务逻辑的亮点。项目名写明“管理系统平台”,本质上是一个单体应用,做好模块化、分层清晰、权限完备,比强行引入中间件更有说服力。2. 核心功能设计与数据库架构2.1 功能模块划分与用户角色矩阵整个系统我拆成了六大模块:系统管理、组织管理、员工管理、绩效指标、考核执行、结果统计。六个模块听起来多,但每个模块的功能边界非常清晰。系统管理管的是用户、角色、菜单和操作日志,属于所有管理系统的底座。组织管理维护公司层级和部门架构,员工接口的员工档案必须挂在部门节点下。绩效指标模块是整个系统和普通增删改查系统的分水岭,这里维护考核周期、指标库、权重配置。考核执行是核心流程,包括评分任务发布、各角色打分、结果确认。结果统计负责把分数汇总成排名、等级和图表报表。角色上我设计了四类:系统管理员、HR专员、部门负责人、普通员工。四种角色对应四种真实的业务视角。管理员维护基础数据;HR 创建考核周期并发布任务;部门负责人负责本部门打分确认和结果复核;普通员工能做360度互评也可以看自己的绩效结果。这个矩阵一旦定义好,后面的前端路由动态生成和接口鉴权就都有了依据。功能模块管理员HR专员部门负责人普通员工系统管理全部查看日志无无组织管理全部全部查看本部门查看本部门员工管理全部维护档案查看本部门查看个人绩效指标配置权重创建周期查看标准查看标准考核执行全局监控发布任务部门打分同事互评结果统计全部全部本部门结果个人结果2.2 数据库核心表设计与SQL脚本要点数据库设计是整个项目的底气。我当时手写了大概十六张表,最关键的是下面这几张:sys_user(用户)、sys_role(角色)、sys_menu(菜单)、dept(部门)、employee(员工档案)、assessment_cycle(考核周期)、indicator(指标库)、indicator_weight(指标权重)、score_record(评分记录)、performance_result(绩效结果)。sys_user 表要挂在 employee 表上,因为登录的是用户、考核的是员工,两者存在一对一关系。employee 表要冗余存 dept_id 和 dept_name,这样查列表的时候少做一次关联,虽然违反了严格的范式约束,但是列表接口性能更好。assessment_cycle 表是整个考核流程的驱动器,字段必须有周期名称、开始时间、结束时间、状态。状态机我用了四个值:未开始、进行中、待确认、已完成。HR 创建周期后,状态流转决定了评分接口是否开放,这个逻辑在代码里用状态判断控制,避免有人绕过前端直接调接口。score_record 表是整个系统里最核心的流水表。每条记录包含评分人、被评人、考核周期ID、指标ID、分数、评语、评分类型。评分类型区分自评、上级评分、同事互评。这张表只做插入和查询,不做更新,一旦提交就不可修改,需要修改必须走申诉流程,这样才能保证绩效数据的公信力。performance_result 表存的是最终汇总结果,包含综合分、等级、排名等冗余字段。这个表的价值在于把复杂的汇总计算固化成快照,报表页面查数据的时候不需要现场跑计算,几十万条历史数据也不会拖慢页面。SQL 脚本要注意的设置项包括:所有关键表都加 create_time 和 update_time 字段,统一用 datetime 类型;删除一律用逻辑删除,加 deleted 字段,避免物理删数据导致考核记录断链;金额类字段用 decimal,分数用 decimal(5,2),用户名和部门名统一 varchar(50),超过50个字符容易在表单校验时报错。2.3 权重算法与绩效量化规则量化的灵魂在于算法,这部分我在项目里设计了一套比较容易讲解的规则。综合分公式是:综合分 = 上级评分×40% + 自评分×20% + 同事互评平均分×40%。每个一级指标下还有若干二级指标,二级指标的分数由评分人在1到100分之间给出,最终每个一级指标得分 = Σ(二级指标分数×指标权重)。权重的设置必须是HR在前端页面动态配置的,而不是写死在代码里。这就是为什么要有 indicator_weight 表,它按考核周期和部门维度存权重。同一个指标,业务部门可能权重高一些,职能部门可能权重低一些,如果权重写死在代码里,系统根本没法落地。等级的规则我设计为:A(优秀)占比不超过20%,B(良好)占比不超过50%,C(合格)占比不低于25%,D(待改进)占比不低于5%,具体定档线由HR在每个考核周期单独设置。这一步是为了防止所有部门都给高分,也就是常见的“评分通胀”问题。答辩的时候对方一定会问“这个等级比例怎么来的”,你回答“参考了业界比较通用的强制分布法,同时允许超参配置,让不同企业灵活调整”,这句话能明显体现出业务思考深度。3. 后端系统实现与接口体系3.1 SpringBoot 工程结构与分层实现细节后端工程我按标准的领域分包来做,分为 controller、service、mapper、entity、dto、common 几层。如果你想让代码显得更“项目级”,可以再加一个 config 包放安全配置和跨域配置。com.company.perf ├── controller # 接口层,只做参数接收和结果封装 ├── service # 业务逻辑层,所有判定和计算都放这里 ├── mapper # MyBatis-Plus 的 Mapper 接口 ├── entity # 数据库实体 ├── dto # 前端传参和返回数据的封装对象 ├── common # 统一返回结果、异常处理、常量 ├── config # JWT、跨域、MyBatis 配置 └── job # 定时任务,处理周期状态自动更新分层最重要的规矩是:controller 不写业务代码。我见过太多同学把查询和计算逻辑直接写在 controller 里,代码看起来确实能跑,但一旦你要写单元测试、复用逻辑,或者答辩时被问“你这个项目怎么体现设计模式”,基本无法招架。我在工期紧张的时候试过前几次偷懒,后来加功能改接口时才发现,不分层就是给自己挖坑。控制器统一通过 ResultData 对象返回结构,格式为 code、message、data。前端拿到 code 为 200 就解析 data,否则弹出 message。这个包裹类必须在 common 包定义,不要每个 controller 自己定义返回类。异常处理使用 @RestControllerAdvice 配合自定义 BizException,服务里判断条件不满足时直接抛出,由统一处理器转成对应 JSON 返回。3.2 登录鉴权与权限控制登录接口我用 JWT 实现。用户输入账号密码后,后端校验通过,生成一个有效期为2小时的 token,里面包含用户ID、角色标识。前端拿到 token 后存在 localStorage,并在 Axios 拦截器里加上 Authorization 头。后端拦截器校验 token 的流程是这样的:拦截所有 /api/** 请求,排除掉 /login、/captcha。如果 token 缺失或过期,直接返回 401。校验通过后,把用户信息放入 ThreadLocal 的 UserContext,后续代码通过 UserContext.getUserId() 获取当前操作用户,不需要每个接口手动传用户ID。角色权限我用的是简单而有效的地址规则匹配。管理员前缀 /admin/,HR 前缀 /hr/,普通员工的接口不带前缀,只通过 @PreAuthorize 注解做方法级校验。做毕业设计完全不需要引入 Spring Security 全家桶,一个拦截器加一个自定义注解就能说清楚权限控制原理。引入太重的东西反而容易把自己绕晕。这里提醒一个容易踩的坑:跨域。如果你前端跑在 localhost:8080,后端跑在 localhost:9090,必须配置 CorsFilter。通常建议允许来源设为 http://localhost:8080,但不能设为 *,否则携带 Authorization 头时会出问题。这个坑我调了很久才发现。3.3 绩效评分接口与汇总计算的实现评分接口是考核执行模块的核心,设计时做了三点约束。第一,提交前校验当前周期状态必须是“进行中”。第二,校验评分人和被评人不能在同一个周期内重复评分,防止一个人换着身份刷分。第三,打分范围必须是配置好的最小值和最大值,超出直接报错。汇总计算我用了一个定时任务入口,也支持管理员手动触发重新计算。计算方法:查询该周期所有 score_record,按被评人、评分类型分组,然后套用权重公式。计算完成后写入 performance_result 表,同时更新一条冗余的综合分和排名。这段汇总逻辑我贴一个简化的写法,方便理解计算过程:public void calcCycleResult(Long cycleId) { List<ScoreRecord> records = scoreRecordMapper.selectList( new LambdaQueryWrapper<ScoreRecord>() .eq(ScoreRecord::getCycleId, cycleId)); Map<Long, List<ScoreRecord>> grouped = records.stream() .collect(Collectors.groupingBy(ScoreRecord::getEmployeeId)); for (Map.Entry<Long, List<ScoreRecord>> entry : grouped.entrySet()) { Long employeeId = entry.getKey(); List<ScoreRecord> list = entry.getValue(); double managerScore = avgByType(list, "MANAGER"); double selfScore = avgByType(list, "SELF"); double peerScore = avgByType(list, "PEER"); double total = managerScore * 0.4 + selfScore * 0.2 + peerScore * 0.4; performanceResultMapper.insert( new PerformanceResult(employeeId, cycleId, total, genRank(total))); } }3.4 接口文档设计与 Apifox 管理这个项目我建议从开发第一天就用 Apifox 管理接口。后端的每个接口写完立即在 Apifox 里调试,效果是接口文档实时生成,前端同学能通过在线文档看到每个字段的含义和示例。对于毕设答辩来说,一份结构完整的接口文档比多说两页 PPT 更有说服力。文档中要包含全部核心接口,我数了一下大概有40来个。按模块分好类:登录认证、员工管理、部门管理、指标配置、周期管理、评分提交、结果查询、报表导出。每个接口必须包含请求方式、URL、请求参数、响应示例、错误码说明。接口命名我采用 RESTful 风格:/api/hr/cycle/create、/api/hr/employee/page、/api/manager/score/submit。不用动词滥用的做法,比如 /api/addEmployee /api/deleteEmployee 这种看起来外行的写法则要避免。另外建议在接口文档里标明每个接口的权限角色,这样答辩时老师问“不同角色能调哪些接口”,你能展示文档并说明角色矩阵如何落实到了接口粒度。4. 前端系统实现与交互设计4.1 Vue 工程搭建与核心依赖清单前端我用 Vue CLI 创建工程,选型是 Vue2 + Vue Router + Vuex + Axios + Element UI。这套依赖组合在管理平台方向成熟度极高,无论上手难度还是社区资料丰富度都排第一。前端代码结构我真的建议按模块和页面双层组织,而不是所有组件平铺乱放。src ├── api # 接口请求封装,按模块拆文件 ├── assets # 静态资源 ├── components # 公共组件 ├── layout # 框架布局,侧边栏、顶栏、面包屑 ├── router # 路由配置,包含动态路由逻辑 ├── store # 状态管理 ├── utils # request封装、token处理、校验工具 └── views # 页面,按功能模块建子目录前端工程结构在很大程度上影响你代码的可读性。很多人写前端就是每个页面复制一个组件,然后页面间直接调用 axios,看起来每个文件都能跑,但整体阅读混乱。在答辩演示时一旦被问“另一个页面在哪里”,你打开目录结构就能直观答出逻辑,这个加分项不需要额外解释。4.2 登录流程与动态路由权限登录页的处理流程:用户输入账号密码,调用 /login 接口,成功后把 token 存到 localStorage,同时获取用户信息和角色信息存到 Vuex。然后根据角色动态生成可访问的菜单和路由。动态路由的实现思路不复杂。路由表先分成两部分:公共路由和权限路由。权限路由定义好后,根据角色标识过滤出对应菜单,用 router.addRoutes 动态挂载。菜单栏数据同样从 store 中获取,由路由数据生成。前端权限这里核心注意点是:不能只依赖隐藏按钮来控制权限。也就是说,就算前端根据角色隐藏了某个按钮,用户还是可以手工输入地址访问页面,所以后端接口必须做二次鉴权。我在实际开发中就是这样实现双向保障的,前端为了用户体验、后端为了真正的数据安全。4.3 核心页面的实现逻辑与展示管理后台很重要的页面包括:仪表盘首页、考核周期列表、指标配置页、评分页、结果分析页。仪表盘首页应该放上员工总数、正在进行的考核数、待确认结果数、近六个月的绩效趋势图。这些数据用一个聚合接口 /api/dashboard/data 返回,前端用 ECharts 渲染图表。评分页是最需要打磨交互的页面。员工点进去,先看到一个需要自己评分的人员列表,每个人员点开后展示对应的一组指标,每个指标后面是一个数字输入框,底部是提交按钮。评分页要处理的核心交互有三个:未填完所有指标不能提交;提交前有二次确认弹窗;提交成功后立即刷新列表并把此人置为“已评分”状态。结果分析页对我展示系统的价值很大。通过筛选考核周期,可以查看部门排名表、等级分布饼图、个人趋势折线图。这张页面直观展示了“绩效量化”这四个字的最终效果,答辩演示时,评委看到图表比看到一堆数据表的印象要直观得多。导出功能我建议用前端把表格数据直接导出成 Excel。用 SheetJS 库,比后端导出更简单,也省去下载文件的接口。4.4 接口联调、响应拦截与状态码处理前端 Axios 封装上有几个细节值得做。第一,request 拦截器统一添加 token,不必每个接口重复写。第二,response 拦截器判断 code 码,非 200 直接弹 Message 提示,401 则跳转登录页,同时清除本地 token。第三,所有请求都用 async/await + try/catch 包裹,避免出现接口报错导致页面白屏。页面数据加载时通过 v-loading 指令控制 loading 状态,这个是 Element UI 自带功能,不需要额外写代码。联调过程中我最大的体会是前后端字段一定要在接口文档里达成一致。我早期因为时间紧张,后端返回的字段名叫 deptName,前端页面用 departmentName,结果页面上永远显示空白,排查了大半天。跟后台的同学定接口时,要用 Apifox 在线 Mock 数据,前端等不到后端时可以用 Mock 数据跑版式,后端就绪后一键切到真实环境。5. 开发实战问题排查与经验总结5.1 高频运行异常与解决速查表这个项目由于代码量不小,我在开发过程中前后处理过蛮多问题,有些是调试环境引起的,有些是自己粗心,下面这张排查表是实用度最高的。现象可能原因解决方式前端请求接口 CORS 报错后端未配置跨域或允许来源不对配置 CorsFilter,AllowedOrigin 写前端完整地址token 过了两小时失效JWT 过期时间设置太短把过期时间调成8小时,答辩演示时更稳妥日期字段返回乱码JSON 序列化格式不对统一配置 Jackson 日期格式 yyyy-MM-dd HH:mm:ss评分提交后数据没变化状态机判断不通过或事务未生效检查 Service 方法上是否有 @Transactional页面菜单不显示路由加规则后未刷新或 store 没存调用一次 router.addRoutes 后重新跳转部门下有员工无法删除外键约束或逻辑删除判断缺失删除部门前先检查员工表 dept_id 引用前端上传 Excel 导入失败导入模板字段不匹配下载导入模板用相同的列名,不要自己另建模板数据权限没生效拦截器未把用户角色写入上下文检查 UserContext 是否在过滤器中正确赋值5.2 编写过程需要注意的设计规范我在这个项目里最想提醒的一条经验是:核心业务表一定要留逻辑删除标记,尤其是 score_record 表和 performance_result 表。绩效数据如果物理删除了,后续要追溯就完全没有痕迹。我在做季度复核回调时,因为一个月前误删了一批测试数据,导致某个部门的结果重新计算后与历史报表对不上,还得回数据库手工补数据。所以请一定给这两张表加 deleted 字段。接口层要做好参数校验统一处理。用 javax.validation 注解给 DTO 字段加上 @NotBlank、@NotNull、@Max、@Min,由全局异常处理捕获 MethodArgumentNotValidException 并返回带字段名的错误提示,不要每个接口内部手动 if (xxx == null) return error。这样写虽然代码有点啰嗦,但项目整体更规范,答辩项目文档的代码可读性会好很多。状态机更新要统一写在一个 Service 方法里。考核周期状态从前端传过来更新是很危险的设计,应当提供专门的方法,例如 publishCycle、confirmResult、completeCycle,内部校验当前状态并更新。这样既保证数据安全,也让接口语义更清晰。在答辩问答环节,如果评委问“分数提交后是否可以撤回”,你可以明确回答“已提交的评分只能走申诉流程由管理员更新状态后进入待审核列表,评分人不能直接改分数”,这个业务设计能提升整个系统有公信力。5.3 项目打包部署与演示环境准备部署环节不需要太复杂,但必须有完整可演示的环境。后端执行 mvn clean package -DskipTests 打包成 jar,在服务器上用 nohup java -jar xxx.jar --spring.profiles.active=prod 运行。前端执行 npm run build 生成 dist 目录,交给 Nginx 托管,同时配置反向代理把 /api 请求转发到后端服务。Nginx 配置要点:server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署时数据库的初始化脚本用项目里的 init.sql 一键执行,默认管理员账号 admin 密码建议初始化为 123456,并在首次登录时强制提示修改密码。演示前把几套不同角色、不同部门的测试数据准备足,比如某部门负责人账号和普通员工账号,方便现场演示数据权限隔离。数据库脚本要在项目内附上,并且注明了创建表和初始化数据放在一起,数据覆盖一个考核周期的完整数据。6. 从毕设到项目落地的迭代思考在系统上线并实际跑完一个季度考核之后,我反而重新思考了技术实现和业务落地的差距。系统的核心功劳不是视觉效果,而是把原本晦涩的绩效数据变成了有记录、可追溯、可申诉的流程资产。有很多内部从最初怀疑这种线上评分,到季度末等待报告时的正常查看,这本身是我认为比较成功的地方。一个值得留意的方向是绩效结果的数据可视化越来越重要。除了 ECharts 做的常规柱状图饼图外,后续我打算加上部门横向对比的热力图和员工绩效趋势预测线。绩效数据的价值不在于一次考核的结果,而在于长期累积后能看到个人或团队的真实成长曲线,这个能力一旦实现,系统就从工具变成了决策辅助。另一个可扩充点是绩效申诉和面谈记录流程。在真实管理实践中,员工对绩效结果有异议是很常见的,如果系统只允许结果发布而不提供申诉入口,反而会影响系统能的正常开展。可以考虑加入申诉申请、HR 复核、线下沟通记录三条流程。这一块虽然业务逻辑不难,但确实能体现对业务场景有进一步理解。权限精细化也是值得深入的方向。现阶段数据权限只做到部门级,后续可以扩展到项目组级、岗位级别以及字段级权限。比如某些敏感指标只有系统管理员能看到详细分数,部门负责人只能看到汇总分布,这个需求在企业级系统中非常常见,把这个问题在论文的研究与展望里写清楚,贴合真实应用场景。前端技术栈方面,如果之后有时间,我会迁移到 Vue3 + TypeScript 并搭配自动生成 API 类型定义的方案。但这属于锦上添花,不是当前阶段的必要内容。毕业设计阶段核心是把业务逻辑跑通、把文档写好、把部署流程熟练,技术栈的新旧一般不会成为主要讨论点。最后一句话是我这段时间最大的体会:做项目不要急着敲代码,先把业务规则、数据流向、角色矩阵画清楚。绩效量化系统这类业务逻辑较重的项目,真正耗时的地方不在写接口,而在想明白谁在什么时间对谁提交什么分数、分数如何校验、结果如何汇总,这些理顺了以后,SpringBoot 和 Vue 只是执行工具而已。
SpringBoot+Vue 这套组合在 Java Web 毕业设计里,可以说是现象级的存在。但绝大多数同学的问题不是不会写代码,而是不知道一个“完整项目”到底该装进哪些东西:后端怎么写才能让代码看起来不像增删改查练习册?数据库脚本怎么设计才能体现“业务逻辑”?接口文档怎么组织才能让答辩老师觉得你真的懂分层架构?这篇我结合自己在某公司做的内部绩效量化管理系统,把这套从零到一拆清楚,顺便把源码结构、SQL设计、接口规范和部署踩坑全放在一起聊,给准备做毕设或者正在做企业级项目的同学一个能直接抄的路线图。1. 项目开篇与整体画像1.1 为什么选“绩效量化”作为业务场景企业内部人员绩效量化管理系统,核心不在“管理”两个字,而在“量化”。做员工信息增删改查的系统满大街都是,但把考勤、KPI、360度互评、项目贡献这些碎片化数据统一折算成分数、排名、等级和趋势图,这就把 CRUD 变成了有逻辑的评分体系。毕设选题如果想体现业务理解能力,这是性价比最高的方向之一。另一个很实际的原因是,这个场景功能边界清晰,可以横向拆模块,也可以纵向做深。横向有组织架构、员工档案、考核周期、指标配置、评分录入、结果审核、报表导出一整套;纵向可以把权重算法、等级规则、部门排名做到有细节可讲。你不需要凭空发明复杂规则,业务逻辑本身就足够撑起前端十几个页面和后端几十个接口。我之前做这个项目是在一家做供应链的某公司,出发点很朴素:HR 还在用 Excel 收集各部门打分表,月底汇总全靠人手算。做得过程中我发现,绩效量化系统的难点不是写接口,而是把人力资源的评分逻辑转成数据结构。比如 KPI 结果怎么跟权重挂钩、部门负责人打分和同事互评怎么合并、最终等级是强制分布还是分数线划分,这些都需要在数据库和代码层面给出可落地的方案。这才是真正能写进毕业论文或者项目文档里的东西。1.2 技术选型背后的取舍逻辑主框架不用犹豫,SpringBoot + Vue 就是当前 Java Web 毕业设计的最优解。SpringBoot 解决的是后端“配置地狱”问题,内嵌 Tomcat 之后打包成 jar 直接跑,省掉一堆 XML 配置,把精力留给业务代码。Vue 配合 Element UI 或者 Vue Element Admin,页面组件现成,前端开发速度非常快。做出来的东西界面干净,答辩演示不丢分。选型上我有几个建议,都是实际项目里验证过的:后端基础组件用 SpringBoot 2.x + MyBatis-Plus,避免在通用 mapper 上浪费时间权限用 JWT,不做 Session,前后端分离部署的时候少很多麻烦数据库用 MySQL 5.7 或 8.0,表结构尽量做第三范式,但评分结果表可以适当冗余前端用 Vue2 + Element UI,稳定性最好,网上踩坑文章最多,遇到问题搜得到接口调试用 Apifox,生成文档的同事后端调试、前端联调、答辩演示都能用这里很多同学会纠结要不要用 Spring Cloud 微服务,或者加一个 Redis 缓存,甚至把前端换成 Vue3 + TypeScript。我的态度很明确:毕业设计不是追求技术栈新,而是追求逻辑自洽、功能完整、能现场跑通。分布式和微服务在单机部署下反而会掩盖你核心业务逻辑的亮点。项目名写明“管理系统平台”,本质上是一个单体应用,做好模块化、分层清晰、权限完备,比强行引入中间件更有说服力。2. 核心功能设计与数据库架构2.1 功能模块划分与用户角色矩阵整个系统我拆成了六大模块:系统管理、组织管理、员工管理、绩效指标、考核执行、结果统计。六个模块听起来多,但每个模块的功能边界非常清晰。系统管理管的是用户、角色、菜单和操作日志,属于所有管理系统的底座。组织管理维护公司层级和部门架构,员工接口的员工档案必须挂在部门节点下。绩效指标模块是整个系统和普通增删改查系统的分水岭,这里维护考核周期、指标库、权重配置。考核执行是核心流程,包括评分任务发布、各角色打分、结果确认。结果统计负责把分数汇总成排名、等级和图表报表。角色上我设计了四类:系统管理员、HR专员、部门负责人、普通员工。四种角色对应四种真实的业务视角。管理员维护基础数据;HR 创建考核周期并发布任务;部门负责人负责本部门打分确认和结果复核;普通员工能做360度互评也可以看自己的绩效结果。这个矩阵一旦定义好,后面的前端路由动态生成和接口鉴权就都有了依据。功能模块管理员HR专员部门负责人普通员工系统管理全部查看日志无无组织管理全部全部查看本部门查看本部门员工管理全部维护档案查看本部门查看个人绩效指标配置权重创建周期查看标准查看标准考核执行全局监控发布任务部门打分同事互评结果统计全部全部本部门结果个人结果2.2 数据库核心表设计与SQL脚本要点数据库设计是整个项目的底气。我当时手写了大概十六张表,最关键的是下面这几张:sys_user(用户)、sys_role(角色)、sys_menu(菜单)、dept(部门)、employee(员工档案)、assessment_cycle(考核周期)、indicator(指标库)、indicator_weight(指标权重)、score_record(评分记录)、performance_result(绩效结果)。sys_user 表要挂在 employee 表上,因为登录的是用户、考核的是员工,两者存在一对一关系。employee 表要冗余存 dept_id 和 dept_name,这样查列表的时候少做一次关联,虽然违反了严格的范式约束,但是列表接口性能更好。assessment_cycle 表是整个考核流程的驱动器,字段必须有周期名称、开始时间、结束时间、状态。状态机我用了四个值:未开始、进行中、待确认、已完成。HR 创建周期后,状态流转决定了评分接口是否开放,这个逻辑在代码里用状态判断控制,避免有人绕过前端直接调接口。score_record 表是整个系统里最核心的流水表。每条记录包含评分人、被评人、考核周期ID、指标ID、分数、评语、评分类型。评分类型区分自评、上级评分、同事互评。这张表只做插入和查询,不做更新,一旦提交就不可修改,需要修改必须走申诉流程,这样才能保证绩效数据的公信力。performance_result 表存的是最终汇总结果,包含综合分、等级、排名等冗余字段。这个表的价值在于把复杂的汇总计算固化成快照,报表页面查数据的时候不需要现场跑计算,几十万条历史数据也不会拖慢页面。SQL 脚本要注意的设置项包括:所有关键表都加 create_time 和 update_time 字段,统一用 datetime 类型;删除一律用逻辑删除,加 deleted 字段,避免物理删数据导致考核记录断链;金额类字段用 decimal,分数用 decimal(5,2),用户名和部门名统一 varchar(50),超过50个字符容易在表单校验时报错。2.3 权重算法与绩效量化规则量化的灵魂在于算法,这部分我在项目里设计了一套比较容易讲解的规则。综合分公式是:综合分 = 上级评分×40% + 自评分×20% + 同事互评平均分×40%。每个一级指标下还有若干二级指标,二级指标的分数由评分人在1到100分之间给出,最终每个一级指标得分 = Σ(二级指标分数×指标权重)。权重的设置必须是HR在前端页面动态配置的,而不是写死在代码里。这就是为什么要有 indicator_weight 表,它按考核周期和部门维度存权重。同一个指标,业务部门可能权重高一些,职能部门可能权重低一些,如果权重写死在代码里,系统根本没法落地。等级的规则我设计为:A(优秀)占比不超过20%,B(良好)占比不超过50%,C(合格)占比不低于25%,D(待改进)占比不低于5%,具体定档线由HR在每个考核周期单独设置。这一步是为了防止所有部门都给高分,也就是常见的“评分通胀”问题。答辩的时候对方一定会问“这个等级比例怎么来的”,你回答“参考了业界比较通用的强制分布法,同时允许超参配置,让不同企业灵活调整”,这句话能明显体现出业务思考深度。3. 后端系统实现与接口体系3.1 SpringBoot 工程结构与分层实现细节后端工程我按标准的领域分包来做,分为 controller、service、mapper、entity、dto、common 几层。如果你想让代码显得更“项目级”,可以再加一个 config 包放安全配置和跨域配置。com.company.perf ├── controller # 接口层,只做参数接收和结果封装 ├── service # 业务逻辑层,所有判定和计算都放这里 ├── mapper # MyBatis-Plus 的 Mapper 接口 ├── entity # 数据库实体 ├── dto # 前端传参和返回数据的封装对象 ├── common # 统一返回结果、异常处理、常量 ├── config # JWT、跨域、MyBatis 配置 └── job # 定时任务,处理周期状态自动更新分层最重要的规矩是:controller 不写业务代码。我见过太多同学把查询和计算逻辑直接写在 controller 里,代码看起来确实能跑,但一旦你要写单元测试、复用逻辑,或者答辩时被问“你这个项目怎么体现设计模式”,基本无法招架。我在工期紧张的时候试过前几次偷懒,后来加功能改接口时才发现,不分层就是给自己挖坑。控制器统一通过 ResultData 对象返回结构,格式为 code、message、data。前端拿到 code 为 200 就解析 data,否则弹出 message。这个包裹类必须在 common 包定义,不要每个 controller 自己定义返回类。异常处理使用 @RestControllerAdvice 配合自定义 BizException,服务里判断条件不满足时直接抛出,由统一处理器转成对应 JSON 返回。3.2 登录鉴权与权限控制登录接口我用 JWT 实现。用户输入账号密码后,后端校验通过,生成一个有效期为2小时的 token,里面包含用户ID、角色标识。前端拿到 token 后存在 localStorage,并在 Axios 拦截器里加上 Authorization 头。后端拦截器校验 token 的流程是这样的:拦截所有 /api/** 请求,排除掉 /login、/captcha。如果 token 缺失或过期,直接返回 401。校验通过后,把用户信息放入 ThreadLocal 的 UserContext,后续代码通过 UserContext.getUserId() 获取当前操作用户,不需要每个接口手动传用户ID。角色权限我用的是简单而有效的地址规则匹配。管理员前缀 /admin/,HR 前缀 /hr/,普通员工的接口不带前缀,只通过 @PreAuthorize 注解做方法级校验。做毕业设计完全不需要引入 Spring Security 全家桶,一个拦截器加一个自定义注解就能说清楚权限控制原理。引入太重的东西反而容易把自己绕晕。这里提醒一个容易踩的坑:跨域。如果你前端跑在 localhost:8080,后端跑在 localhost:9090,必须配置 CorsFilter。通常建议允许来源设为 http://localhost:8080,但不能设为 *,否则携带 Authorization 头时会出问题。这个坑我调了很久才发现。3.3 绩效评分接口与汇总计算的实现评分接口是考核执行模块的核心,设计时做了三点约束。第一,提交前校验当前周期状态必须是“进行中”。第二,校验评分人和被评人不能在同一个周期内重复评分,防止一个人换着身份刷分。第三,打分范围必须是配置好的最小值和最大值,超出直接报错。汇总计算我用了一个定时任务入口,也支持管理员手动触发重新计算。计算方法:查询该周期所有 score_record,按被评人、评分类型分组,然后套用权重公式。计算完成后写入 performance_result 表,同时更新一条冗余的综合分和排名。这段汇总逻辑我贴一个简化的写法,方便理解计算过程:public void calcCycleResult(Long cycleId) { List<ScoreRecord> records = scoreRecordMapper.selectList( new LambdaQueryWrapper<ScoreRecord>() .eq(ScoreRecord::getCycleId, cycleId)); Map<Long, List<ScoreRecord>> grouped = records.stream() .collect(Collectors.groupingBy(ScoreRecord::getEmployeeId)); for (Map.Entry<Long, List<ScoreRecord>> entry : grouped.entrySet()) { Long employeeId = entry.getKey(); List<ScoreRecord> list = entry.getValue(); double managerScore = avgByType(list, "MANAGER"); double selfScore = avgByType(list, "SELF"); double peerScore = avgByType(list, "PEER"); double total = managerScore * 0.4 + selfScore * 0.2 + peerScore * 0.4; performanceResultMapper.insert( new PerformanceResult(employeeId, cycleId, total, genRank(total))); } }3.4 接口文档设计与 Apifox 管理这个项目我建议从开发第一天就用 Apifox 管理接口。后端的每个接口写完立即在 Apifox 里调试,效果是接口文档实时生成,前端同学能通过在线文档看到每个字段的含义和示例。对于毕设答辩来说,一份结构完整的接口文档比多说两页 PPT 更有说服力。文档中要包含全部核心接口,我数了一下大概有40来个。按模块分好类:登录认证、员工管理、部门管理、指标配置、周期管理、评分提交、结果查询、报表导出。每个接口必须包含请求方式、URL、请求参数、响应示例、错误码说明。接口命名我采用 RESTful 风格:/api/hr/cycle/create、/api/hr/employee/page、/api/manager/score/submit。不用动词滥用的做法,比如 /api/addEmployee /api/deleteEmployee 这种看起来外行的写法则要避免。另外建议在接口文档里标明每个接口的权限角色,这样答辩时老师问“不同角色能调哪些接口”,你能展示文档并说明角色矩阵如何落实到了接口粒度。4. 前端系统实现与交互设计4.1 Vue 工程搭建与核心依赖清单前端我用 Vue CLI 创建工程,选型是 Vue2 + Vue Router + Vuex + Axios + Element UI。这套依赖组合在管理平台方向成熟度极高,无论上手难度还是社区资料丰富度都排第一。前端代码结构我真的建议按模块和页面双层组织,而不是所有组件平铺乱放。src ├── api # 接口请求封装,按模块拆文件 ├── assets # 静态资源 ├── components # 公共组件 ├── layout # 框架布局,侧边栏、顶栏、面包屑 ├── router # 路由配置,包含动态路由逻辑 ├── store # 状态管理 ├── utils # request封装、token处理、校验工具 └── views # 页面,按功能模块建子目录前端工程结构在很大程度上影响你代码的可读性。很多人写前端就是每个页面复制一个组件,然后页面间直接调用 axios,看起来每个文件都能跑,但整体阅读混乱。在答辩演示时一旦被问“另一个页面在哪里”,你打开目录结构就能直观答出逻辑,这个加分项不需要额外解释。4.2 登录流程与动态路由权限登录页的处理流程:用户输入账号密码,调用 /login 接口,成功后把 token 存到 localStorage,同时获取用户信息和角色信息存到 Vuex。然后根据角色动态生成可访问的菜单和路由。动态路由的实现思路不复杂。路由表先分成两部分:公共路由和权限路由。权限路由定义好后,根据角色标识过滤出对应菜单,用 router.addRoutes 动态挂载。菜单栏数据同样从 store 中获取,由路由数据生成。前端权限这里核心注意点是:不能只依赖隐藏按钮来控制权限。也就是说,就算前端根据角色隐藏了某个按钮,用户还是可以手工输入地址访问页面,所以后端接口必须做二次鉴权。我在实际开发中就是这样实现双向保障的,前端为了用户体验、后端为了真正的数据安全。4.3 核心页面的实现逻辑与展示管理后台很重要的页面包括:仪表盘首页、考核周期列表、指标配置页、评分页、结果分析页。仪表盘首页应该放上员工总数、正在进行的考核数、待确认结果数、近六个月的绩效趋势图。这些数据用一个聚合接口 /api/dashboard/data 返回,前端用 ECharts 渲染图表。评分页是最需要打磨交互的页面。员工点进去,先看到一个需要自己评分的人员列表,每个人员点开后展示对应的一组指标,每个指标后面是一个数字输入框,底部是提交按钮。评分页要处理的核心交互有三个:未填完所有指标不能提交;提交前有二次确认弹窗;提交成功后立即刷新列表并把此人置为“已评分”状态。结果分析页对我展示系统的价值很大。通过筛选考核周期,可以查看部门排名表、等级分布饼图、个人趋势折线图。这张页面直观展示了“绩效量化”这四个字的最终效果,答辩演示时,评委看到图表比看到一堆数据表的印象要直观得多。导出功能我建议用前端把表格数据直接导出成 Excel。用 SheetJS 库,比后端导出更简单,也省去下载文件的接口。4.4 接口联调、响应拦截与状态码处理前端 Axios 封装上有几个细节值得做。第一,request 拦截器统一添加 token,不必每个接口重复写。第二,response 拦截器判断 code 码,非 200 直接弹 Message 提示,401 则跳转登录页,同时清除本地 token。第三,所有请求都用 async/await + try/catch 包裹,避免出现接口报错导致页面白屏。页面数据加载时通过 v-loading 指令控制 loading 状态,这个是 Element UI 自带功能,不需要额外写代码。联调过程中我最大的体会是前后端字段一定要在接口文档里达成一致。我早期因为时间紧张,后端返回的字段名叫 deptName,前端页面用 departmentName,结果页面上永远显示空白,排查了大半天。跟后台的同学定接口时,要用 Apifox 在线 Mock 数据,前端等不到后端时可以用 Mock 数据跑版式,后端就绪后一键切到真实环境。5. 开发实战问题排查与经验总结5.1 高频运行异常与解决速查表这个项目由于代码量不小,我在开发过程中前后处理过蛮多问题,有些是调试环境引起的,有些是自己粗心,下面这张排查表是实用度最高的。现象可能原因解决方式前端请求接口 CORS 报错后端未配置跨域或允许来源不对配置 CorsFilter,AllowedOrigin 写前端完整地址token 过了两小时失效JWT 过期时间设置太短把过期时间调成8小时,答辩演示时更稳妥日期字段返回乱码JSON 序列化格式不对统一配置 Jackson 日期格式 yyyy-MM-dd HH:mm:ss评分提交后数据没变化状态机判断不通过或事务未生效检查 Service 方法上是否有 @Transactional页面菜单不显示路由加规则后未刷新或 store 没存调用一次 router.addRoutes 后重新跳转部门下有员工无法删除外键约束或逻辑删除判断缺失删除部门前先检查员工表 dept_id 引用前端上传 Excel 导入失败导入模板字段不匹配下载导入模板用相同的列名,不要自己另建模板数据权限没生效拦截器未把用户角色写入上下文检查 UserContext 是否在过滤器中正确赋值5.2 编写过程需要注意的设计规范我在这个项目里最想提醒的一条经验是:核心业务表一定要留逻辑删除标记,尤其是 score_record 表和 performance_result 表。绩效数据如果物理删除了,后续要追溯就完全没有痕迹。我在做季度复核回调时,因为一个月前误删了一批测试数据,导致某个部门的结果重新计算后与历史报表对不上,还得回数据库手工补数据。所以请一定给这两张表加 deleted 字段。接口层要做好参数校验统一处理。用 javax.validation 注解给 DTO 字段加上 @NotBlank、@NotNull、@Max、@Min,由全局异常处理捕获 MethodArgumentNotValidException 并返回带字段名的错误提示,不要每个接口内部手动 if (xxx == null) return error。这样写虽然代码有点啰嗦,但项目整体更规范,答辩项目文档的代码可读性会好很多。状态机更新要统一写在一个 Service 方法里。考核周期状态从前端传过来更新是很危险的设计,应当提供专门的方法,例如 publishCycle、confirmResult、completeCycle,内部校验当前状态并更新。这样既保证数据安全,也让接口语义更清晰。在答辩问答环节,如果评委问“分数提交后是否可以撤回”,你可以明确回答“已提交的评分只能走申诉流程由管理员更新状态后进入待审核列表,评分人不能直接改分数”,这个业务设计能提升整个系统有公信力。5.3 项目打包部署与演示环境准备部署环节不需要太复杂,但必须有完整可演示的环境。后端执行 mvn clean package -DskipTests 打包成 jar,在服务器上用 nohup java -jar xxx.jar --spring.profiles.active=prod 运行。前端执行 npm run build 生成 dist 目录,交给 Nginx 托管,同时配置反向代理把 /api 请求转发到后端服务。Nginx 配置要点:server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署时数据库的初始化脚本用项目里的 init.sql 一键执行,默认管理员账号 admin 密码建议初始化为 123456,并在首次登录时强制提示修改密码。演示前把几套不同角色、不同部门的测试数据准备足,比如某部门负责人账号和普通员工账号,方便现场演示数据权限隔离。数据库脚本要在项目内附上,并且注明了创建表和初始化数据放在一起,数据覆盖一个考核周期的完整数据。6. 从毕设到项目落地的迭代思考在系统上线并实际跑完一个季度考核之后,我反而重新思考了技术实现和业务落地的差距。系统的核心功劳不是视觉效果,而是把原本晦涩的绩效数据变成了有记录、可追溯、可申诉的流程资产。有很多内部从最初怀疑这种线上评分,到季度末等待报告时的正常查看,这本身是我认为比较成功的地方。一个值得留意的方向是绩效结果的数据可视化越来越重要。除了 ECharts 做的常规柱状图饼图外,后续我打算加上部门横向对比的热力图和员工绩效趋势预测线。绩效数据的价值不在于一次考核的结果,而在于长期累积后能看到个人或团队的真实成长曲线,这个能力一旦实现,系统就从工具变成了决策辅助。另一个可扩充点是绩效申诉和面谈记录流程。在真实管理实践中,员工对绩效结果有异议是很常见的,如果系统只允许结果发布而不提供申诉入口,反而会影响系统能的正常开展。可以考虑加入申诉申请、HR 复核、线下沟通记录三条流程。这一块虽然业务逻辑不难,但确实能体现对业务场景有进一步理解。权限精细化也是值得深入的方向。现阶段数据权限只做到部门级,后续可以扩展到项目组级、岗位级别以及字段级权限。比如某些敏感指标只有系统管理员能看到详细分数,部门负责人只能看到汇总分布,这个需求在企业级系统中非常常见,把这个问题在论文的研究与展望里写清楚,贴合真实应用场景。前端技术栈方面,如果之后有时间,我会迁移到 Vue3 + TypeScript 并搭配自动生成 API 类型定义的方案。但这属于锦上添花,不是当前阶段的必要内容。毕业设计阶段核心是把业务逻辑跑通、把文档写好、把部署流程熟练,技术栈的新旧一般不会成为主要讨论点。最后一句话是我这段时间最大的体会:做项目不要急着敲代码,先把业务规则、数据流向、角色矩阵画清楚。绩效量化系统这类业务逻辑较重的项目,真正耗时的地方不在写接口,而在想明白谁在什么时间对谁提交什么分数、分数如何校验、结果如何汇总,这些理顺了以后,SpringBoot 和 Vue 只是执行工具而已。