
简介面向计算机相关专业毕业设计与课程设计场景这份基于SSM框架的广告管理系统以Java为后端、Vue为前端采用B/S架构和MySQL数据库完整实现管理员、广告客户、广告商、广告制作商四类角色围绕广告投放渠道管理、需求提案、设计交付、签署与结算等核心业务形成闭环适合需要完整项目参考的学生快速复用。资源共540个文件约83.42MB以Java源码、Vue页面、SVG图标、JPG截图、XML配置及SQL脚本为主并附带部署运行脚本与演示视频目录按后端代码、前端页面和数据库脚本分层便于直接导入开发环境核对效果。已有150人学习下载。通过项目可掌握SSM整合、Vue前后端联调、权限分角色设计及广告业务建模思路是一份可用于开题参照、功能扩展和答辩讲解的成套毕业设计资料。1. 为什么Java毕业设计选SSM广告管理系统四角色权限就是答辩加分项Java毕业设计围着一套SSM框架转最怕的不是代码写不完而是做出来的东西只有CRUD答辩时讲不出业务逻辑。这套基于SSMVue的广告管理系统功能面是完整的管理员、广告客户、广告商、广告制作商四个角色从广告需求、提案、设计预览到签署结算一条业务链路贯穿所有核心表。对正在做毕业设计或课程设计的人来说它最大的价值是帮你省掉设计权限边界和状态流转的精力这两块正是评委最爱追问的点。适合手里已经有源码但跑不起来、或者想把功能讲成完整业务故事的人。下面按项目结构、部署方式、业务实现和踩坑排查四个角度拆一遍哪些能直接用哪些需要自己填。2. 系统角色与权限设计四类账号的权限边界怎么画2.1 功能矩阵把投放链路拆给评委看一条广告从想法到投放实际要经过需求方、代理方、制作方三个角色接力再加上管理员的统一管控正好对应项目里的四类账号。拆这套系统时第一步不是看代码而是先画功能矩阵。答辩第一问十有八九是你这个系统有哪些角色、各自能做什么这张表就是标准答案。角色核心模块关键操作管理员个人中心、广告客户管理、广告商管理、广告制作商管理、广告投放渠道管理账号增删改、投放渠道配置广告客户个人中心、广告投放渠道管理、广告需求管理、广告提案管理、广告设计预览管理、广告调整信息管理、广告签署管理、广告结算管理提交需求、审核提案、预览设计图、提出调整、签署、结算广告商个人中心、广告需求管理、广告提案管理、广告制作需求管理、广告设计图交付管理、广告设计预览管理、广告调整信息管理、广告签署管理、广告结算管理承接需求、提交提案、发起制作、交付设计图广告制作商个人中心、广告制作需求管理、广告设计图交付管理接收制作任务、上传设计图这四层权限的划分是有讲究的管理员只管人和渠道不碰业务单据广告客户是需求发起方和最终确认方所以签署和结算都攥在他手里广告商是中间调度既接客户需求又给制作商派单广告制作商只干执行做完设计图交付就结束。这种边界清晰的分工答辩时要当成系统设计亮点来讲比背几个Controller类名有说服力得多。2.2 数据库表设计角色字段放在用户表还是单独建表这种规模的毕设一般不需要做五张表的RBAC权限模型常见的做法是在用户表里放一个role字段前端靠role渲染菜单后端靠拦截器做统一校验。表数量少答辩讲起来不绕缺点是想加细粒度权限时得回头改造。CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT MD5加密后的密码, role INT NOT NULL DEFAULT 1 COMMENT 角色: 1管理员 2广告客户 3广告商 4广告制作商, real_name VARCHAR(50) COMMENT 真实姓名, phone VARCHAR(20) COMMENT 手机号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;角色用INT存是SSM项目里的常见做法查询时靠常量类做映射比直接存字符串省空间代价是SQL和Java代码里容易出现裸数字。我一般会顺手建一个UserRole常量类把1、2、3、4定义成有名字的常量Service层就不会出现意思不明的魔法值。username加唯一索引是为登录等值查询服务同时兜底防重复注册。password字段留64位是因为MD5固定32位但要是后期想升级成BCrypt加盐加密64位才放得下。对应的登录查询在MyBatis里长这样Select(SELECT id, username, real_name, role, phone FROM t_user WHERE username #{username} AND password #{password}) User login(Param(username) String username, Param(password) String password);Param的作用是给SQL里#{}占位符绑定参数名漏掉会直接抛MyBatis的BindingException。这条SQL只做凭证校验不做权限判断因为用户角色是从查询结果里直接取的登录成功后放进Session。需要提一句明文密码查询虽然演示方便但评委追问安全性时至少得回答生产环境要加盐加密或换Spring Security别硬说MD5绝对安全。2.3 登录校验与拦截器后端闸门放在哪一层SSM项目里真正拦截请求的是SpringMVC的HandlerInterceptor。preHandle在进入Controller之前执行返回false就中断请求这是整个系统最值得讲清楚的一层。拆这种项目时发现拦截器通常只做登录校验不做角色校验属于够用但不深入二次开发时值得补一层角色判断。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object loginUser session.getAttribute(loginUser); String uri request.getRequestURI(); // 登录接口和静态资源必须放行否则页面都打不开 if (uri.contains(/login) || uri.contains(/static/) || uri.contains(/error)) { return true; } if (loginUser null) { // 未登录页面请求重定向到登录页接口请求直接返回401 if (uri.startsWith(/api/)) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\未登录\}); } else { response.sendRedirect(request.getContextPath() /login.html); } return false; } return true; } }这段代码的核心是分层放行登录接口不能拦拦了会死循环静态资源不能拦拦了样式和脚本全挂业务接口则必须校验Session。注意对接口请求返回401而不是跳转页面因为Vue前端拿到401会自己控制跳转而不是被HTML登录页糊一脸。如果项目不是前后端分离.html页面直接由Tomcat渲染那第二种跳转方式更合适。配置到SpringMVC时拦截路径要写/**别只配*.do否则REST风格的接口会漏过去。mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ bean classcom.ad.interceptor.AuthInterceptor/ /mvc:interceptor /mvc:interceptorsexclude-mapping的白名单必须和preHandle里的放行逻辑保持一致两处不一致是个很隐蔽的坑你在代码里放行了/loginXML里忘了除外结果登录接口照样被拦。我一般建议白名单集中在XML里维护Java代码只做Session判断这样排查时只看一处就行。提示Session里的loginUser建议存一个只含id、role、realName的轻量对象别把整个User实体塞进去每次请求都带着一堆用不上的字段纯属浪费。3. 部署与项目结构从.bak文件和三个bat脚本还原运行链路3.1 资源包里的.bak文件是什么别把它们当垃圾删掉打开压缩包最常见的第一反应是把所有.bak文件当垃圾删除。但在这种毕设包里.bak恰恰是后悔药。update-password.vue.bak、IndexAsideStatic.vue.bak、BreadCrumbs.vue.bak、IndexHeader.vue.bak这四个文件是前端工程改版之前的副本文档。大概率是之前的开发者改侧边栏、头部布局或密码组件时怕改坏复制了一份原文件带.bak后缀放在同目录。项目根目录里还有.classpath和org.eclipse.wst.common.component这类文件说明工程原本用Eclipse Web项目的方式维护导出的压缩包把这些历史文件一并带出来了。恢复方法很简单把目标.bak复制一份去掉.bak后缀。比如IndexAsideStatic.vue.bak复制成IndexAsideStatic.vue被改坏的侧边栏组件就回来了。但如果同目录同时存在同名.vue和.vue.bak说明代码改到一半停了此时对比两个文件修改时间以版本完整的那个为准。答辩前务必做这个检查因为有些.bak文件反而是完整版本而.vue文件是改残的版本直接启动就报编译错误。3.2 1-install / 2-run / 3-build三个批处理脚本的分工脚本命名已经说明了职责安装、运行、打包三步分离。在不同机器上还原这类SSM项目时发现脚本内容大同小异第一段负责建库导SQL和编译后端第二段启动后端服务第三段构建Vue前端。下面是按这类项目最常见的写法还原的示例具体内容以你手里的压缩包为准。echo off chcp 65001 nul rem 1-install.bat初始化数据库并编译后端 echo 正在创建数据库... mysql -uroot -proot -e CREATE DATABASE IF NOT EXISTS ad_system DEFAULT CHARSET utf8mb4 echo 正在导入表结构和初始数据... mysql -uroot -proot ad_system sql\ad_system.sql echo 正在编译后端跳过测试... mvn clean install -DskipTests echo 安装完成 pausechcp 65001是把控制台切成UTF-8编码不然bat里的中文注释会显示成乱码注意这只是命令提示符的显示问题不影响脚本执行。mysql -uroot -proot里root是账号和密码实际部署时必须改成你本机MySQL的真实凭据。CREATE DATABASE用IF NOT EXISTS是防重复执行报错很实用。mvn加-DskipTests跳过单元测试是因为测试类如果依赖数据库环境在全新机器上会卡住跳过是省事且安全的做法。echo off rem 2-run.bat启动后端服务 cd /d %~dp0 echo 启动Tomcat默认端口8080... mvn tomcat7:run pausecd /d %~dp0的作用是切到脚本自身所在目录避免双击运行时工作目录落到系统目录下导致找不到pom.xml。mvn tomcat7:run依赖pom.xml里配置的tomcat7-maven-plugin插件这类SSM毕设十有八九是靠它起的服务。如果项目是传统WAR包结构改成外部Tomcat部署也行把WAR拷进webapps后调startup.bat。启动后看到Starting ProtocolHandler日志说明端口已起来运行窗口保持别关按CtrlC才会停服务。echo off rem 3-build.bat构建Vue前端 cd /d %~dp0\frontend call npm install call npm run build echo 前端打包完成输出目录frontend\dist pausenpm install首次执行通常很慢国内网络环境下失败重跑是常态这不是脚本问题。npm run build产出纯静态文件到dist目录可以交给Nginx托管也可以直接把dist内容拷到后端webapp目录由Tomcat统一托管。如果只是答辩演示我更推荐用Vue开发模式起前端改代码即时生效遇到演示中发现问题还能现场兜底不必反复打包。3.3 前端Vue与后端SSM联调代理还是CORS前端开发服务器跑3000端口后端SSM跑8080端口一跨域就出幺蛾子。这个项目里前端通过axios调后端常用解法有两种前端配devServer代理或者后端开CORS。我的习惯是用代理方案对后端代码零侵入答辩时也好讲——前端主动适配后端接口更接近工程化实践。// vue.config.js module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, // 把 /api 前缀去掉后端Controller无需写统一前缀 pathRewrite: { ^/api: } } } } };代理机制的核心是changeOrigin它会把请求头里的Host字段改写成目标地址后端收到的请求就变成了同源。前端请求路径带/api前缀代理转发到后端时会被pathRewrite剥掉所以后端Controller里不要出现/api路径前缀否则URL会变成/api/api/login这种双层拼接错误。前后端分离项目里所有接口请求统一走baseURL保证代理规则全局生效。// src/utils/request.js const service axios.create({ baseURL: /api, // 统一走代理前缀 timeout: 10000, // 10秒超时防止后端卡死时前端一直转圈 withCredentials: true // 跨域请求携带Cookie保证Session不丢 });timeout字段给你的接口调用设定10秒上限后端如果启动到一半或SQL卡死前端能快速失败而不是让评委盯着转圈等一分钟。withCredentials是Cookie伴随跨域请求发送的开关下面CORS配置必须和它配对使用。如果项目不走代理而是直连后端那后端必须开CORS才有救Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:3000) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }这组配置里有个容易翻车的细节allowCredentials(true)一旦开启allowedOrigins就不能写*通配符必须写具体前端地址否则浏览器直接拒绝响应控制台报CORS错误。allowedMethods必须包含OPTIONS因为跨域复杂请求会先发预检请求缺了它预检直接失败所有POST接口看起来像超时。maxAge(3600)的意思是预检结果缓存一小时能显著减少OPTIONS请求次数提升调试验证时的响应速度。4. 核心业务流实现从广告需求到结算的状态机与文件交付4.1 需求—提案—设计—签署状态机怎么设计不漏状态广告需求从创建到结算在数据库里表现为一条记录的状态反复流转在前端则对应着需求、提案、制作、预览、签署、结算六个模块。把它整个项目里的流程梳理成下面这条状态链答辩时可以对着流程图讲清楚每一步的触发条件。状态值状态含义触发操作0待受理客户提交需求广告商可查看并提交提案1提案中广告商已提交提案等待客户审核2设计中提案通过生成制作需求广告制作商接手3待预览设计图已交付客户可预览4调整中客户提出调整意见制作商修改后重新交付5待签署预览通过双方准备签署6已结算结算完成流程终止状态从0走到6每一步都会触发下游记录的新增提案表、制作需求表、设计图交付表、签署表、结算表是典型的主表状态驱动从表动作结构。如果建表时漏掉某个状态最常见的表现是前端菜单点了没数据、流程走不通比如设计图还没交付签署按钮却已经能点了这种逻辑漏洞在答辩演示时非常显眼。状态更新不能粗暴写成SET status 新值必须带上当前状态条件这是拆这套系统时最想强调的一个细节-- 提案审核通过流程从 提案中(1) 转到 设计中(2) UPDATE t_ad_requirement SET status 2, update_time NOW() WHERE id #{reqId} AND status 1这个写法的价值在于防并发乱序。如果两个请求同时改同一条需求不带status条件的更新会互相覆盖带了status 1条件后同时只会有一条SQL执行成功另一条影响行数为0程序里判断update方法的返回值就能知道状态不对直接给前端返回当前状态不可操作。这种状态守卫在答辩现场属于加分点因为绝大多数同类项目都没有这层保护。4.2 设计图交付文件上传、重命名与虚拟路径映射广告制作商上传设计图是文件流进入系统的核心过程。SpringMVC里用MultipartFile接收文件拆这类项目时看到的大部分实现都是下面的套路核心处理有三个点空文件校验、按日期分目录、UUID重命名。PostMapping(/design/deliver) ResponseBody public Result deliverDesign(RequestParam(designId) Integer designId, RequestParam(file) MultipartFile file) { if (file null || file.isEmpty()) { return Result.error(请选择要上传的设计图); } // 按日期分目录避免所有文件堆在一个目录里 String dir D:/upload/design/ new SimpleDateFormat(yyyyMMdd).format(new Date()); File folder new File(dir); if (!folder.exists()) { folder.mkdirs(); } // 用UUID重命名保留原扩展名防止中文文件名乱码 String ext StringUtils.getFilenameExtension(file.getOriginalFilename()); String filename UUID.randomUUID().toString().replace(-, ) . ext; file.transferTo(new File(folder, filename)); // 数据库只存相对Web路径页面通过虚拟映射访问 designService.saveDeliver(designId, /upload/design/ filename); return Result.ok(); }RequestParam(file)要求前端FormData里的文件字段名必须叫file对不上就抛MissingServletRequestParameterException。UUID重命名同时解决两个隐患中文文件名在部分Tomcat版本下会乱码多人传同名文件会互相覆盖。数据库只存相对路径不存磁盘绝对路径好处是换机器部署时历史数据不用改只要保持虚拟映射规则一致就行。要让页面能访问这个文件SpringMVC配置里必须加一个虚拟目录映射把Web请求的/upload/**指到磁盘上的真实文件夹mvc:resources mapping/upload/** locationfile:D:/upload/ /mapping是浏览器访问时用的URL路径location是服务器磁盘路径这个写法只对本地磁盘生效。如果部署到Linux服务器location要改成file:/home/user/upload/这样的绝对路径。上传大小默认只有1MB左右设计图一般远超这个数需要单独调大multipartResolverbean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value52428800/ property namedefaultEncoding valueUTF-8/ /beanmaxUploadSize单位是字节52428800换算下来是50MB。defaultEncoding必须设成UTF-8否则带中文名的文件上传后会乱码。这两处配置漏掉任何一个上传功能都会以很诡异的方式失败而且控制台不一定有明显的报错日志。4.3 结算模块金额精度与幂等更新结算模块是广告客户给广告商付款的账务节点核心表t_settlement至少要有金额、结算状态、关联需求ID三个字段。这个模块最大的坑不是逻辑复杂而是金额精度。项目里如果拿double来算钱0.1加0.2这种老问题迟早会冒出来演示时界面上算出200.0000000001这种数评委一眼就知道代码不严谨。// 错误示范金额用 double 计算存在精度误差 // double total 100.1 99.9; // 正确做法全程 BigDecimal金额从字符串构造 BigDecimal amount new BigDecimal(1000.00); // 合同金额 BigDecimal taxRate new BigDecimal(0.06); // 税率6% BigDecimal tax amount.multiply(taxRate).setScale(2, RoundingMode.HALF_UP); BigDecimal payAmount amount.add(tax);BigDecimal构造必须传字符串传new BigDecimal(0.06)会得到一个带一长串小数位的近似值精度反而更差。setScale(2, RoundingMode.HALF_UP)表示保留两位小数、四舍五入是财务计算的常规要求。计算完写入数据库时字段类型必须是DECIMAL(10,2)而不是DOUBLE否则结算单金额在数据库里又会变成浮点存储前面的精度控制前功尽弃。结算状态更新同样要带状态守卫防止重复入账UPDATE t_settlement SET status 已结算, settle_time NOW(), pay_amount #{payAmount} WHERE id #{settleId} AND status 待结算这条SQL返回的影响行数为0就说明这张结算单已经是已结算状态程序直接提示该结算单已处理请勿重复提交。这个幂等逻辑在演示时特别有用因为现场演示连点按钮是常态没有这层保护一张单子会被重复结算两次。道理和4.1里的状态守卫一脉相承属于同一个设计思路在不同模块的复用。5. 避坑与排查SSMVue项目最容易翻车的六个细节5.1 MySQL驱动与时区启动即报错的第一关现象1-install.bat执行完数据库也建了一启动Tomcat就抛ClassNotFoundException或者Communications link failure日志里往往还带着Server returns invalid timezone字样。 原因本地MySQL是8.0以上版本但项目pom.xml里引的驱动还是5.x或者驱动版本没问题连接URL缺了serverTimezone参数MySQL 8对时区参数是强制要求不填直接拒连。 解决先把pom里的mysql-connector-java版本对齐到8.0.x再把jdbc连接串改成jdbc:mysql://localhost:3306/ad_system?useUnicodetruecharacterEncodingutf8serverTimezoneGMT%2B8。改完重建库重启驱动和时区的双重问题一次解决。我一般会在答辩前检查一遍jdbc.properties这类问题在评委的电脑上出现概率极高。5.2 中文乱码一条链上的三个环节都要查现象前端输入中文提交后数据库里存成了问号或者数据库里存得好好的页面查出来显示乱码。 原因问题可能藏在三个环节里。请求参数没走UTF-8编码、数据库连接串没指定characterEncoding、表结构本身建成了latin1。只修其中一个乱码会在另一个环节重新冒出来这就是乱码问题最折磨人的地方。 解决按顺序排查三处web.xml里加CharacterEncodingFilter并设forceEncodingtruejdbc连接串带characterEncodingutf8建表语句统一用utf8mb4。判断卡点用排除法——先看数据库里存的值是否正确如果是问号说明连接串或表结构有问题存进去正常、查出来乱说明响应编码或页面编码不对。这三处全都对齐乱码基本消失。5.3 跨域与Session丢失登录成功却被不停踢回登录页现象登录接口明明返回成功紧接着访问业务接口却一直提示未登录反复登录反复踢。 原因后端确实把loginUser放进了Session但前端的axios请求没有携带Session ID对应的Cookie每个请求都是新会话后端自然认为你没登录。跨域场景下这是最高发的Session问题。 解决前端axios实例加上withCredentials: true后端CORS配置里必须同时满足两个硬条件——allowCredentials(true)且allowedOrigins写具体地址而不是星号。缺任何一个浏览器都不会把Cookie带回给前端。排查方法简单粗暴打开浏览器Network面板点击任意一个业务请求看Request Headers里有没有Cookie字段没有就先检查这两个配置。5.4 vue.bak文件干扰前端构建现象npm run build报找不到模块或语法错误报错文件指向components目录下的某个vue组件但目录里明明躺着同名.vue.bak文件。 原因之前的开发者改组件时把旧版本备份成.bak留在同目录某些构建环境下这些文件不参与编译但如果项目里import语句写错文件名或者dist产物混入了.bak文件就会出现这类诡异报错。这类问题属于典型的文件残留引发的玄学故障。 解决先全项目搜索*.bak逐个确认。同名.vue还在的.bak直接删掉.vue不在了把.bak复制改名恢复。检查完重新build这类报错基本消失。从那以后我每次拿到这类资源包第一件事就是清理src目录下的.bak文件而不是等构建报错再回头找。5.5 端口被占用与Tomcat重复部署现象跑2-run.bat控制台直接报Address already in use: JVM_Bind或者打开8080端口看到的却是另一个不相关的页面。 原因之前启动的Tomcat进程没关干净IDE后台还挂着旧实例或者你手动起过第二个服务8080被占死。 解决Windows下先执行netstat -ano | findstr 8080拿到占用端口的进程PID再用taskkill /F /PID 进程号强杀。杀完之后去任务管理器确认Java进程确实没了再重启。端口被占和重复部署是两回事端口占用是进程还没死透重复部署是webapps目录下积累了多个版本记得只保留当前要用的一个。5.6 SQL脚本重复执行的隐患现象第二次在本地跑1-install.bat导入SQL时报SQL语法错误或Table already exists。 原因SQL脚本里的建表语句没有DROP TABLE IF EXISTS前缀数据库里已经有同名的表执行到CREATE TABLE时直接冲突。 解决最干净的做法是手动执行DROP DATABASE ad_system;再重新跑建库导库流程。更稳妥的方案是让每个CREATE TABLE语句前都加DROP TABLE IF EXISTS前缀脚本就具备幂等性了。我的习惯是本地环境直接删库重建因为重复执行就是为了拿到干净环境留旧数据反而容易干扰判断。6. 答辩前验证把四角色演示走成一个闭环核对清单代码能跑通和答辩能讲清楚是两回事。拿到这类SSMVue毕设包后我的流程是先不看代码把演示跑成一条完整业务线再回头讲实现。四角色闭环是这样管理员先建三个业务账号广告客户登录发一条广告需求广告商登录看到需求并提交提案客户在提案列表点通过系统自动生成制作需求广告制作商接单并上传一张设计图客户在预览模块确认进入签署最后在结算模块走完付款。整条流程走下来每张核心表都产生了有关联关系的数据评委看到的是完整业务流而不是一堆孤立的增删改查页面。按下面这张表逐项验证过一项勾一项顺序操作预期结果1管理员登录新增三个角色账号用户列表出现三条记录角色字段正确2广告客户登录创建广告需求需求状态为待受理渠道信息可选3广告商登录对需求提交提案需求状态变为提案中4客户通过提案状态变为设计中生成制作需求5制作商接单并上传设计图交付记录出现需求进入待预览6客户预览并提出调整状态变为调整中制作商收到调整信息7制作商修改后重新交付状态回到待预览8客户确认双方签署签署记录生成状态待结算9客户完成结算结算金额正确状态已结算验证过程中顺手做两件事。一是把演示数据做成真实业务的样子渠道名、广告语、金额都用看得懂的内容别留测试项目123的痕迹画面观感完全不同。二是用录屏工具把全流程录一遍时间压在5分钟以内万一现场环境崩了直接放录屏兜底。Network面板开着看每个请求的响应状态哪个接口突然500截图记下来那基本就是评委追着问的点提前想好解释。最后检查那三个bat脚本的路径是否写死成本机地址答辩机器环境变量未必和你本地一样脚本里的路径如果指向不存在的目录就在PPT里备注一句路径因环境而异别现场翻车。从那以后我每次拿到这类SSMVue的毕设资源都强制先重置账号、清空数据、把四角色流程完整跑一遍再交出去。状态守卫、金额精度、上传重命名这些细节平时看不出来真到了演示现场就是能不能收场的问题。希望帮到你。本文还有配套的精品资源点击获取