在企业里摸爬滚打过的朋友应该都有感触,工资核算这件事看起来简单,真正做起来却是一堆细碎又敏感的活儿。绩效系数、五险一金基数、个税专项附加扣除、考勤扣款、补贴项……每一处都容不得半点马虎。以前靠Excel表格手工核算,不仅效率低,还容易出错,碰上月份特殊(比如年终奖发放、调薪生效)更是焦头烂额。正因如此,一个可复用的企业工资管理系统在课程设计、毕业设计甚至小型企业实际落地中都是非常受欢迎的选题。今天我就结合手头这套基于Spring Boot + Vue的企业工资管理系统(含源码、数据库、文档),把从设计思路到关键技术实现、再到部署交付的完整链路拆开讲清楚,帮大家少走弯路。
这套系统是典型的前后端分离架构:后端用Spring Boot 2.x提供RESTful API,前端用Vue 2.x + Element UI搭建管理界面,数据库采用MySQL 5.7。整套项目包含员工管理、工资项目管理、工资核算、工资条发放、统计报表、用户登录与权限控制等功能模块,压缩包里还自带了完整的SQL脚本和部署文档,属于那种拿到手就能跑起来、边看代码边学习整体业务流的好素材。无论你是准备做Java课程设计的在校生,还是想快速搭建内部工具的技术人员,这篇文章都会对你有用。
1. 项目整体设计与技术选型
1.1 为什么选择Spring Boot + Vue这套组合
先说后端。Spring Boot在Java生态里的地位基本不需要我多强调,它通过自动配置把过去Spring MVC项目里大量的XML配置消掉了,起步依赖机制也很方便,想加什么功能直接引入对应的starter即可。对企业工资管理系统这种典型CRUD + 业务计算 + 报表展示的项目来说,Spring Boot的快速开发特性和稳定的生态支持都是最合适的。
再说前端。Vue 2.x + Element UI的组合在中小型管理后台中应用得非常广。Vue的响应式数据绑定让页面状态管理变得简单,Element UI则提供了一套开箱即用的表格、表单、弹窗、日期选择器等组件,做后台管理页面基本就是“搭积木”。相比React + Ant Design的组合,Vue的模板语法对初学者更友好,社区中文资料也丰富,上手成本更低。
这套前后端分离的架构还有几个额外的好处:
- 后端只负责数据接口和业务逻辑,前端只负责展示和交互,职责边界清楚,后期维护不乱。
- 前后端可以并行开发,接口定义好之后各做各的,效率提升明显。
- 前端打包成静态文件后可以部署到Nginx,后端打jar包独立运行,生产环境部署灵活。
1.2 系统核心功能模块拆解
在动手写代码之前,先把系统要做什么想清楚,这是整个项目中最值得花时间的事情。工资管理系统的核心不是代码多复杂,而是业务边界要清晰。这套系统的功能模块划分如下:
| 模块 | 核心功能 | 角色权限 |
|---|---|---|
| 登录认证 | 用户名密码校验、Session/Token管理 | 所有用户 |
| 员工管理 | 员工信息的增删改查、部门维护 | 管理员 |
| 工资项目管理 | 基础工资、绩效、补贴、扣款项配置 | 管理员 |
| 工资核算 | 根据员工和工资项自动生成工资单 | 管理员、财务 |
| 工资条查询 | 员工查看个人工资明细 | 普通员工 |
| 统计报表 | 部门工资汇总、月度趋势图表 | 管理员、财务 |
| 用户管理 | 账号分配、角色设置、密码重置 | 管理员 |
角色权限这里建议做成最简单的三表模型(用户表、角色表、用户角色关联表),不需要引入Spring Security的完整RBAC,但要在后端接口层做拦截判断,避免低权限用户直接绕过前端调接口。
1.3 项目目录结构与代码组织
拿到源码后先别急着跑,建议先把项目结构看清楚。后端是标准的Maven项目,包结构一般按照controller、service、mapper、entity、config来分包:
com.company.salary ├── controller // 接收前端请求,返回JSON数据 ├── service // 业务逻辑层,工资计算、统计等都在这里 ├── mapper // MyBatis接口层,操作数据库 ├── entity // 实体类,对应数据库表结构 ├── config // 配置类,如CORS跨域配置、拦截器配置 ├── common // 公共类,统一返回结果、异常处理、工具类 └── SalaryApplication.java // Spring Boot启动类前端Vue项目用Vue CLI创建,src目录下分为api(接口请求封装)、router(路由配置)、views(页面组件)、components(公共组件)、store(Vuex状态管理)几个部分。把公共请求方法统一封装到request.js里,每个页面对应一个views下的文件,这套规范写在文档里,后面扩展功能时也方便按图索骥。
2. 数据库设计与核心表结构
2.1 核心数据表拆解
工资系统最核心的资产就是数据,数据库表设计的好不好直接决定后续开发的顺利程度。这套系统的表设计我拆开看,主要围绕“员工—工资项—工资单—用户”四条线展开。
员工表(employee)
CREATE TABLE `employee` ( `id` int(11) NOT NULL AUTO_INCREMENT, `emp_no` varchar(20) NOT NULL COMMENT '员工工号', `emp_name` varchar(50) NOT NULL COMMENT '员工姓名', `department` varchar(50) DEFAULT NULL COMMENT '所属部门', `position` varchar(50) DEFAULT NULL COMMENT '岗位', `entry_date` date DEFAULT NULL COMMENT '入职日期', `base_salary` decimal(10,2) DEFAULT NULL COMMENT '基础工资', `status` tinyint(4) DEFAULT '1' COMMENT '在职状态 1在职 0离职', PRIMARY KEY (`id`), UNIQUE KEY `uk_emp_no` (`emp_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;工号emp_no建议设置为唯一索引,后续工资核算、统计报表都依赖这个字段关联。员工状态字段很重要,离职员工不做删除而是修改状态,这样历史工资数据才能留存。
工资项目表(salary_item)
这个表的设计在整个系统里属于特别容易忽略但极其关键的地方。工资项目不写死在代码里,而是做成一张配置表,好处是后续想增加“高温补贴”“全勤奖”这类新项目时,不需要改代码,数据库里加一条记录就能出现在核算页面中。
CREATE TABLE `salary_item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `item_name` varchar(50) NOT NULL COMMENT '项目名称', `item_type` tinyint(4) NOT NULL COMMENT '类型 1加项 2扣项', `item_default` decimal(10,2) DEFAULT '0.00' COMMENT '默认金额', `is_editable` tinyint(4) DEFAULT '1' COMMENT '是否允许调整', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;item_type的正负逻辑建议在代码里统一处理,数据库中统一存正数,加项加钱,扣项减钱,最后汇总时按类型判断。这样比存负数更直观,也避免出现“负负得正”的低级错误。
工资单表(salary_record)
CREATE TABLE `salary_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `emp_no` varchar(20) NOT NULL, `salary_month` varchar(7) NOT NULL COMMENT '工资月份 如2025-06', `item_name` varchar(50) NOT NULL, `item_type` tinyint(4) NOT NULL, `amount` decimal(10,2) NOT NULL, `remark` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_emp_month` (`emp_no`, `salary_month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里需要重点说下设计取舍。工资明细有两种存储方式:一种是一条工资单记录存一行汇总数据(员工、月份、总应发、总实发);另一种是每个员工每个月的每个工资项都单独存一条记录。这套系统的表结构采用的是明细存放方案,也就是每个工资项单独一条记录,然后通过SQL聚合出工资条的汇总数据。
我个人的观点是明细方案虽然数据行数多一些,但灵活性和可追溯性更好。后期做“为什么这个员工这个月工资和上个月不一样”的时候,直接对着明细记录一目了然。而且索引建好之后(idx_emp_month),查询性能基本不会成为瓶颈。
2.2 视图与统计SQL的设计
除了业务表,数据库脚本中建议把常用的汇总查询封装成视图。比如部门月度薪资汇总视图:
CREATE VIEW v_department_salary AS SELECT e.department, r.salary_month, SUM(CASE WHEN r.item_type = 1 THEN r.amount ELSE 0 END) AS total_add, SUM(CASE WHEN r.item_type = 2 THEN r.amount ELSE 0 END) AS total_minus, SUM(CASE WHEN r.item_type = 1 THEN r.amount ELSE -r.amount END) AS final_salary FROM salary_record r LEFT JOIN employee e ON r.emp_no = e.emp_no GROUP BY e.department, r.salary_month;视图的好处在于把复杂的聚合逻辑在数据库中固化好,后端代码只需要简单查询视图,不需要反复嵌套子查询。同时,视图还能起到一定程度的权限隔离作用——比如普通员工账号不给直接查询salary_record表的权限,只给查询个人视图的权限。
2.3 演示数据的准备方法
源码包里自带的salary_db.sql文件里,除了建表语句,还预置了20个左右的员工和两个月的工资项数据。这个设计对快速跑通项目非常有用,尤其是做课程设计答辩时,如果数据库里一张演示数据都没有,你很难向老师展示“报表功能”“趋势图”这些亮眼模块的实际效果。
所以我强烈建议大家拿到SQL脚本后,不要急着删掉演示数据,先把系统跑起来,看看各个模块在“有数据”的状态下是什么效果。以后想换成自己的数据,再执行DELETE清空业务表,保留配置表的数据即可。用户表里的管理员账号密码(一般是admin/admin123)是MD5加密存储的,千万别直接改数据库密码字段然后发现自己登不进去。
3. 后端Spring Boot核心实现
3.1 项目初始化与依赖配置
后端项目的依赖配置很朴素,基本上就是Spring Boot做Web服务、MyBatis做持久层、MySQL驱动连数据库、Druid做连接池这几样。关键的pom.xml依赖如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency>这里有个容易踩坑的地方:如果本地安装的是MySQL 8.x版本,mysql-connector-java要用8.0以上的版本,同时驱动类名和URL参数也有区别。MySQL 8的驱动类名是com.mysql.cj.jdbc.Driver,URL中还要追加serverTimezone=Asia/Shanghai参数,否则会报时区错误。源码默认按MySQL 5.7配置,如果你用的是MySQL 8,记得同步修改pom.xml和application.yml两个地方。
application.yml里主要配置端口、数据源、MyBatis映射路径:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/salary_db?useUnicode=true&characterEncoding=utf8&useSSL=false username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.company.salary.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case这个配置建议打开,数据库字段emp_no会自动映射到Java属性empNo,省去一堆@Results注解。
3.2 统一返回结果与异常处理
前后端分离项目中,接口返回的数据格式必须统一,否则前端拿到数据后无法做通用处理。我通常会在common包下定义一个Result类:
public class Result<T> { private Integer code; // 200成功 500失败 401未登录 private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }所有Controller方法的返回值都包装成Result对象,前端request.js里统一做拦截判断。配合自定义异常类和@RestControllerAdvice全局异常处理器,可以把业务异常直接抛出,由全局处理器捕获后转成统一的错误JSON,代码里就不需要到处写try-catch了。
3.3 工资核算的核心逻辑实现
工资核算这块是整个项目中最有技术含量的部分,也往往是课设答辩时老师最喜欢提问的地方。核心流程是:前端传入核算月份,后端遍历在职员工,根据工资项目配置,结合员工的基础工资数据,生成该员工该月度的工资明细记录。
删掉重来是这里一个很重要的设计细节。工资核算应该是幂等的——同一个月份,你点击一万次核算按钮,结果都应该一样。所以核算的第一步不是新增记录,而是先删除该月份已有的旧数据:
@Override @Transactional(rollbackFor = Exception.class) public boolean calculateSalary(String salaryMonth) { // 第一步:先删除该月份已有数据,保证幂等 salaryRecordMapper.deleteByMonth(salaryMonth); // 第二步:查询所有在职员工 List<Employee> employees = employeeMapper.selectAllActive(); // 第三步:遍历员工,生成工资明细 for (Employee emp : employees) { List<SalaryItem> items = salaryItemMapper.selectAll(); boolean hasBaseSalary = false; for (SalaryItem item : items) { SalaryRecord record = new SalaryRecord(); record.setEmpNo(emp.getEmpNo()); record.setSalaryMonth(salaryMonth); record.setItemName(item.getItemName()); record.setItemType(item.getItemType()); record.setAmount(item.getItemDefault()); // 基础工资项特殊处理:金额取员工表中的基础工资 if ("基础工资".equals(item.getItemName())) { record.setAmount(emp.getBaseSalary()); hasBaseSalary = true; } salaryRecordMapper.insert(record); } // 如果配置表中没有基础工资项,自动补一条 if (!hasBaseSalary) { SalaryRecord record = new SalaryRecord(); record.setEmpNo(emp.getEmpNo()); record.setSalaryMonth(salaryMonth); record.setItemName("基础工资"); record.setItemType(1); record.setAmount(emp.getBaseSalary()); salaryRecordMapper.insert(record); } } return true; }@Transactional注解在这个方法上必须加上,保证整个核算过程要么全部成功、要么全部回滚。如果中途某条记录插入失败而前面已经插入了大半数据,事务回滚之后数据还是干净的,不会出现“半个工资表”的脏数据状态。
工资计算的拓展思路是,如果你希望系统更智能一点,可以在工资项表里加一个item_formula字段,存计算表达式。例如“绩效=基础工资*0.2 + 500”,后端用脚本引擎解析这个表达式,就能实现灵活配置的工资计算。这样系统的灵活性和课程设计的答辩亮点会明显上一个档次。
3.4 登录认证与权限拦截实现
登录这块项目采用了比较轻量级的方案,没有引入Spring Security全家桶,而是用拦截器 + Session来做。登录成功后把用户信息存入Session,同时写一个LoginInterceptor拦截器:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("loginUser"); if (user == null) { // 未登录,返回401状态码 response.setStatus(401); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"message\":\"未登录或会话已过期\"}"); return false; } // 管理员接口校验权限 if (request.getRequestURI().startsWith("/api/admin/")) { User loginUser = (User) user; if (!"admin".equals(loginUser.getRole())) { response.setStatus(403); response.getWriter().write("{\"code\":403,\"message\":\"无权限访问\"}"); return false; } } return true; } }在WebConfig中注册拦截器,并放行登录接口和静态资源:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/logout"); } }这套轻量级方案有它的适用场景。对于课设项目而言,逻辑简单、代码量少、讲解方便,比引入Spring Security更贴合“课程设计”的定位。但如果你打算在企业生产环境中真正使用这套系统,还是建议把Spring Security + JWT那套整合进来,尤其是要考虑Token过期、密码加密强度(建议加盐)、接口防刷等问题。
4. 前端Vue实现与交互细节
4.1 Vue环境搭建与项目初始化
前端开发环境建议使用Vue CLI 3.x/4.x来创建项目。创建完成后,首先是安装项目依赖,Element UI、Axios、Vue Router是核心依赖,如果做图表还需要安装ECharts:
npm install npm install element-ui -S npm install axios -S npm install vue-router@3 -S npm install echarts -S提醒一下,Vue 2.x对应的Vue Router必须是3.x版本,如果你不小心装了Vue Router 4,项目启动时会直接报错——Vue Router 4只兼容Vue 3。这是新手朋友经常会踩的坑,装依赖之前务必看清版本兼容性。
在main.js中全局注册Element UI和路由:
import Vue from 'vue' import App from './App.vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import router from './router' Vue.use(ElementUI) Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')4.2 路由设计与页面骨架
前端路由设计建议跟后端菜单结构一一对应。为了管理方便,把需要登录后访问的页面放在一个Layout组件下,通过嵌套路由实现侧边栏布局:
const routes = [ { path: '/login', component: () => import('../views/Login.vue') }, { path: '/', component: () => import('../layout/Layout.vue'), redirect: '/dashboard', children: [ { path: 'dashboard', component: () => import('../views/Dashboard.vue') }, { path: 'employee', component: () => import('../views/Employee.vue') }, { path: 'salary-item', component: () => import('../views/SalaryItem.vue') }, { path: 'salary-calc', component: () => import('../views/SalaryCalc.vue') }, { path: 'salary-query', component: () => import('../views/SalaryQuery.vue') }, { path: 'report', component: () => import('../views/Report.vue') }, { path: 'user', component: () => import('../views/User.vue') } ] } ]路由懒加载(component: () => import(...))是实际开发中一个必须注意的细节。打包时每个页面会拆成独立的JS文件,首屏加载速度会有明显改善。对课设演示来说,更重要的是能让项目结构看起来更规范。
4.3 Axios请求封装与拦截器
前端所有接口请求要统一封装,不能每个页面都直接写axios.get完整地址。基本的request.js如下:
import axios from 'axios' import { Message } from 'element-ui' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:统一携带登录凭证 request.interceptors.request.use(config => { const user = sessionStorage.getItem('loginUser') if (user) { config.headers['Content-Type'] = 'application/json' } return config }) // 响应拦截器:统一处理返回结果和错误 request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } else if (res.code === 401) { Message.error('登录状态已过期,请重新登录') router.push('/login') return Promise.reject(new Error('未登录')) } else { Message.error(res.message) return Promise.reject(new Error(res.message)) } }, error => { Message.error('网络请求失败') return Promise.reject(error) } ) export default request这里需要特别说明baseURL的配置。开发环境下前端通过Vue CLI的proxy代理解决跨域问题,在vue.config.js中配置:
module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }生产环境部署时则把前端打包后的静态文件放在Nginx下,Nginx配置反向代理把/api开头的请求转发到后端服务。这样前后端分离的项目最终也能以同一个域名对外提供服务,规避跨域问题。
4.4 核心业务页面实现要点
员工管理页面和工资核算页面是这套系统里最具代表性的两个页面。员工管理页面本质上是典型的CRUD表格页——表格展示员工列表,弹窗嵌套表单实现新增编辑功能,删除操作加一个确认框防止误操作。这里我建议用Element UI的el-table配合el-dialog实现,代码结构清晰且完全不复杂。
工资核算页面的交互稍微复杂一点。页面顶部放一个月份选择器(el-date-picker的month类型),点击“开始核算”按钮后调用后端接口。核算完成后在同页面的下方表格展示本次核算的结果统计,包括参与核算人数、应发总额、实发总额等汇总数据。为了更好的用户体验,核算过程用loading状态盖住按钮,防止用户重复点击。工资核算这个操作本身很快,但万一数据量大导致接口响应时间超过2秒,loading状态的提示能避免用户误以为页面卡死。
统计报表页面推荐直接用ECharts实现柱状图和折线图。部门月度薪资汇总用柱状图,近6个月薪资趋势用折线图。ECharts的配置项不复杂,就是数据聚合处理好后setOption传进去即可。ECharts的初始化要在mounted生命周期中执行,组件销毁时记得调用chart.dispose()释放内存。
5. 项目运行中的常见问题与排查经验
5.1 后端启动失败的几种典型场景
后端跑不起来的报错五花八门,但九成以上是环境问题。我总结几个高频场景:
场景一:端口被占用Spring Boot默认使用8080端口,如果本地有其他服务占用了8080,启动日志中会出现Port already in use的报错。解决方式有两种,要么把占用8080的进程关掉,要么在application.yml中修改server.port。
场景二:数据库连接失败报错信息类似Cannot create PoolableConnectionFactory或Access denied for user。前者是MySQL服务没启动或连接URL写错,后者是用户名密码不对。这里教大家一个快速排查方法——先用Navicat或命令行工具直接连接MySQL数据库,如果能连上说明数据库本身没问题,问题出在Spring Boot配置或驱动上;如果命令行都连不上,优先检查MySQL服务是否启动、账号权限是否正常。
场景三:MyBatis映射文件位置错前端项目开发中,接口请求报404或500的排查会比后端稍微复杂一些。如果你的controller路径和前端请求路径对不上,最直观的排查手段是打开浏览器的开发者工具,切到Network面板,看请求的URL、请求方法、请求体内容是否和后端接口定义完全一致。前后端联调时养成先看Network再猜问题这样的习惯,能帮你节省大量无意义的排查时间。
5.2 前端跨域问题三板斧
前端调用后端接口报跨域错误(浏览器控制台出现CORS字样)时,按以下顺序排查:
先看Nginx或开发代理配置。开发环境下Vue CLI的proxy代理是最佳方案,一定要确保vue.config.js中proxy路径与请求baseURL对应。
再看后端CORS配置。如果不走代理直接请求后端地址,后端要开启跨域支持:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }如果前后端同域名部署,则检查Nginx配置中的location /api代理块确认配置正确。
最后检查浏览器请求方式。如果是PUT/DELETE请求触发跨域预检OPTIONS请求失败,确认后端接口是否正确处理了OPTIONS请求。在Spring Boot中,Spring MVC默认支持OPTIONS请求处理,通常不需要额外关注。
5.3 数据库字符集导致的乱码问题
中文乱码问题在项目中很常见,通常表现在数据库存储乱码或前端展示乱码两个层面。数据库层面,建表语句中统一使用utf8mb4字符集,确保表字段能存储emoji表情和中文字符。JDBC连接URL中加上characterEncoding=utf8参数。前端层面,确保所有HTML页面和JS文件的编码都是UTF-8,Vue CLI创建的项目默认就是UTF-8,不用额外处理。
排查乱码时有一个实用的小技巧:先用数据库客户端直接执行SQL插入一条中文数据,再去前端页面看能否正常显示。如果能正常显示说明数据库本身没问题,问题出在程序中数据传输的过程中可能出现乱码;如果数据库客户端都显示乱码,那就是数据库表字符集或连接参数的问题。
5.4 页面数据刷新与Session过期问题
菜单切换后数据没有更新的问题,很多时候是前端缓存或者生命周期钩子使用不当导致的。例如在Vue中,从A页面跳转到B页面时会执行B页面的created/mounted钩子,但如果B页面之前被keep-alive缓存了,再次进入时不会重新执行mounted,需要配合activated钩子或者在离开页面时主动销毁组件。课设项目一般不会用keep-alive,所以通常不需要担心这个。
Session过期问题则是另一个常见情况。后端在用户长期不操作后Session会自动过期,用户再次点击页面上的按钮时,请求返回401状态码。前端响应拦截器检测到401后自动跳转到登录页,这是API统一封装的好处。如果页面一直停留在打开状态没有发起请求,后端Session可能已经过期,这不影响已经加载出来的静态页面,但点击任何需要调用接口的按钮时就会被自动踢回登录页。这个逻辑在答辩演示时最好提前说清楚,避免被误认为是“Bug”。
6. 部署交付与文档配套
6.1 项目启动的完整流程
这套系统的部署流程不算复杂,但建议严格按照文档中的步骤来。第一步是创建数据库并导入初始数据,使用Navicat或命令行工具连接到MySQL,执行salary_db.sql脚本,完成建库建表和初始数据导入。第二步是修改application.yml中的数据库连接配置,将数据库地址、账号、密码改成你本机的配置。第三步是启动后端Spring Boot应用,在项目根目录执行mvn spring-boot:run,或者用IDE直接运行SalaryApplication主类。第四步是启动前端Vue应用,在vue目录下执行npm install安装依赖,再执行npm run serve启动开发服务器。最后浏览器访问http://localhost:3000,用admin账号登录,系统就完整跑起来了。
这套流程务必在答辩前完整走一遍,并且把每一步涉及的关键命令记清楚。很多时候不是系统本身有问题,而是演示时太紧张在环境上卡住了。提前演练三遍以上,比多背十页PPT都管用。
6.2 Maven打包与前端构建
正式的交付物中建议打包部署,而不是直接跑开发服务器。后端打包命令:
mvn clean package -DskipTests打包之后在target目录下得到salary-system.jar文件,使用java -jar salary-system.jar即可启动。注意如果打包时引用了外部配置文件,可以放到jar包同级目录下,通过spring.config.location参数指定:
java -jar salary-system.jar --spring.config.location=file:./application.yml前端打包命令:
npm run build打包完成后在dist目录下生成静态文件,把dist目录下的所有文件拷贝到Nginx的html目录下,配置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://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files这行配置很重要,它的作用是让前端路由在刷新页面时始终回到index.html,由Vue Router接管页面渲染,否则直接访问http://localhost/employee这个路径时Nginx会返回404。
6.3 配套文档与后续扩展方向
源码包里自带的文档通常包含需求分析、系统设计、数据库设计、接口说明和操作手册几个部分。文档的套路是标准的软件工程遵循流程,但在实际写文档时我建议大家重点补充两块内容:一是接口说明要写清楚请求参数和响应示例,这样后续自己维护或别人接手都不用重新看代码;二是操作手册要有截图步骤,特别是数据库配置和启动这两步,因为对新手来说卡住的地方往往都在跑环境上。
这个项目后续还有不少可以扩展的方向。工资报表加导出Excel功能可以引入EasyPoi或阿里EasyExcel,员工自助服务平台可以增加个人信息修改、请假申请和工资条确认签收功能,系统集成方面可以对接钉钉或企业微信的免登录。这套代码的基础架构比较清晰,在这些方向上做扩展不会太痛苦。对在校学生来说,选其中一两个扩展点作为论文的创新点也很有说服力。
我自己的实际体会是,企业工资管理系统虽然看起来是一个常见的课设题目,但把“核算幂等、数据留痕、权限隔离”这三件事做扎实了,系统的完成度和可用性就能明显超过同期的大部分作品。很多人在做这个题目时只顾着把CRUD写完,忽略了业务核心的特殊性,做出来的东西更像一个通用的增删改查模板。把工资核算背后的约束(不可重复执行、数据可追溯、金额敏感)想透,再回头去写代码,你的设计思路和实现方案都会有本质上的不同。希望这篇文章对你动手实践这个项目有帮助。