资讯详情

基于SpringBoot+Vue的智慧交通综合监管平台毕业设计实战解析

📅 2026/10/20 8:37:19 | 华诺云谱 👁 阅读
基于SpringBoot+Vue的智慧交通综合监管平台毕业设计实战解析
1. 整体设计与思路拆解做毕业设计这么多年被问得最多的一句话就是“老师我这个系统到底做什么才能过”说实话像“基于SpringBootVue的智慧交通综合监管平台”这种题目听上去挺唬人但拆开了看核心就三件事车辆违章管理、路况信息发布、一体化联动监管。把这三块做扎实了答辩稳落地也实用。1.1 题目核心需求解析这个题目的主标题拆出来其实是三个关键词智慧交通、综合监管、一体化。先别急着写代码先理解出题人到底想要什么。“智慧交通”意味着系统不能只是一个简单的增删改查要有数据看板、统计分析、可视化展示这些偏“智能”的元素。哪怕你底层就是一个CRUD也必须在表现层让人觉得“这系统有脑子”。比如违章记录按路段聚合分析哪个路口违章最多哪个时间段事故高发这些图表一出来答辩老师一眼就能看出价值。“综合监管”对应的是多角色权限体系。一个交通平台不可能只有一个管理员。通常要拆出几类角色交警处理违章、审核上报、路况编辑发布拥堵、施工、事故信息、系统管理员账号分配、数据备份、系统配置。这就涉及Spring Security或Shiro之类的权限框架至少也要用拦截器配合角色字段控制访问。“一体化管理”说的是业务闭环。违章不是录一条记录就完了得能从“抓拍上报”走到“审核确认”再到“处罚生成”最后“车主查询认罚”。路况信息也要有一条线采集、审核、发布、失效下线。A模块的结束是B模块的开始这种状态流转设计是评分的重点。1.2 为什么选SpringBootVue这套组合很多同学问我要不要用SSH、SSM那些老古董我的态度一直很明确只要不是学校硬性规定一律用SpringBootVue前后端分离。SpringBoot的优势不用多吹关键是不用自己配一堆XML内嵌Tomcat一键启动对新手友好到有点宠溺。它加上MyBatis-Plus之后单表CRUD连SQL基本都不用写了BaseMapper里全给你封装好了省下来的时间足够你把业务逻辑做得更细。Vue这边同样省心。Element UI一把梭表格、表单、弹窗、菜单全是现成的配合Vue Router做页面跳转Vuex或者Pinia管登录状态一个中后台管理系统在地图上都能找到账号。要是你以前用JSP写过后台再用Vue写一套那种体验落差基本就是“二八大杠换电动车”。最关键的是这套组合现在的学习资料最多。B站、某课网、某云社区从零搭建到上线部署的教程一抓一大把遇到问题搜一下基本都有答案。毕设最怕的不是技术难是查不到解决方案。1.3 系统架构与应用场景整体架构是标准的前后端分离三层结构。前端是Vue全家桶跑在8080端口后端是SpringBoot跑在8081端口数据落MySQL启动缓存Redis部署的时候用Nginx把前端打包后的静态文件一挂然后用反向代理把/api开头的请求转发到后端服务上。这套架构要说复杂也不复杂但它和现在企业里实际在用的开发模式是同一个路子拿到面试里也敢说。应用场景上面向的是“某市交通综合监管平台”。管理员通过PC端登录后台处理各类业务。系统我建议拆成六大功能模块用户管理系统用户的增删改查与角色分配是一个大分类而不是直接固定角色。更新为用户注册、登录鉴权、个人信息维护。而账号审核、角色分配、权限控制属于系统管理模块统一处理。车驾管理车辆信息登记、车辆年检状态跟踪、车辆类型分类管理。违章管理违章记录录入、图片证据上传、违章审核、处罚确认、车主查询。路况管理路况信息发布、道路施工/事故标记、拥堵指数上报、信息过期下线。数据分析违章高发路段排行、交通流量趋势图、各区域违章对比。系统管理不同职能账号的创建、功能权限分配、密码重置、操作日志审计。与用户管理严格区分一个面向业务身份一个面向系统登录授权。这套模块圈下来你的系统就不再是“有一个违章表增删改查”而是一个能覆盖真实业务场景的小平台。答辩的时候你说“我把一个违章从录入到处罚结案的全流程打通了”这句话的分量完全不一样。2. 数据库设计与核心表结构很多同学做毕设有个习惯上来就建表建到一半发现字段不够回头再加。我建议把表结构当成系统的地基地基歪了后面全歪此处先静下心把表理清楚。2.1 核心数据表规划以“一体化监管”为目标我规划了这么几张核心表。先声明这不是唯一答案但这是一个比较顺手且能覆盖所有功能的方案。用户表sys_userid、username、password、real_name、phone、status、role、create_timerole字段用字符串直接存比如ADMIN/POLICE/TRAFFIC简单直观比中间表好懂。车辆信息表vehicle_infoid、plate_no车牌号、vehicle_type小型汽车/大型汽车/新能源、owner_name车主姓名、owner_phone、engine_no、register_date、status车牌号要做唯一索引这是整个系统关联的钥匙。违章记录表violation_recordid、plate_no、violation_type违停/超速/闯红灯/压线等、violation_time、violation_location、evidence_img证据图地址、status待审核/已确认/已处罚/已缴款/已撤销、fine_amount、deduct_points、handler_id、create_time这张表是整个系统的核心设计时务必把状态字段想全。状态机的流转待审核 - 已确认 - 已处罚 - 已缴款。如果证据不清晰或上传错误还有已撤销状态。状态不全业务流程就断了一截。路况信息表road_conditionid、road_name易堵路段、direction方向、condition_type拥堵/事故/施工/管制、description、start_time、end_time、publisher_id、status生效中/已下线、create_time系统日志表sys_logid、operator_id、operation、method、params、ip、create_time日志表是加分项。答辩的时候老师问你“系统安全怎么考虑的”你把日志表亮出来比嘴上吹十句都管用。2.2 表关系与字段设计思路表关系其实不复杂核心就是交通管理领域最关键的业务关联违章记录表通过plate_no关联车辆信息表从而取到车主姓名、电话通过handler_id关联用户表知道这条记录是谁处理的。路况信息表通过publisher_id关联用户表。整个系统没有多对多的绕弯子这符合毕设的定位——合理的复杂度但不过度设计。这里要特别提醒一个细节违章记录表要不要冗余车主姓名我的建议是存。虽然可以通过车牌号实时去关联车辆表查询但违章记录属于高频查询的表一旦数据量大了每次join都会拖慢速度。冗余存储意味着查询车辆信息时直接用违章表里的plate_no去车辆信息表拿数据时主查询往往只需要轻轻一扫并不会因为冗余而产生额外压力反而是把热点数据固化在业务表里让列表页的查询少了一次关联。冗余带来的副作用是数据一致性需要靠代码保证不过在这个系统里车辆信息基本不变冗余安全性足够高。在列表页用select * from violation_record直接展示性能就是好。别管那些所谓“三大范式”的教条项目里适当冗余是常态。时间字段统一用datetime不要用timestamp后者有2038年问题而且不方便做时间范围查询。金额字段用decimal(10,2)不要用float避免浮点精度问题。数据库排序规则统一用utf8mb4_general_ci避免中文乱码也能正确支持车牌号里的中文和特殊字符。2.3 初始化数据与SQL脚本技巧别等系统做好了再补测试数据建完表第一件事就是写一个init_data.sql把演示数据准备好。至少要做这些事三个角色的用户各一个admin/admin123police/police123traffic/traffic12320条以上的车辆信息车牌号不要全是“某A12345”尽量覆盖不同字母开头、燃油车和新能源50条以上的违章记录时间分布分散要覆盖近一年的数据方便图表统计、地点分布至少5个路段10条路况信息至少有3条是“生效中”的状态有了这些基础数据前端页面一打开就不空图表有数据可画列表有内容可翻页。很多同学答辩翻车不是系统不行是打开来一片空白体验太差。数据灌进去系统“活”起来演示效果直接上一个档次。这里分享一个我常用的生成测试数据的快速写法直接用MySQL的存储过程循环插入比如循环200次生成违章记录时间用DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY)随机偏移地点从预定义的数组里随机取。五分钟就能把库填得漂漂亮亮。3. 后端核心模块实现后端部分我给一个可落地的架构不求花哨但求每一层都有存在感。3.1 项目初始化与依赖配置用Spring Initializr生成项目骨架Java版本选8或11这个在答辩环境里最稳。打包方式jar依赖选择Spring Web、MyBatis Framework、MySQL Driver、Lombok。pom.xml里还需要手动加两个关键依赖MyBatis-Plus和JWT。MyBatis-Plus不用多说JWT用于登录认证。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency配置文件application.yml如下server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/traffic_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true有几个点容易踩坑我提前说一下serverTimezone必须配否则MySQL 8.x会报时区错误。MyBatis-Plus升级到3.5.x以后分页插件配置方式有变化需要新建一个配置类。数据库名不要用traffic这种太短的词容易和系统表冲突用traffic_system更稳。分页配置类这么写Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }不加这个配置调用selectPage分页不生效返回的全量数据。这个坑我见过很多次了。3.2 JWT登录认证与拦截器登录流程用户提交用户名密码 - Controller接收 - Service校验 - 校验通过生成JWT返回前端 - 前端把Token存到localStorage - 后续每次请求在Header里携带Authorization: Bearer token- 后端拦截器校验Token并解析用户信息。Token工具类核心代码Component public class JwtUtil { private final String secret your-secret-key-your-secret-key; private final long expiration 86400000L; // 24小时 public String generateToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expiration)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } }再写一个拦截器统一处理Token校验Component public class JwtInterceptor implements HandlerInterceptor { Autowired private JwtUtil jwtUtil; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/auth/login)) { return true; } String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { try { Claims claims jwtUtil.parseToken(authHeader.substring(7)); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\登录状态已过期请重新登录\}); return false; } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或Token缺失\}); return false; } }这里有个细节值得注意拦截器只做了Token解析没做角色鉴权。角色判断我建议放在Controller层用自定义注解RequireRole(ADMIN)配合AOP实现或者简单点在Service里判断request.getAttribute(role)。毕设层级不用做得太复杂但思路要能讲清楚——认证管“你是谁”授权管“你能做什么”这两个概念答辩必问。3.3 违章管理模块的完整实现违章管理是整个系统的业务重心。我先讲后端服务接口设计这部分是整个系统的数据支撑核心。POST/api/violation新增违章记录GET/api/violation/page分页查询违章记录支持条件查询车牌模糊、违章类型、时间段、状态GET/api/violation/{id}查询违章详情PUT/api/violation/{id}更新违章记录审核、处罚DELETE/api/violation/{id}删除违章记录PUT/api/violation/{id}/status变更违章状态其中分页查询是最核心的接口代码我给你们写清楚RestController RequestMapping(/api/violation) public class ViolationController { Autowired private ViolationService violationService; GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String plateNo, RequestParam(required false) String violationType, RequestParam(required false) String status) { PageViolationRecord page violationService.getViolationPage(pageNum, pageSize, plateNo, violationType, status); return Result.success(page); } }Service层Service public class ViolationServiceImpl extends ServiceImplViolationRecordMapper, ViolationRecord implements ViolationService { Override public PageViolationRecord getViolationPage(Integer pageNum, Integer pageSize, String plateNo, String violationType, String status) { LambdaQueryWrapperViolationRecord wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(plateNo), ViolationRecord::getPlateNo, plateNo) .eq(StringUtils.isNotBlank(violationType), ViolationRecord::getViolationType, violationType) .eq(StringUtils.isNotBlank(status), ViolationRecord::getStatus, status) .orderByDesc(ViolationRecord::getViolationTime); return this.page(new Page(pageNum, pageSize), wrapper); } }你注意一下这里用了StringUtils.isNotBlank作为like和eq的第一个参数这个方法太实用了。如果条件为空这个条件自动不拼接SQL就干干净净。老手写动态SQL都这么干新手还在傻傻if判断代码差一截。违章状态变更的接口要重点说。状态不能乱跳我加了一层简易校验不允许“已缴款”回退到“待审核”更不允许“已撤销”改成“已处罚”。状态机的流转规则要写清楚这本身就是业务逻辑的体现。Override public boolean changeStatus(Long id, String targetStatus) { ViolationRecord record this.getById(id); if (record null) { throw new RuntimeException(违章记录不存在); } String currentStatus record.getStatus(); // 定义合法的状态流转 MapString, ListString transitions new HashMap(); transitions.put(待审核, Arrays.asList(已确认, 已撤销)); transitions.put(已确认, Arrays.asList(已处罚, 已撤销)); transitions.put(已处罚, Arrays.asList(已缴款, 已撤销)); transitions.put(已缴款, Collections.emptyList()); transitions.put(已撤销, Collections.emptyList()); ListString allowedTargets transitions.get(currentStatus); if (allowedTargets null || !allowedTargets.contains(targetStatus)) { throw new RuntimeException(非法的状态变更 currentStatus - targetStatus); } record.setStatus(targetStatus); return this.updateById(record); }这套状态机模型讲出去答辩老师就明白你不是只写了个CRUD而是真正考虑了业务规则。如果时间充裕可以考虑用状态模式重写但毕设里一个Map状态机足够亮眼了。4. 前端Vue核心页面实现后端搭好接着写前端。选Vue 2还是Vue 3如果学校没要求直接Vue 3 Element Plus。Vue 3的组合式API写起来更顺手而且现在是主流了。但如果你只看得懂Vue 2的选项式API用Vue 2也没问题。答辩不考框架版本考的是功能完整度和你对代码的理解。我自己更推荐Vue 3生态已经非常成熟。4.1 项目初始化与路由设计前端项目用Vite初始化网址上这样操作npm create vitelatest traffic-web选Vue框架和JavaScript变体。然后安装依赖npm install npm install vue-router4 pinia element-plus axios echarts路由设计要清晰按模块拆分const routes [ { path: /login, component: Login }, { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: Dashboard, meta: { title: 数据看板 } }, { path: violation, component: ViolationList, meta: { title: 违章管理 } }, { path: violation/detail/:id, component: ViolationDetail, meta: { title: 违章详情 } }, { path: vehicle, component: VehicleList, meta: { title: 车辆管理 } }, { path: road, component: RoadCondition, meta: { title: 路况管理 } }, { path: system/user, component: SysUser, meta: { title: 系统管理 } } ] } ];路由守卫也别忘了核心作用就是守卫登录态router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path /login) { next(); } else { if (!token) { next(/login); } else { next(); } } });这一步非常关键。如果不加路由守卫前端所有页面对未登录用户完全开放那JWT认证就形同虚设。到时候老师直接地址栏输入路径就能看到系统页面项目评分直接掉档。4.2 数据可视化看板实现智慧交通的“智”最直观的体现就在数据看板。我建议首页放四个核心图表近7日违章数量趋势折线图违章类型占比饼图违章高发路段Top5横向柱状图今日实时路况列表滚动列表ECharts接入Vue的方式很简单装完依赖后封装一个BaseChart组件统一处理图表的初始化和销毁。template div refchartRef :style{ width: 100%, height: 400px }/div /template script setup import { onMounted, onBeforeUnmount, ref, watch } from vue; import * as echarts from echarts; const props defineProps({ option: { type: Object, required: true } }); const chartRef ref(null); let chartInstance null; onMounted(() { chartInstance echarts.init(chartRef.value); chartInstance.setOption(props.option); // 监听窗口变化重绘图表 window.addEventListener(resize, resizeHandler); }); const resizeHandler () { chartInstance chartInstance.resize(); }; watch(() props.option, (newOption) { chartInstance chartInstance.setOption(newOption); }, { deep: true }); onBeforeUnmount(() { window.removeEventListener(resize, resizeHandler); chartInstance chartInstance.dispose(); }); /script这个封装好在哪三个亮点一是所有图表共用一套生命周期管理不会出现切页面后定时器还在跑的内存泄漏二是resize事件统一处理窗口缩放图表自适应三是option用watch监听数据更新后图表自动刷新。调用时只需要传入option即可BaseChart :optiontrendOption /后端对每个图表提供一个聚合统计接口前端拿数据组装option。统计接口是后端写的比如GET /api/violation/trend?days7返回[{date: 2024-12-01, count: 5}]。前端再根据数据组装折线图的series。4.3 违章管理页面实战违章管理页面是整个系统交互最复杂的模块。表格展示、多条件搜索、弹窗新增、状态流转这些组件组合起来就是一个完整的中后台管理页面模板。搜索区我用了栅格布局一行放三列车牌号输入框、违章类型下拉框、状态下拉框后面跟查询和重置按钮。这个界面排版几乎不用动脑但用户用起来十分舒坦。表格列包括车牌号、违章类型、违章时间、违章地点、金额、扣分、状态、操作。操作列放两个按钮编辑、删除。状态我用Element Plus的el-tag标签渲染不同状态不同颜色待审核-灰色已确认-蓝色已处罚-橙色已缴款-绿色已撤销-红色。新增违章用弹窗表单校验规则用Element Plus自带表单验证const rules { plateNo: [{ required: true, message: 请输入车牌号, trigger: blur }], violationType: [{ required: true, message: 请选择违章类型, trigger: change }], violationTime: [{ required: true, message: 请选择违章时间, trigger: change }], violationLocation: [{ required: true, message: 请输入违章地点, trigger: blur }] };表单提交时注意一个细节时间字段从前端传到后端的格式是2024-12-01T10:30:00需要处理后端接收。我建议在实体类的violationTime字段上加上JsonFormat(pattern yyyy-MM-dd HH:mm:ss)注解保证JSON格式正确。页面与后端交互统一走封装的请求工具。前端的请求封装是整个项目的交通枢纽我一般这样处理响应拦截器统一解析接口返回import axios from axios; import { ElMessage } from element-plus; import router from ../router; const request axios.create({ baseURL: /api, timeout: 10000 }); // 请求拦截器 request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); // 响应拦截器 request.interceptors.response.use( response { const res response.data; if (res.code 200) { return res; } if (res.code 401) { // 登录失效跳转登录页 localStorage.removeItem(token); router.push(/login); ElMessage.error(登录已过期请重新登录); return Promise.reject(new Error(res.message)); } ElMessage.error(res.message || 请求失败); return Promise.reject(new Error(res.message)); }, error { ElMessage.error(error.message || 网络异常); return Promise.reject(error); } ); export default request;响应拦截器里处理401跳转是一个你迟早会踩坑、但提前封装就能完美规避的经典场景。Token过期在后端返回401前端统一拦截跳转登录页用户重新登录后又回到系统。这个链路如果不做用户操作到一半突然卡住体验极其难受。5. 路况信息一体化发布与联动设计路况模块虽然代码量没有违章多但它是体现“一体化”的重要模块。5.1 路况信息管理后端实现路况表字段我前面列过了这里说说发布流程。路况编辑人员登录后发布一条路况信息填写路段名称、方向、类型、描述、生效时间。系统默认状态是“生效中”前端首页的地图和列表页实时展示。过期后管理员可以手动下线也可以写一个定时任务自动下线。定时任务用Spring自带的Scheduled就可以Component public class RoadConditionTask { Autowired private RoadConditionService roadConditionService; // 每小时执行一次自动下线已过期的路况 Scheduled(cron 0 0 * * * ?) public void autoOfflineExpired() { roadConditionService.autoOfflineExpired(); } }别小看这个定时任务它给系统加了一个“自动化”的标签。答辩时可以这样说“路况信息超过结束时间后系统会自动将其置为下线状态无需人工干预。”这又是一个加分点。5.2 一体化联动的几个关键环节一体化体现在哪我做这么几个联动违章处理联动当违章记录状态变为“已处罚”时系统自动扣减对应车辆车主的驾驶证分数在车辆信息表或专门的分数字段中。这个逻辑放在Service层在changeStatus方法里完成代码比异步消息队列简单得多但同样的业务效果。就是状态流转之后加几行判断如果是已处罚就更新车主扣分。数据看板联动首页的数据图表每隔30秒自动刷新一次通过setInterval定时调用统计接口让看板上的数据像“实时”的一样。答辩演示的时候你新加一条违章刷新首页数字马上变化这个视觉冲击力很强很能说明系统是真的“活”的。路况与违章联动可选加分项在违章新增弹窗里如果违章地点匹配到生效中的路况信息系统自动提示“该路段当前正处于施工状态”。操作人员可以选择备注“施工区域违章”。这个联动做好系统的智能化水平直接上一个台阶。这些细节不需要额外做复杂的中间件后端Service层多写几行判断就能完成。但它们带来的效果是“系统是一个整体”而不是“一堆零散页面”。6. 前后端联调与常见问题排查到了联调环节很多同学要开始头疼了。我把这几年带毕业设计最常遇到的问题汇总一下全是实测干货能帮你省下大量的调试时间。6.1 跨域问题与解决方案前后端分离的项目跨域属于“必定会踩”的第一大坑。前端页面跑在http://localhost:5173后端接口是http://localhost:8081浏览器因为同源策略把请求拦下来了。报错信息一般是Access-Control-Allow-Origin。最简单的方案是后端加一个配置类实现WebMvcConfigurerConfiguration 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); } }注意两点allowedOriginPatterns(*)而不是allowedOrigins(*)因为allowCredentials为true时后者会失效OPTIONS方法必须放行否则预检请求直接挂在拦截器上。还有一种做法是走Nginx反向代理部署的时候建议统一走这种方式。前端请求/api开头的接口Nginx转发到后端8081端口浏览器跨域问题彻底消失。不过开发阶段还是建议后端配置CORS简单直接改完就生效。6.2 Token失效与状态管理问题“登录之后刷新页面就回到登录页”这个问题我见得太多。原因基本是前端没有做Token持久化Token存在Vuex或Pinia的store里页面一刷新store清空路由守卫判断没有Token就把你踢回登录页。正确做法Token存入localStorage页面刷新时从localStorage读取初始化store。登录成功后的保存代码至少要把Token持久化到本地存储。loginRes.data { token: xxx, userInfo: { name: 管理员 } }; localStorage.setItem(token, loginRes.data.token);路由守卫里判断时优先从localStorage取const token localStorage.getItem(token) || store.state.token;另一种情况是Token过期后前端所有请求401。响应拦截器统一处理跳转登录页这个我前面已经写过了。后端拦截器判401前端拦截器跳转整条链路闭环。6.3 日期和时间显示错误这个问题同样高频。数据库存的2024-12-01 10:30:00接口返回给前端变成2024-12-01T10:30:00或者毫秒时间戳。原因有几种后端返回JSON时没配时间格式。处理方式application.yml里加统一格式或在时间字段上配JsonFormat。前端没做时间格式化。Element Plus的el-table列上可以加formatter函数统一格式化。时区问题。数据库连接URL要带serverTimezoneAsia/Shanghai否则MySQL返回的时间和本地时间差8小时。日期显示这种小问题很容易被忽略但真心影响观感。你说一个交通系统里违章时间显示成2024-12-01T10:30:00.00000:00答辩时懂行的人一眼就能看出问题。细节打磨到位整体分就高。6.4 部署上线常见坑毕设一般不用真的部署到云服务器但如果你想把项目放到服务器上提交时把线上地址一写档次直接不一样。部署流程我简单说一下后端打包成jar包服务器装JDKnohup java -jar traffic-system.jar 运行。前端npm run build打包dist目录挂到Nginx静态目录下。Nginx配置监听80端口root指向前端dist目录location /api开头的请求proxy_pass http://localhost:8081。这里容易出问题的是后端打包时没把配置文件里的IP地址改成本机IP或公网IP数据库连接不上前端打包时axios的baseURL是/api如果后端不在同一个地址就请求失败。6.5 问题排查速查表现象可能原因排查思路前端请求接口CORS报错后端未配置跨域检查CorsConfig是否生效OLTPIONS请求是否放行登录后刷新页面跳到登录页Token未持久化检查登录后是否写入localStorage分页数据不分页未配置分页插件检查MybatisPlusConfig中的PaginationInnerInterceptor返回时间差8小时数据库时区问题检查连接串是否带serverTimezoneAsia/Shanghai中文乱码数据库字符集问题建库时选utf8mb4连接串带characterEncodingutf8页面有数据但图表不渲染echarts容器高度为0给容器设置一个固定高度或在图表init前获取容器尺寸上传图片无法预览静态资源映射缺失配置WebMvcConfigurer映射本地文件夹为URL6.6 常见易错点与避坑指南再说几个新手最容易踩的坑提前提醒一次。MyBatis-Plus的字段映射坑实体类字段phone数据库字段phone没问题实体类字段createTime数据库字段create_time需要开启map-underscore-to-camel-case: true。但如果你字段名不一致且没开驼峰映射查询结果全是null找半天bug才发现是映射问题。逻辑删除坑MyBatis-Plus的逻辑删除用的是TableLogic注解配置后调用deleteById其实执行的是update语句。很多同学不知道这个机制在表格里显示已删除的数据还在数据库里误以为删除没生效。合理利用逻辑删除系统数据更安全同时也别让“删不掉”变成隐藏坑。Lombok坑实体类用了Data注解但如果你在类里手写了getter/setterLombok不会再帮你生成部分字段可能没有getter调用时报空指针。排查方法就是检查IDE的Structure面板看字段前有没有对应的方法。Vue组件名坑组件名用了HTML原生标签重名比如header、section会导致渲染异常。组件名最后都带上前缀比如BaseChart、TrafficTable默认会按驼峰命名。7. 文档撰写与答辩准备心得系统做得再漂亮文档和答辩不给力还是容易翻车。这部分讲点实际经验尤其适合把毕设当成项目来认真对待的同学。7.1 毕业论文结构建议一篇交通管理系统相关的毕业论文核心结构我建议这样布局绪论研究背景与意义、国内外研究现状记得引用3-5篇参考文献不要编造真实项目名和真人姓名可用模糊代称相关技术介绍SpringBoot、Vue、MyBatis-Plus、MySQL、JWT每个技术写原理和为什么选它系统分析可行性分析、需求分析、用例图、业务流程系统设计架构设计、功能模块设计、数据库设计附上核心表的建表SQL系统实现截图核心代码实现说明重点描述违章管理和路况发布这两个核心流程系统测试测试用例表功能测试性能测试简单写主要是逻辑验证文档里的截图一定要重新截不要把开发过程中的半成品截图放进去。表格、图表、弹窗这些关键界面都单独截一张图上加标注专业感瞬间就出来了。关键代码正文放一段完整的放附录。查重率高的同学建议不要大段贴代码而是用自己的话描述代码逻辑配合小段核心代码展示。7.2 答辩常见问题演练答辩老师问的问题其实来来去去就那些提前准备就好高频提问回答方向为什么用SpringBoot不用SSM内嵌Tomcat免部署、生态完善、与Vue天然搭配Token机制是怎么实现的JWT无状态认证讲解生成、校验、过期处理三个环节系统有什么安全措施JWT认证、密码MD5加盐/BCrypt加密、角色权限控制、操作日志数据量大了怎么办分页查询、索引优化、后续可引入缓存为什么要前后端分离开发职责分离、前端交互体验更好、后端接口可复用系统最大的亮点是什么一体化管理、状态机流转、数据可视化最后一个问题一定要认真答。你的亮点最好是真实实现过的功能不要虚构。有一说一我听过太多“我的系统采用了微服务架构”结果Service层都没拆明白的同学当场被问穿。答辩还有一个技巧演示系统时先展示数据看板再进到具体的业务流程里走一遍“新增违章 - 审核 - 处罚 - 缴款”的完整流程。这比零散地介绍模块有说服力得多。老师看到的是一个能跑通闭环的系统而不是一个个孤岛页面。8. 从毕设到真实项目的最后一步系统做完了论文交了答辩过了这个项目就算翻篇了。但如果你以后想走开发这条路我建议再花点时间做这几件事把这个毕设的价值榨干。把这个项目往Git上推README写清楚运行方式、技术栈、功能清单。面试的时候直接甩链接比嘴上说“我做过一个系统”有效十倍。在面试聊项目时被问最多的是“你怎么解决跨域的”“Redis缓存用在哪儿了”“如果违章数据量到百万级怎么优化”。这些问题你不用答得完美但要有思路。比如百万级数据可以说分库分表、ES搜索、冷热数据分离至少要让面试官知道你有架构意识不是只会写CRUD。这就是我想说的主线一个认真的毕设完全可以作为工程能力的敲门砖。技术栈是企业主流的业务是贴近真实场景的代码是你一行行写出来的你在答辩时说的每一句话都有底气。很多时候拉开差距的不是天赋就是这种“把该做的事做到位”的踏实劲。如果你正在做类似的系统希望这篇文章能帮你绕过几个坑少熬几个夜。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑