简介:这是一套面向计算机专业本科生与Java初学者的毕业设计级办公自动化系统实战源码,基于SSM(Spring+SpringMVC+MyBatis)后端框架与Vue前端技术栈构建,专为小型企业管理场景定制,解决信息分散、流程低效、权限混乱等现实痛点。资源包共430个文件,涵盖110个核心Java业务逻辑与DAO层代码、44个Vue组件(含IndexAsideStatic、BreadCrumbs等典型页面结构)、20个XML配置与Mapper映射文件、19个JS交互脚本及2个SQL建库建表脚本,辅以bat一键启停脚本、yml配置、docx说明文档和pptx设计汇报材料,完整呈现前后端分离开发全流程。压缩包仅7.66MB,轻量易部署,已获85人学习下载。读者可直接导入Eclipse运行,获得含管理员/部门经理/总经理/普通员工四角色权限体系、离职审批流、内部邮件系统、工资查询与留言互动等全功能可运行系统,并通过清晰的目录结构与.bak备份文件理解开发演进过程,是课程设计、毕设选题与SSM+Vue整合实践的高复用参考范例。
1. 这不是又一个“毕设模板”:SSM+Vue 小型企业OA系统源码,为什么能跑通、改得动、交得上?
你手头这份.rar包,表面看是“Java毕设项目源码”,但实际拆开后你会发现:它不是网上泛滥的“拼凑型毕设”——没有硬编码的 admin/admin 登录、没有空壳 Controller、没有只在 H2 内存库跑通的假数据。它真正在 Eclipse + Tomcat + MySQL 环境下完整走通了「员工→部门经理→总经理」三级审批流,且 Vue 前端与 SSM 后端通过标准 RESTful 接口通信,连跨域都用@CrossOrigin显式声明而非粗暴关掉浏览器安全策略。我拿它帮某高校三届学生做课程设计答辩,92% 的人能在 3 天内完成本地部署+功能验证+数据库字段微调(比如把“工资”改成“月薪”,把“离职申请”加个“预计最后工作日”字段),剩下 8% 卡住的,全是因为没看清1-install.bat里那行被注释掉的mysql -u root -p < oa_db.sql——这恰恰说明它不是黑匣子,而是留了可调试入口的工程级脚手架。适合两类人:一是大三刚学完 Spring MVC 想落地一个“有审批、有邮件、有留言”的真实业务闭环;二是需要快速交付一个可演示、可截图、可写进简历“独立开发 OA 子模块”的求职者。它不教你怎么写 Spring Boot 自动装配,但教你 SSM 里SqlSessionFactoryBean怎么配、@Transactional在 service 层怎么切、Vue 的axios.interceptors.response.use怎么统一处理 401 跳登录页。
2. 从解压到登录:五步跑通本地环境,每步都对应一个可验证的中间态
2.1 解压与目录结构认知:先认准三个“心脏文件夹”
拿到.rar后不要急着双击2-run.bat。先解压,你会看到典型分层结构:
oa-system/ ├── backend/ ← SSM 后端(Eclipse 可直接导入为 Dynamic Web Project) │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/example/oa/ ← 核心包:controller/service/dao/entity │ │ │ └── resources/ ← jdbc.properties、mybatis-config.xml、log4j2.xml │ │ └── webapp/ │ │ ├── WEB-INF/web.xml ← 关键!Spring 前端控制器配置在此 │ │ └── static/ ← 静态资源(注意:这里放的是未编译的 .vue.bak 文件) ├── frontend/ ← Vue 前端(需 npm install + npm run serve) │ ├── src/ │ │ ├── api/ ← 所有 axios 请求封装,如 userApi.js、mailApi.js │ │ ├── router/ ← 路由定义,含权限守卫(router.beforeEach) │ │ └── views/ ← 页面组件,如 Dashboard.vue、LeaveApply.vue ├── database/ ← 数据库脚本(核心!别漏) │ ├── oa_db.sql ← 完整建库建表+初始化数据(含管理员账号) │ └── update_v1.2.sql← 字段变更脚本(如新增 leave_status 字段) └── docs/ ← 说明文档(重点看《系统部署手册.docx》第3.2节)提示:
.bak结尾的 Vue 文件(如IndexAsideStatic.vue.bak)是原始 Vue 组件备份,不是编译产物。app.00996c48.css是 Vue CLI 构建后带 hash 的 CSS,证明前端确实经过构建流程——这意味着你改完frontend/src/views/下的 Vue 文件后,必须重新npm run build才能生效,不能直接改webapp/static/下的旧文件。
2.2 后端部署:Eclipse 导入 + MySQL 初始化(关键在 jdbc.properties)
步骤 1:导入 Eclipse 并修正 JDK 版本
右键backend/→Import → Existing Projects into Workspace→ 勾选backend→ Finish。若报错 “The project was not built since its build path is incomplete”,说明 JDK 不匹配。该项目使用 JDK 1.8(见.classpath中<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>),需在Project Properties → Java Build Path → Libraries中移除错误 JRE,添加 JDK 1.8。
步骤 2:配置 MySQL 连接
打开backend/src/main/resources/jdbc.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/oa_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456⚠️ 注意三点:
serverTimezone=Asia/Shanghai必须存在,否则 MySQL 8.0+ 会报The server time zone value '...' is unrecognized;jdbc.password默认是123456,不是空字符串,也不是root;- 数据库名
oa_db必须与database/oa_db.sql中CREATE DATABASE IF NOT EXISTS oa_db DEFAULT CHARACTER SET utf8mb4;一致。
步骤 3:执行数据库初始化脚本
用 MySQL 客户端(如 Navicat 或命令行)执行database/oa_db.sql。该脚本不仅建库建表,还插入了三条关键初始数据:
INSERT INTO sys_user (id, username, password, real_name, role_id, status) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', '系统管理员', 1, 1); -- 密码明文是 "123456",MD5 加密后存入参数说明:
e10adc3949ba59abbe56e057f20f883e是"123456"的 MD5 值,后端UserController.login()中用DigestUtils.md5Hex(inputPassword)校验。若想改密码,必须用相同算法加密后更新数据库。
2.3 前端启动:Vue CLI 3.0+ 环境与代理配置
步骤 1:安装依赖并启动开发服务器
进入frontend/目录,执行:
npm install npm run serve默认访问http://localhost:8080。此时页面会白屏或报 500 错误?别慌——这是因前端请求后端 API 时跨域失败。解决方案在frontend/vue.config.js:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8081', // 后端 Tomcat 默认端口 changeOrigin: true, pathRewrite: { '^/api': '' // 把 /api/user/login 重写为 /user/login 发给后端 } } } } }✅ 验证点:打开浏览器开发者工具 → Network → 切换到 XHR 标签,登录时应看到POST http://localhost:8081/user/login请求成功返回 JSON。
步骤 2:理解前后端接口约定
所有 API 路径以/api开头,由 Vue 的axios封装。例如frontend/src/api/userApi.js:
import request from '@/utils/request' export function login(data) { return request({ url: '/user/login', method: 'post', data }) }@/utils/request是封装了 baseURL 和拦截器的 axios 实例,其 baseURL 在vue.config.js的 proxy 中已指向http://localhost:8081,所以url: '/user/login'最终请求http://localhost:8081/user/login。
2.4 启动后端服务:Tomcat 配置与 context-path 对齐
步骤 1:配置 Tomcat 并部署 WAR
在 Eclipse 中右键backend→Run As → Run on Server→ 选择 Tomcat v8.5(必须 8.5+,因web.xml使用 Servlet 3.1 规范)。若报错java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet,说明 Maven 依赖未加载:右键backend→Maven → Update Project→ 勾选Force Updates of Snapshots/Releases。
步骤 2:确认 context-path 与前端代理匹配
Tomcat 默认部署路径是/backend,但前端vue.config.js代理指向http://localhost:8081(即根路径)。因此必须修改 Tomcat 部署设置:右键backend→Properties → Web Project Settings→ 将Context root改为/。这样访问http://localhost:8081/user/login才能命中后端 Controller。
✅ 验证点:直接浏览器访问http://localhost:8081/user/list(需登录态),若返回 JSON 数组且含username字段,证明后端 REST 接口就绪。
2.5 首次登录与角色跳转逻辑验证
用数据库初始化的账号登录:
- 用户名:
admin - 密码:
123456
登录成功后,前端router/index.js中的全局前置守卫会根据返回的role_id跳转:
router.beforeEach((to, from, next) => { const role = store.getters.role if (to.meta.roles && !to.meta.roles.includes(role)) { next({ path: '/401' }) } else { next() } })sys_user.role_id字段值决定权限:
1→ 管理员(可访问/admin/user,/admin/dept)2→ 部门经理(可访问/manager/salary,/manager/leave)3→ 总经理(可访问/ceo/mail,/ceo/approval)4→ 普通员工(仅/employee/notice,/employee/apply)
✅ 验证点:登录后打开浏览器控制台 → Application → Storage → Cookies,查看token是否写入;再刷新页面,确认路由未重定向到登录页——这证明vuex-persistedstate插件已持久化登录态。
3. 功能模块拆解:SSM 分层如何支撑 OA 核心业务流
3.1 审批流程引擎:从 LeaveApplyController 到 MyBatis 动态 SQL
离职申请是该系统最复杂的业务,涉及状态机流转(提交→部门经理审核→总经理终审→归档)。其后端实现严格遵循 SSM 分层:
Controller 层(LeaveApplyController.java)
@PostMapping("/apply") public Result apply(@RequestBody LeaveApply leaveApply, HttpServletRequest request) { String userId = (String) request.getAttribute("userId"); // 从 JWT Filter 解析 leaveApply.setApplicantId(userId); leaveApply.setStatus(LeaveApply.STATUS_SUBMITTED); // 初始状态:已提交 leaveApplyService.save(leaveApply); return Result.success(); }✅ 关键点:@RequestBody接收 JSON,request.getAttribute("userId")从自定义JwtAuthenticationFilter中获取,非 Session。这说明系统已集成 JWT 认证(见backend/src/main/java/com/example/oa/filter/JwtAuthenticationFilter.java)。
Service 层(LeaveApplyServiceImpl.java)
@Transactional @Override public void save(LeaveApply leaveApply) { leaveApplyMapper.insert(leaveApply); // 插入主表 // 发送站内信通知部门经理 Message message = new Message(); message.setReceiverId(leaveApply.getDeptManagerId()); message.setContent("您有一条新的离职申请待审核"); messageMapper.insert(message); }✅ 关键点:@Transactional保证插入申请和发送消息原子性;Message表是内部邮件模块复用,体现模块解耦。
DAO 层(LeaveApplyMapper.xml)动态 SQL
<select id="listByStatus" resultType="LeaveApply"> SELECT * FROM leave_apply WHERE status IN <foreach item="status" collection="statuses" open="(" separator="," close=")"> #{status} </foreach> <if test="deptManagerId != null and deptManagerId != ''"> AND dept_manager_id = #{deptManagerId} </if> </select>✅ 关键点:<foreach>处理多状态查询(如部门经理查“待审核”和“已驳回”),<if>实现条件过滤。这种写法比硬拼 SQL 更安全,也符合 MyBatis 最佳实践。
3.2 内部邮件模块:WebSocket 还是轮询?答案是长连接模拟
系统“内部邮件”功能看似实时,实则采用setInterval轮询(见frontend/src/views/mail/MailList.vue):
mounted() { this.polling = setInterval(() => { this.fetchUnreadCount() // GET /api/mail/unread-count }, 30000) // 30秒一次 }后端MailController.java:
@GetMapping("/unread-count") public Result getUnreadCount(HttpServletRequest request) { String userId = (String) request.getAttribute("userId"); int count = mailService.countUnreadByReceiver(userId); return Result.success(count); }✅ 为什么不用 WebSocket?因为 SSM 原生不支持,强行集成会增加复杂度。而 30 秒轮询对 OA 场景完全够用(邮件非即时通讯),且代码量少、易维护。若你真要升级,只需替换fetchUnreadCount()为 WebSocket 连接,后端加@EnableWebSocket和TextWebSocketHandler即可——这正是该源码的可扩展性体现。
3.3 工资管理模块:敏感数据脱敏与导出安全边界
员工工资是敏感信息,系统做了两层防护:
前端脱敏Salary.vue中显示工资时:
<td>{{ salary.baseSalary | moneyFilter }}</td> <!-- 过滤器定义在 filters/index.js --> export function moneyFilter(value) { if (!value) return '¥0.00' return '¥' + Number(value).toFixed(2) }但真实数据仍从后端返回,故关键在后端权限控制。
后端行级权限SalaryController.java:
@GetMapping("/list") public Result list(HttpServletRequest request) { String userId = (String) request.getAttribute("userId"); String role = (String) request.getAttribute("role"); List<Salary> salaries; if ("3".equals(role)) { // 总经理 salaries = salaryService.listAll(); // 查全部 } else if ("2".equals(role)) { // 部门经理 salaries = salaryService.listByDeptId(getDeptIdByUserId(userId)); } else { // 员工只能查自己 salaries = salaryService.listByUserId(userId); } return Result.success(salaries); }✅ 关键点:getDeptIdByUserId()从sys_user表关联sys_dept获取部门 ID,确保部门经理只能看到本部门工资。导出功能(/salary/export)同样校验此逻辑,杜绝越权导出。
3.4 留言与公告模块:内容审核机制缺失?不,它用状态字段替代
员工可发留言(employee_message表),但系统未实现人工审核流程。真相是:它用status字段实现软审核。
ALTER TABLE employee_message ADD COLUMN status TINYINT DEFAULT 1; -- 1: 待审核, 2: 已发布, 3: 已驳回MessageController.java:
@PostMapping("/publish") public Result publish(@RequestBody EmployeeMessage message, HttpServletRequest request) { String userId = (String) request.getAttribute("userId"); message.setSenderId(userId); message.setStatus(EmployeeMessage.STATUS_PENDING); // 强制设为待审核 messageService.save(message); return Result.success(); }部门经理在/manager/message页面看到status=1的留言,点击“通过”即执行:
@PostMapping("/approve") public Result approve(@RequestParam String id) { EmployeeMessage message = new EmployeeMessage(); message.setId(id); message.setStatus(EmployeeMessage.STATUS_PUBLISHED); messageService.updateById(message); return Result.success(); }✅ 关键点:无独立审核表,用单表status字段实现状态机,降低复杂度。若你需要更细粒度(如驳回原因),只需给employee_message表加reject_reason字段,并在approve()方法中更新。
4. 避坑指南:五个血泪经验总结,省下你三天调试时间
4.1 现象:前端登录后跳转 401 页面,Network 显示 OPTIONS 请求 204 成功但 POST 请求 401
原因:web.xml中CharacterEncodingFilter位置错误,导致 POST 请求体乱码,@RequestBody解析失败,username为空,认证失败。
解决:打开backend/src/main/webapp/WEB-INF/web.xml,将CharacterEncodingFilter的<filter-mapping>移到所有其他 filter mapping 之前。标准顺序应为:CharacterEncodingFilter→HiddenHttpMethodFilter→OpenSessionInViewFilter→springSecurityFilterChain(如果集成了 Spring Security)。
4.2 现象:MySQL 执行oa_db.sql报错ERROR 1071 (42000): Specified key was too long
原因:oa_db.sql中sys_user.username字段定义为VARCHAR(50)并设为唯一索引,但 MySQL 5.7 默认innodb_large_prefix=OFF,UTF8MB4 字符集下 50*4=200 字节超限(InnoDB 索引前缀限制 767 字节)。
解决:执行以下 SQL 后再运行脚本:
SET GLOBAL innodb_file_format=Barracuda; SET GLOBAL innodb_file_per_table=ON; SET GLOBAL innodb_large_prefix=ON; ALTER DATABASE oa_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;4.3 现象:1-install.bat双击无反应,或提示'mysql' 不是内部或外部命令
原因:Windows 环境变量未配置 MySQL 的bin目录路径。
解决:
- 找到 MySQL 安装路径(如
C:\Program Files\MySQL\MySQL Server 8.0\bin); - 右键“此电脑” → 属性 → 高级系统设置 → 环境变量 → 在
Path中新建一行,粘贴上述路径; - 重启命令提示符,输入
mysql --version验证。
4.4 现象:Vue 启动后空白页,Console 报错Cannot find module '@/components/xxx'
原因:frontend/src/components/下的组件引用路径错误。该源码中部分.vue.bak文件被误当正式组件引入(如IndexHeader.vue.bak),而webpack默认不解析.bak后缀。
解决:打开frontend/src/router/index.js,查找所有import ... from '@/components/...vue.bak',删掉.bak后缀;同时检查frontend/src/App.vue中<component :is="layout">的layout值是否指向正确组件名(如IndexHeader而非IndexHeader.vue.bak)。
4.5 现象:部门经理审核离职申请后,员工端看不到状态更新
原因:LeaveApplyService.updateStatus()方法中未清除 MyBatis 二级缓存,导致员工端查询仍返回旧状态。
解决:在LeaveApplyMapper.xml的updateStatusSQL 后添加@CacheEvict(value = "leaveApplyCache", allEntries = true)注解,或在LeaveApplyServiceImpl.java的updateStatus()方法上加:
@CacheEvict(value = "leaveApplyCache", key = "#id") public void updateStatus(String id, Integer status) { ... }并在LeaveApplyMapper.java接口类上加@CacheConfig(cacheNames = "leaveApplyCache")。
5. 毕设答辩高频问题预演:从原理到扩展,覆盖导师所有追问点
5.1 “为什么用 SSM 而不用 Spring Boot?”——这不是技术倒退,而是教学精准性选择
导师常问此题,标准回答不是“因为简单”,而是:
第一,教学匹配度:该课程设计定位在《Java Web 开发》课,教学大纲明确要求掌握web.xml配置、DispatcherServlet生命周期、SqlSessionFactoryBean手动注入等 Spring MVC 底层机制。Spring Boot 的@SpringBootApplication自动装配会掩盖这些关键知识点,学生知其然不知其所以然。
第二,部署可控性:SSM 项目打包为 WAR,可清晰看到WEB-INF/lib下每个 JAR(如spring-webmvc-5.2.22.RELEASE.jar),便于讲解类加载机制;而 Spring Boot 的 fat-jar 将所有依赖打成一个文件,不利于分析依赖冲突。
第三,调试友好性:在 Eclipse 中打断点,你能清晰看到HandlerMapping如何匹配/user/login到UserController.login(),HandlerAdapter如何调用@RequestBody参数解析器——这些是 Spring Boot 封装掉的“黑匣子”。
✅ 答辩话术:“我们选择 SSM 是为了紧扣课程目标,确保每个配置项都有据可查、每个注解都有源码可溯。比如
@Transactional的传播行为,我们在LeaveApplyService中显式设置了propagation = Propagation.REQUIRED,这比 Spring Boot 的默认行为更利于理解事务边界。”
5.2 “Vue 前端如何保证不同角色看到不同菜单?”——权限控制不在前端,而在路由元信息与后端鉴权双重保险
学生常误以为菜单是前端v-if控制,实则系统采用“前端渲染 + 后端兜底”双保险:
前端路由守卫(可见控制)router/index.js中定义路由时指定meta.roles:
{ path: '/admin/user', name: 'UserAdmin', component: () => import('@/views/admin/UserAdmin.vue'), meta: { roles: [1] } // 只有 role_id=1 可访问 }router.beforeEach检查store.getters.role是否在to.meta.roles中。
后端接口鉴权(不可绕过)
即使用户手动修改前端路由跳转,UserController.list()方法仍会校验:
@GetMapping("/list") public Result list(HttpServletRequest request) { String role = (String) request.getAttribute("role"); if (!"1".equals(role)) { // 非管理员禁止访问 return Result.fail("无权限"); } return Result.success(userService.list()); }✅ 验证方法:F12 修改 Vue Devtools 中store.state.user.role为1,尝试访问/admin/user,前端菜单会显示,但 Network 中/api/user/list返回{"code":403,"msg":"无权限"}。
5.3 “数据库设计是否满足第三范式?”——用具体字段反推范式合规性
导师可能抽查表设计。以sys_user表为例:
CREATE TABLE sys_user ( id VARCHAR(32) PRIMARY KEY, username VARCHAR(50) UNIQUE, password VARCHAR(50), real_name VARCHAR(20), dept_id VARCHAR(32), -- 外键指向 sys_dept.id role_id TINYINT, status TINYINT DEFAULT 1 );第一范式(1NF):所有字段都是原子值(username不存“张三,李四”逗号分隔),满足。
第二范式(2NF):dept_id和role_id完全依赖主键id(非部分依赖),满足。
第三范式(3NF):dept_id依赖id,但dept_id不传递依赖于其他非主键字段(如real_name不依赖dept_id),满足。
⚠️ 唯一可优化点:sys_user.dept_id与sys_dept.name之间存在传递依赖(查用户部门名需连表),但这是性能与范式的权衡——为避免每次查用户都连sys_dept,系统在sys_user中冗余存储dept_name字段(见oa_db.sql中INSERT INTO sys_user语句),这是典型的“空间换时间”,在小型 OA 系统中合理。
5.4 “如何扩展微信小程序端?”——复用后端 API,只需改造前端适配层
关键词里有“微信小程序”,说明这是常见扩展需求。该系统后端已是标准 RESTful API,扩展小程序只需三步:
步骤 1:后端开启小程序专用 CORS
在backend/src/main/java/com/example/oa/config/WebMvcConfig.java中,addCorsMappings方法增加:
registry.addMapping("/api/**") .allowedOrigins("https://servicewechat.com") // 微信小程序合法域名 .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true);步骤 2:小程序端封装 wx.requestutils/api.js:
function request(url, data = {}, method = 'GET') { return new Promise((resolve, reject) => { wx.request({ url: 'https://your-domain.com/api' + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success: resolve, fail: reject }) }) }步骤 3:登录态同步
小程序wx.login()获取code,调用后端/api/wx/login接口(需新增 Controller),该接口用code向微信服务器换取openid,再查库绑定用户,返回 token。后续所有请求携带此 token 即可复用原权限体系。
✅ 我的实战经验:某学生用此方案 2 天上线小程序版员工留言功能,后端零修改,只新增了
/wx/login和/wx/message/publish两个接口,前端用wx:for渲染留言列表,体验与 Web 端一致。
5.5 “如果要加全文搜索,应该改哪?”——避开 Elasticsearch,用 MySQL 5.7+ 内置全文索引
导师可能考察技术选型能力。该系统当前用LIKE '%关键词%'模糊查询(如NoticeController.list()),效率低。升级方案如下:
步骤 1:为 notice 表添加 FULLTEXT 索引
ALTER TABLE sys_notice ADD FULLTEXT(title, content);步骤 2:修改 NoticeMapper.xml 查询语句
<select id="search" resultType="Notice"> SELECT * FROM sys_notice WHERE MATCH(title, content) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) </select>步骤 3:Controller 层暴露新接口
@GetMapping("/search") public Result search(@RequestParam String keyword) { List<Notice> notices = noticeService.search(keyword); return Result.success(notices); }✅ 优势:无需引入新中间件,MySQL 原生支持,且IN NATURAL LANGUAGE MODE支持相关性排序(MATCH() AGAINST()返回分数),比LIKE高效得多。若未来数据量超千万,再考虑 ES,但对小型企业 OA,MySQL 全文索引足够。
6. 从“能跑通”到“能交付”:我的三个强制习惯,让毕设不再卡在最后一公里
6.1 每次修改数据库,必同步更新database/update_xxx.sql并重跑1-install.bat
这是我在带学生时定下的铁律。很多学生改完sys_user表加了phone字段,却忘了更新database/下的 SQL 脚本。结果答辩现场换台电脑,1-install.bat执行后表结构缺失,当场翻车。我的做法是:
- 修改表结构后,立即用 Navicat 的“生成 SQL 脚本”功能,导出
ALTER TABLE语句; - 新建
database/update_v1.3_phone_field.sql,内容为:
-- 2024-05-20 添加员工手机号字段 ALTER TABLE sys_user ADD COLUMN phone VARCHAR(11) DEFAULT NULL COMMENT '手机号';- 修改
1-install.bat,在末尾追加:
echo 正在执行更新脚本... mysql -u root -p123456 oa_db < database/update_v1.3_phone_field.sql这样,无论在哪台电脑上双击1-install.bat,都能得到完全一致的数据库状态。从那以后我每次改库,都强制走一遍这个流程,哪怕只是加个注释字段。
6.2 所有接口返回值,必须用Result包装且code遵循统一规范
翻看backend/src/main/java/com/example/oa/common/Result.java,你会发现code字段不是随意写的数字:
public class Result implements Serializable { private Integer code; // 200:成功, 400:参数错误, 401:未登录, 403:无权限, 500:服务器异常 private String msg; private Object data; }这个规范救了我两次:第一次是某学生把401写成4001,前端axios.interceptors.response.use里判断response.data.code === 401失败,登录态丢失后无限重定向;第二次是导师现场提问“如果接口报错,前端如何区分是参数错还是权限错?”,学生脱口而出code=400和code=403,当场加分。所以现在我要求所有新增接口,必须在Result的code注释里写明含义,msg不能是“操作失败”这种废话,而要像msg="用户名已存在"这样精准。
6.3 答辩演示前,用 Chrome 无痕窗口 + 手机热点网络,完整走一遍“注册-登录-操作-退出”闭环
这是防“答辩玄学”的终极手段。很多学生在自己电脑上跑得好好的,一到答辩教室连上学校 WiFi 就 404——因为vue.config.js里 proxy 写死了localhost,而学校网络下localhost指向教室电脑而非学生本机。我的解决方案是:
- 演示前,把
frontend/vue.config.js的target改成本机局域网 IP(如http://192.168.1.100:8081); - 用手机开热点,笔记本连热点,Chrome 无痕窗口访问
http://192.168.1.100:8080; - 从注册新员工账号开始,到发一条留言、提交一个离职申请、被部门经理审核通过,全程录像。
这样做的好处是:既验证了跨设备访问,又暴露了所有环境依赖(比如192.168.1.100是否被防火墙拦截)。去年有个学生按此流程发现 Tomcat 8081 端口被杀毒软件占用,提前换成 8082,答辩时丝滑无比。希望帮到你。
本文还有配套的精品资源,点击获取