资讯动态

SpringBoot+Vue企业员工薪酬管理系统设计与实现:从架构到部署全解析

发布时间:2026/10/14 6:21:48 来源:尧图企业网站定制
企业员工薪酬管理系统这种项目在国内各技术社区里几乎算得上“常青树”了。我接触过不少用 SpringBootVue 做毕设或者课设的同学十个里至少有四五个选题方向都是这类企业管理平台。这其实很好理解技术栈主流、需求边界清晰、场景大家也都熟悉再加上这类系统天然自带权限、流程、统计等功能模块拿来练手也好、应付答辩也好都是比较稳妥的选择。这篇文章我就围绕一套 SpringBootVue 前后端分离的员工薪酬关系系统管理平台把整体的设计思路、技术选型、数据库建模、核心模块实现、环境搭建过程和踩坑经验完整拆开讲一遍。不管你是准备拿它做毕业设计、课程设计还是单纯想学一下企业级管理系统怎么从零落地这篇内容都能给你一个可以直接参考的工程样本。先说明一下以下所有代码、配置和建表语句都来自我实际搭建和运行过的一套模拟项目脱敏处理过业务数据但工程结构和核心代码都是真实可跑的。文章会尽量把关键细节讲透而不是贴一堆代码就完事。1. 这个薪酬系统到底做了什么1.1 核心功能模块盘点薪酬关系管理系统本质上是一个围绕“员工—薪资—绩效—发放记录”这四条主线展开的信息管理平台。我做的这套系统在功能划分上遵循了大多数同类型毕设项目的惯例同时又额外兼顾了一点业务上的合理性。系统整体分为前台用户端和后台管理端两大部分。普通员工登录后能看到自己的基本档案、每个月的工资条、社保缴纳记录以及修改个人密码等功能管理员登录后则拥有完整的管理权限包括员工档案管理、部门管理、职位管理、薪酬项目配置、月度薪酬核算、工资条生成与导出、系统用户管理、角色权限分配等。从模块粒度上拆开看主要包含这么几个核心块员工信息管理支持员工档案的录入、编辑、离职标记、批量导入导出。这里我建议加一个逻辑删除字段不要物理删除员工记录否则每年有员工离职的时候历史薪酬台账会断链后面对账非常麻烦。薪酬项目管理基本工资、岗位津贴、绩效奖金、加班费、餐补、交通补贴以及五险一金、个税扣除项等全部作为可配置的薪酬项存储而不是写死在代码里。这样当公司调整福利政策时管理员只需要增删改薪酬项配置不需要动一行代码。月度薪酬核算管理员选定核算月份后系统自动读取该月在职员工列表根据每个员工的薪酬项配置生成当月薪酬单。这一步很多毕设项目会偷懒做成手工录入但实际使用中薪酬项目多、公式复杂手工录入容易出错所以我的方案是“配置自动生成 允许手动微调”。工资条查看与导出员工端按月查看自己的工资条详情管理员端可以按部门、按月度筛选也可以把整月工资表导出成 Excel。我用的是 EasyExcel比直接用 POI 写原生代码省心得多。统计报表按部门汇总薪酬总额、平均薪酬按月度展示薪酬趋势这个模块答辩时是最加分的点一定要做。权限管理基于用户的角色权限模型区分管理员、部门主管、普通员工三种角色后端通过拦截器校验接口访问权限前端配合路由守卫控制页面访问。这些功能看着多但每个模块本身都不算复杂难点主要在于模块与模块之间的数据联动以及权限控制如何做得干净、不混乱。下面我会在架构和实现部分把这两点展开讲。1.2 这套技术组合为什么适合做毕设我接触到的很多毕业设计题目其实都存在同一个问题题目听起来很大但落地过程中要么技术过于老旧要么需求边界模糊做起来容易陷入泥潭。SpringBootVueMySQL 这套组合恰好避开了这些坑。第一技术栈处在一个“既主流又不至于过度复杂”的甜蜜区间。SpringBoot 是当前后端开发的绝对主流框架Vue 在前端领域也有庞大的生态从技术评分角度来说答辩评委看到这两个关键词基本不会对技术选型本身提出质疑。相比之下如果用纯 JSPServlet 做技术上确实有点脱节如果上 SpringCloud 微服务全家桶对课设和毕设来说又有点用力过猛部署和讲解都要多花很多时间。第二前后端分离的结构特别适合拆解任务。一个人开发的时候可以先集中力量把后端的接口全部调通再去做前端的页面展示两边互不阻塞。对于做课设、毕设的同学来说这种分阶段的推进节奏非常友好因为你可以快速建立一个“后端已经通了一半”的阶段性成果心态上会稳很多。第三MySQL 作为数据库完全可以满足这个量级的业务场景。员工薪酬系统再怎么说也就是一个中小型企业的内部管理系统数据量通常在几千到几万条的量级MySQL 无论是性能、稳定性还是使用者的熟悉程度都足够胜任。而且 MySQL 对于 group by、子查询、联表查询这些常规操作的支持非常成熟做统计数据很顺手。总的来说这套组合不是所谓“最先进”的技术架构但它是性价比最高、风险最低、最适合一个人在一个学期内交付完整项目的方案。2. 项目架构与技术选型的背后逻辑2.1 为什么选前后端分离而不是服务端渲染做个对照其实就很清楚如果使用传统的 Thymeleaf 模板渲染后端既写接口又要管页面跳转前后端逻辑耦合在一起在项目中期需求调整时非常被动。比如前端页面改个字段展示后端模板也要跟着动权限控制如果放到模板里做每次渲染都要实时校验权限代码维护成本直接翻倍。用前后端分离之后后端只需要专注于提供 RESTful API前端完全独立开发。开发时我用 Vue CLI 的 devServer 做接口代理部署时把后端打成 jar 包、前端打包成静态资源丢到 Nginx 或者容器里整个部署链路清晰明了。这个选择也影响了后续一系列技术决策登录之后状态怎么保持跨域请求怎么处理前端路由如何做访问控制等等。所以架构选型不是拍脑袋决定的它会连锁地决定你后面的每个细节。2.2 核心依赖与版本配套方案版本选择这块很容易被忽略但我真心建议不要上来就选最新版。所谓“稳定大于一切”在毕设场景下尤其成立。我实际使用下来比较省心的一套版本组合是组件版本说明JDK1.8 或 11项目用 1.8 完全可以兼容性最好SpringBoot2.7.x这个版本生态非常成熟不要用 3.x因为 3.x 底层变动大很多老教程方法不通用MyBatis-Plus3.5.x比原生 MyBatis 省太多事分页插件自动 CRUD 很香MySQL5.7 或 8.0两个版本都行驱动注意配合Vue2.6.x Element UI对新手最友好Element UI 组件全、资料多Node14.x 到 16.xVue CLI 5 在低版本 Node 下可能有问题建议用 16Maven3.6 以上常规配置为什么 SpringBoot 用 2.7.x 而不是 3.x因为目前网上你能搜到的大部分教程、博客、开源项目都基于 2.x 版本3.x 虽然性能更好但切到 Jakarta 命名空间后很多老代码直接编译不过对新手来说排查问题的成本陡增。做项目不是追新能稳定跑起来才是第一位的。2.3 工程目录结构怎么组织最舒服项目采用标准的前后端分离目录布局salary-system/ ├── backend/ # SpringBoot 后端工程 │ ├── src/main/java │ │ └── com/example/salary │ │ ├── controller/ # 控制层 │ │ ├── service/ # 业务层 │ │ ├── mapper/ # MyBatis-Plus 的 DAO 层 │ │ ├── entity/ # 实体类 │ │ ├── dto/ # 请求与响应对象 │ │ ├── config/ # 跨域、拦截器、MyBatis-Plus 配置 │ │ ├── common/ # 统一返回体、异常处理、工具类 │ │ └── utils/ # JWT、Excel 等工具类 │ └── src/main/resources │ ├── mapper/ # XML 映射文件 │ └── application.yml └── frontend/ # Vue 前端工程 ├── src │ ├── api/ # axios 请求封装 │ ├── router/ # 路由配置 │ ├── store/ # Vuex 状态管理 │ ├── views/ # 页面组件 │ └── utils/ # 请求拦截等工具 └── package.json这个结构有几个好处。第一entity、dto、vo 分离避免直接把数据库实体暴露给前端接口答辩的时候讲起来非常清晰。第二common 包里放统一返回体配合全局异常处理接口风格一致前端处理响应时心智负担小。第三前后端完全独立后续无论是单独测试还是分别部署都不需要互相迁就。3. 数据库设计与核心表结构3.1 核心表的字段设计与关系梳理数据库设计是整个项目的根基。我见过很多同学代码写得还不错但库表结构一团糟部门表和员工表之间没有外键关系薪酬表里存的是部门名的字符串而不是部门ID等做到统计报表的时候完全没法写 SQL只能硬编码去匹配字符串非常痛苦。我这次的表结构设计遵循一个原则所有业务关系都通过外键或逻辑外键表达不搞冗余存储。核心表大致包括-- 部门表 CREATE TABLE sys_dept ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, parent_id BIGINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 员工表 CREATE TABLE employee ( id BIGINT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender TINYINT, dept_id BIGINT, position VARCHAR(50), hire_date DATE, status TINYINT DEFAULT 1, -- 1 在职 0 离职 deleted TINYINT DEFAULT 0, -- 逻辑删除标记 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_dept (dept_id) ); -- 用户表登录账号 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, user_type TINYINT DEFAULT 2, -- 1 管理员 2 普通员工 employee_id BIGINT, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 薪酬项配置表 CREATE TABLE salary_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, item_name VARCHAR(50) NOT NULL, item_type TINYINT, -- 1 增项 2 减项 amount DECIMAL(10,2), is_default TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 员工薪酬项表 CREATE TABLE employee_salary_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, employee_id BIGINT NOT NULL, salary_item_id BIGINT NOT NULL, amount DECIMAL(10,2), effective_date DATE, expire_date DATE ); -- 月薪酬单表 CREATE TABLE salary_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, employee_id BIGINT NOT NULL, salary_month VARCHAR(7) NOT NULL, -- 格式2025-06 base_salary DECIMAL(10,2), total_income DECIMAL(10,2), total_deduction DECIMAL(10,2), actual_salary DECIMAL(10,2), status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_emp_month (employee_id, salary_month) );这里有个重要设计salary_record 里的 base_salary、total_income、actual_salary 其实属于冗余字段。有人可能会问既然薪酬明细都在 employee_salary_item 里为什么还要在月表里冗余一份汇总数据原因很简单薪酬记录本质上是一种财务快照。员工小李 4 月份的绩效奖金是 2000 元到了 7 月份发现当时核错了改成了 1500如果工资条直接实时去关联查询当月的配置那么从 7 月开始看 4 月份的工资条数据就会变成 1500这不符合财务审计的要求。所以月薪酬单表必须把当月核算结果全部落盘存储。这个道理在做毕设时讲给答辩老师听通常都会得到一个不错的评价。3.2 薪酬计算逻辑在表结构里的落地方案薪酬计算可以抽象成非常清晰的两步第一步构建“员工—薪酬项—金额”的有效关系。同一个员工在不同时间段相同薪酬项的金额可能不一样所以 employee_salary_item 表里特意设计了 effective_date 和 expire_date 两个字段。核算某月薪酬时只需要筛选出”生效日期 核算月份月末 且 失效日期 核算月份月初”的记录就是这个员工在当月的薪酬配置。第二步把增项、减项分别求和再加上基本工资得到应发金额和实发金额。公式就是应发合计 基本工资 各类增项绩效、补贴、加班费等 扣款合计 社保个人部分 公积金个人部分 个税 其他扣款 实发工资 应发合计 - 扣款合计个税的计算我使用的是按年累计预扣法的简化版本毕竟是开发学习和课设这个层面把公式写明白就足够了。如果后续想扩展成生产级可以考虑引入专门的税务计算库但目前这个复杂度已经能展示出你对业务的理解深度。这张配置化的结构最大的好处是工资项可调整、可追溯、可扩展。新入职的员工分配默认薪酬项调薪时只改生效日期不会破坏历史记录。这就是我前文说的“配置自动生成 允许手动微调”模式得以落地的基石。4. 核心模块实现实录4.1 登录认证与权限控制怎么实现权限认证这块我选择了 JWT 方案配合拦截器做接口级别的访问控制。整体流程是用户提交用户名和密码后端校验后生成 JWT Token返回给前端。前端把 Token 存到 localStorage 或 Vuex 里每次请求通过 axios 拦截器塞进 Header 的 Authorization 字段。后端写一个拦截器对需要认证的接口统一解析 Token解析通过才放行同时从 Token 里取出用户角色做接口级的角色校验。JWT 的工具类核心逻辑大致是public class JwtUtils { private static final String SECRET your-secret-key; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; // 7天 public static String createToken(Long userId, String username, Integer userType) { return Jwts.builder() .claim(userId, userId) .claim(username, username) .claim(userType, userType) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }有一点提醒一下JWT 的密钥不要写在代码里硬编码实际项目至少应该放到 application.yml 配置文件中甚至可以用环境变量覆盖。虽然做毕设时很多同学图省事但把这个细节做好在答辩时可以作为一个亮点提出来。密码加密这块不要用明文也不要只做 MD5。我用的 BCryptPasswordEncoder这是 Spring Security 内置的加密器每次加密同一个密码都会生成不同盐值的密文安全性有保障。虽然我们没有引入完整 Spring Security但单独引一个加密工具类成本很低。拦截器注册时注意排除登录接口、验证码接口等无需认证的路径否则会出现登录接口自己都要先登录的死循环这个细节我见过不少同学踩坑。4.2 员工信息管理的增删改查细节员工模块是整套系统最基础的模块基本上就是常规的单表 CRUD但有几个细节值得展开。一是分页查询。我直接用了 MyBatis-Plus 的分页插件在配置类里添加Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }然后 Service 层调用 page 方法传入页码和每页条数。分页插件会自动生成 limit 语句非常方便。但是有一点需要注意如果查询语句里有自定义的 XML SQL分页插件可能不会自动帮你包装这时候要么自己写分页要么改造 SQL 用 lambdawrapper 的方式查询否则就会出现明明传了页码但返回全量数据的情况。二是条件查询。员工列表页通常会有姓名、工号、部门、状态等多个筛选条件。在 Service 层用 LambdaQueryWrapper 动态拼接条件LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(empNo), Employee::getEmpNo, empNo) .like(StringUtils.hasText(name), Employee::getName, name) .eq(deptId ! null, Employee::getDeptId, deptId) .eq(status ! null, Employee::getStatus, status) .orderByDesc(Employee::getCreateTime);这个写法既简洁又不容易出错。很多新手习惯自己拼 SQL 字符串又麻烦又容易引发 SQL 注入完全没有必要。三是部门关联。列表页往往要展示部门名称但员工表里只存了 dept_id所以我做了一条副表查询批量查出部门 ID 集合后一次性查回部门名称映射然后组装到返回对象里。这种查询方式可以避免 N1 问题也就是避免循环里查数据库在数据量上来时性能优势非常明显。4.3 薪酬登记与工资条生成流程这块是业务核心值得从头到尾捋一遍。管理员发起“生成某月工资条”的请求时后端流程是这样的第一步查询该月在职员工列表。所谓在职就是员工表里 status 为 1并且 hire_date 早于或等于该月最后一天。注意这里要处理一个边界情况员工入职当月是发全月工资还是按天折算现实中不同公司规则不同我在这套模拟项目里采用了简化处理即入职当月按满月发放。答辩的时候如果被问到这一点可以说这是当前项目的业务规则并根据需要扩展按天折算的需求。第二步遍历在职员工为每个员工构建当月薪酬数据。从 employee_salary_item 表读取该员工当月生效的薪酬项再从 salary_item 表拿到增项还是减项的类型统一累加计算。第三步保存到 salary_record 表。由于建表时设计了 employee_id 和 salary_month 的唯一索引同一员工同一个月如果重复生成数据库会直接报错Service 层捕获到 DuplicateKeyException 后就抛给全局异常处理器提示用户“该员工当月数据已存在”。这个唯一索引是数据完整性的关键不要省。工资条展示时前端调用了一个接口去查工资条的明细记录。这里我需要说明一下表格里存的是聚合汇总数据并没有存每一项的明细快照。严格来说要做完整的工资条详情展示应该再建一张 salary_record_detail 表逐项保存增项、扣项、金额一旦生成后不再改动。我在这个模拟项目里做了一部分这样的事如果你想把它扩展得更接近生产级这一块是值得完善的。Excel 导出用的是 EasyExcel。操作方式很直接定义好导出实体类加上注解然后一行代码写出去EasyExcel.write(outputStream, SalaryExportDto.class).sheet(工资表).doWrite(dataList);相比原生 POI 需要在内存里手动创建 Cell 对象EasyExcel 的使用门槛低到不需要专门学习。4.4 报表统计的 SQL 怎么写效率高统计模块我只用了三个维度的报表部门汇总、月度趋势、员工薪酬排名。部门月度汇总的核心 SQL 大概是SELECT d.dept_name, COUNT(DISTINCT r.employee_id) AS emp_count, SUM(r.total_income) AS total_income, SUM(r.total_deduction) AS total_deduction, SUM(r.actual_salary) AS actual_salary FROM salary_record r JOIN employee e ON r.employee_id e.id JOIN sys_dept d ON e.dept_id d.id WHERE r.salary_month #{month} GROUP BY d.dept_name ORDER BY actual_salary DESC;这个 SQL 不算复杂但有一个小技巧先关联条件收窄数据范围再去 group by这样扫描的行数会少很多。WHERE r.salary_month #{month} 会先过滤掉大部分 salary_record 行再去做关联和分组相比先全表 join 再 where性能差异在数据量大的时候非常明显。月度趋势报表就更简单了直接按 salary_month 分组求和SELECT salary_month, SUM(actual_salary) AS total FROM salary_record WHERE salary_month BETWEEN #{startMonth} AND #{endMonth} GROUP BY salary_month;这类 SQL 建议写在 XML 映射文件里用 ResultMap 映射回 DTO。在 XML 里写的好处是 SQL 可以格式化调整起来不会像 Java 代码里拼字符串那么痛苦。报表页面前端我用了一个开源图表库柱状图展示部门对比、折线图展示月度趋势。图表的存在能极大提升整个项目在答辩时的观感因为它让“数据管理系统”这件事从数据堆砌变成了可视化展示评委一眼就能看到你到底做了什么。5. 运行环境搭建与部署实操5.1 本地环境准备与版本避坑环境准备是很多新手翻车的第一站。我给你列一个稳妥的操作顺序安装 JDK 8配置 JAVA_HOME命令行输入 java -version 确认安装 Maven 3.6 以上版本配置阿里云镜像否则拉依赖会慢到你怀疑人生安装 MySQL 5.7 或 8.0创建数据库和账号安装 Node.js 16.x顺带把 npm 源切到国内镜像开发工具后端用 IDEA前端用 VS Code或者 IDEA 全家桶也行。有个版本坑要重点说SpringBoot 2.7.x 版本内置的 MySQL 驱动对 MySQL 8.0 的支持有些许差异连接串里必须带上 useSSLfalse 和 serverTimezoneAsia/Shanghai否则很可能会出现时区相关的报错。这是全社区最常见的问题之一遇到了不用慌张检查一下连接串参数就好。5.2 后端从克隆到启动的关键步骤拿到项目源码之后不要急着直接点运行。先按下面这个顺序来打开 application.yml修改数据源配置数据库名、用户名、密码按自己本地环境改掉确认 mybatis-plus 的 mapper-locations 路径和你实际 XML 存放位置一致不一致会导致启动时明明有 Mapper 接口却报找不到绑定方法执行项目里自带的 sql 脚本把库表和初始数据建好如果项目里有 Redis 依赖记得先启动本地 Redis否则启动过程会一直等待连接超时。我这里没有使用 Redis因为毕设场景里 JWT 无状态化已经足够没必要再引入一层缓存中间件增加复杂度启动 SpringBoot 主类看到 Started 日志后访问一下 Swagger 地址验证接口文档是否能打开。后端跑通后用 IDEA 自带的 HTTP Client 或者 Postman 先调一下登录接口拿到 Token 后再去调员工列表接口。这一步验证通过就说明后端基本没有大问题了。5.3 前端从安装依赖到联调的细节前端部分标准流程是cd frontend npm install npm run serve不过在 npm install 这一步很多同学会遇到 node_modules 目录损坏、依赖包版本冲突、安装速度过慢等问题。我的经验是优先使用 npm install 而不是 cnpm如果下载慢就临时切换镜像源每装完一个关键依赖可以顺手跑一次 npm run serve 做快速验证不要等所有依赖全装完再启动否则排查范围会变得很大。前后端联调时最关键的一步是配置接口代理。在 vue.config.js 里这么写module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } };这样做的好处是前端开发时请求 /api 开头的地址会被自动转发到后端的 8080 端口完美规避了浏览器跨域限制。这个方案比在后端写 CORS 配置更贴近实际生产环境后续部署到 Nginx 时也基本沿用同样的代理思路。联调阶段建议先跑通一个最完整的链路登录 → 获取 Token → 进入首页 → 打开员工列表 → 新增一个员工 → 生成工资条 → 导出 Excel。这条链路通了说明整个项目的主干流程没有问题剩下的只是各个细节的打磨。6. 踩坑记录与常见问题排查6.1 前后端联调最常踩的坑前后端联调可以说是整个项目里最折磨人的阶段我记录一下几个高频问题。一个是响应数据不一致。后端统一返回 Result 对象结构是 code、message、data 三层但总有人个别接口的返回值类型写成了 String 或者其他裸类型导致前端拿去取 data 字符串却发现拿到的是 JSON 字符串怎么解析都报错。解决方案是后端严格统一返回体任何接口都走 Result 包装不要有例外。另一个是 Token 失效后的处理。Token 过期后前端再请求受保护接口会返回 401但很多新手前端拿到 401 后不知道跳转登录页页面表现为所有数据加载失败但点击哪里都没反应。我习惯在 axios 响应拦截器里统一处理service.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } )第三是时间格式不一致。MySQL 里 datetime 类型通过 Jackson 默认序列化后返回给前端的是带 T 的 ISO 格式比如 2025-06-18T10:30:00而前端表格组件默认期望的是 2025-06-18 10:30:00。解决方式是在 application.yml 里统一配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这个配置一加上很多“前端时间显示多了一个 T”的问题直接就消失了。6.2 数据库层面的坑数据库层面我遇到的最典型的问题是字符集和排序规则不一致。建库的时候如果字符集不是 utf8mb4存入中文没问题但表情符号和特殊字符会变成问号。我个人习惯建库语句统一用CREATE DATABASE salary_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;排序规则选择 utf8mb4_general_ci 还是 utf8mb4_unicode_ci在小项目里差异不大关键是全库统一。如果混合使用关联查询时可能出现 Illegal mix of collations 的报错。另一个常见问题是 SQL 模式。MySQL 5.7 和 8.0 默认开启了 ONLY_FULL_GROUP_BY如果报表 SQL 中 select 了非聚合列且不在 group by 里就会报错。解决方法是严格写合规 SQL把需要展示的列都放进 group by或者用 ANY_VALUE 函数包住非聚合列。6.3 权限与安全细节权限这块有一个比较隐蔽的问题前端通过路由守卫控制页面跳转但这只是一个体验层面的控制并不是真正的安全边界。前端隐藏了一个按钮不代表后端接口就安全了。真正有效的权限控制一定在后端所以我用拦截器统一校验接口角色。这里有个细节是拦截器的角色校验建议基于路径白名单来做例如 /admin/** 的接口只允许管理员访问而不是在每一个 Controller 方法里手写逻辑判断否则容易漏掉某个接口。关于密码安全还有一点要特别提醒避免把密码字段返回给前端。很多同学数据库里存了 sys_user 表查出来直接整表返回给前端密码字段也跟着出去了。即使密码是 BCrypt 密文也绝对不能暴露给客户端。规范做法是实体类里在密码字段上加 JsonIgnore或者查询时用 select 排除该字段。另外前端禁止展示后端异常堆栈。我在后端定义了一个全局异常处理器业务异常返回友好提示预期外异常统一记录日志并返回“系统繁忙请稍后重试”。这样既保护了系统信息也让前端处理异常时有一个统一的判断入口。7. 一点个人体会做完这个项目我最深的感受是一个管理系统能不能让人用得舒服靠的不是某个花哨功能而是每一个细节是否经得起推敲。比如员工离职后历史薪酬是否还在重复生成工资条会不会出错权限漏了哪个接口会导致什么后果。这些东西单独拎出来都不难但它们叠加在一起才决定了系统是能交付的成品还是只能停留在截图里的演示代码。如果你打算拿这套项目做课程设计或者毕业设计我的建议是先跑通完整流程再去做两件事第一把某个模块往纵深方向强化比如把薪酬计算从简单配置升级成可自定义公式第二把某一个非功能性指标做好比如接口层的参数校验、操作日志记录、数据权限隔离。这些差异化改造远比把 CRUD 做得更熟练更能在答辩时打动评委。最后分享一个实际的小技巧答辩前把 Nginx 部署走一遍前端打包成静态文件、后端打成 jar 包放到服务器上跑通整个部署流程。虽然本地开发模式下项目已经能跑但如果你能在现场展示从零部署的过程评委会明显感觉到你对这个项目不只是“会用”而是“真正掌握”。这比多写一百行业务代码都更有说服力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑