SSM+Vue超市管理系统毕设全解析:从数据库设计到部署上线
做毕业设计这几年我陆续帮人审过不下二十套基于SSMVue的超市管理系统。说句实话这个题目想拿高分不是把源码跑起来就够了关键是能不能把“超市业务”和“代码结构”之间那条线讲清楚。SSM负责后端接口和业务事务Vue负责页面交互和前端工程化两者拼起来的超市场景看着不复杂但涉及的商品管理、进出库、收银、报表这些模块正好能把JavaWeb的主流知识点和前端框架能力都覆盖到。这篇文章我就从项目设计、数据库、后端、前端、部署几个角度完整拆一遍给要做类似题目、或者想把这套系统改成自己毕设的同学做个参考。这套系统真正要解决的事情很简单中小型超市的商品多、进货渠道杂、员工操作权限不好控制、每天卖了多少全靠手工账。传统Excel记录很容易出现“账面库存和实际库存对不上”的尴尬局面。所以系统定位成一套面向管理员、收银员、仓库人员的后台管理工具管理员维护商品分类、供应商和员工账号录入每个商品的进价、售价、库存上下限收银员在收银台快速搜索商品、逐项结算并打印小票仓库人员按入库单进货、做盘点、处理库存预警。所有订单写成数据库流水月底按日聚合出销量和销售额报表把“对账”这件事从人工翻本子变成系统点一下。我建议你这么看这篇拆解如果你是还没开始动手的毕业设计选手前两章帮你定方案、建数据表如果你代码已经写了一半直接从第三章开始对照后端事务和权限的写法大概率能救回几个容易翻车的点要是项目已经能跑只是不知道怎么部署、不知道答辩时老师会问什么第五章和第六章帮你扫尾。1. 项目整体怎么构思先把设计思路理顺1.1 为什么这个题目要选 SSM Vue而不是无脑 Spring Boot很多同学会纠结都什么年代了现在新项目谁还用 SSM毕业设计为什么还要写这套组合我的理解是高校很多题目就是明确限定SSM尤其数据库、JavaWeb方向很多老师觉得SSM能真实体现出你对Spring容器、事务、MyBatis映射这些底层机制的理解而Spring Boot大量自动化配置反而把细节藏起来了。从就业角度讲现在企业里确实Spring Boot更主流但SSM和Spring Boot的内核几乎一样Service、Mapper、Controller这些代码迁移成本很低先把SSM跑明白后面看Spring Boot项目会轻松很多。Vue选型则没太多悬念Vue在国内生态太成熟了配合Element UI做中后台管理系统几乎是一天一套页面的节奏网上资料多遇到问题好搜。整套系统的技术栈可以定了后端用Spring SpringMVC MyBatis前端用Vue 2 Element UI Axios数据库用MySQL权限控制用拦截器加token项目管理用Maven。这个组合既能展示Java基础又能秀前端工程化能力而且每一个环节都方便在答辩时讲清楚“为什么这么设计”。1.2 功能模块怎么定先画三张图再动手写代码我见过太多同学拿到题目就往IDE里冲结果写了三周发现订单表缺字段、角色权限没预留又推倒重来。做管理系统尤其超市这种业务流比较完整的题目动手前一定要画三张图用例图、E-R图、系统模块图。不要觉得画图浪费时间这三张图画完你的论文第三章基本写完一半写代码时也不会大脑一片空白。超市系统的核心业务流程可以拆成七个闭环登录与权限校验、商品档案维护、供应商档案维护、采购入库、销售收银、库存预警、销售统计。对应的角色权限我建议分成三种不要一上来就把权限设计得太花哨管理员可以操作全部模块包括员工账号、商品、分类、供应商、订单查询、统计报表。收银员只能打开收银台和自己的订单记录不能看到成本价不能修改商品。仓库人员能管理入库单、库存盘点、库存预警不能接触销售价格和统计报表。功能范围控制在三个角色、七个模块左右就够了。毕业设计不是企业级商业软件模块太多会把自己拖死模块太少又显得没工作量。把商品生命周期新增-入库-销售-库存变动-统计做成完整闭环答辩时老师问“数据是怎么流转的”你能从头到尾自圆其说这个项目就算立住了。1.3 后端目录和前端目录怎么分层代码分层是否清晰决定源码给人第一印象好不好。后端我建议用标准的MVC分层不要把所有逻辑都堆在Controller里面。分包结构可以这样设计com.supermarket ├── controller // 接收前端请求、参数校验、返回统一结果 ├── service // 业务接口 │ └── impl // 业务实现事务注解加在这里 ├── dao // MyBatis的Mapper接口 ├── entity // 数据库表对应的实体类 ├── dto // 接收前端参数的对象避免实体类直接暴露 ├── vo // 返回给前端的视图对象比如分页结果、订单VO ├── config // 拦截器、跨域配置 └── common // 通用Result封装、异常处理、工具类前端工程项目建议用Vue CLI初始化不是直接在HTML里引一个Vue的script就算完。src内部的目录也固定下来api目录放所有请求方法、router目录放路由配置、views目录放页面组件、components目录放公共组件比如分页、弹窗、图片上传、utils目录放axios封装和工具函数。这样前后端各司其职答辩演示时连代码结构都能成为加分项。2. 数据库设计与核心表结构先把仓库的地基打好2.1 八张核心表覆盖超市全流程超市管理系统的数据量不大但表之间的关系比较典型数据库设计是答辩老师最喜欢提问的地方。我不建议把表拆得特别碎也不建议所有数据塞一张表。按照业务角色和流程有一套被我验证过很多次的八表方案覆盖超市从进货到销售到统计的完整链路表名用途关键字段说明user用户/员工表role区分admin/cashier/keeperstatus控制是否禁用category商品分类表一级分类即可表里存name排序sortsupplier供应商表contact、phone供入库单关联product商品表核心表包含分类、供应商、进价、售价、库存、预警值stock_in入库单主表记录一次入库的整体信息供应商、入库人、总金额stock_in_item入库单明细表一次入库可能包含多个商品数量、进价各自记录orders销售订单主表订单号、收银员、总金额、实收金额、支付方式order_item订单明细表每一件卖出商品的快照名称、单价、数量如果你的项目还想要积分、会员、促销这些功能可以再加会员表和促销表。但毕业设计不建议把战线拉太长把上述八张表做扎实每种角色、每条数据流都能解释清楚就已经够了。2.2 订单主表和明细表为什么必须拆开订单设计是数据库部分最容易暴露问题的地方。有些同学图省事订单表里设计一个goods字段把商品名和数量用逗号拼成字符串存进去。这样查订单时确实方便一行数据就是一个订单但完全没法做按商品维度统计销量也没法退款时单独扣某一项库存。正确做法是把一次购买抽象成“主表和明细表”两个结构。orders主表只存一笔订单的整体信息订单编号、收银员ID、下单时间、商品总金额、实收金额、支付方式、状态。order_item明细表存这次订单里的每一种商品关联的订单ID、商品ID、购买时的商品名称、单价、数量、小计金额。查询用户的订单列表时先查主表再根据主表ID批量查明细表然后用一条SQL把两张表join起来也可以。后面做月度销售报表时直接group by order_item里的商品ID就可以统计不需要解析任何字符串。2.3 可直接导入的建表SQL与测试数据为什么先做数据库再写后端的Java代码因为接口、实体类都是跟着表结构走的表结构一旦稳定写实体类就变成了机械劳动。下面给一份核心product和orders表的设计读者可以在此基础上扩展其他表。CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 商品ID, category_id INT NOT NULL COMMENT 分类ID, supplier_id INT DEFAULT NULL COMMENT 供应商ID, barcode VARCHAR(32) DEFAULT NULL COMMENT 条形码, name VARCHAR(128) NOT NULL COMMENT 商品名称, unit VARCHAR(10) DEFAULT 件 COMMENT 单位, spec VARCHAR(64) DEFAULT NULL COMMENT 规格如500ml, purchase_price DECIMAL(10,2) NOT NULL COMMENT 进价, sale_price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 当前库存, warning_stock INT NOT NULL DEFAULT 10 COMMENT 库存预警值, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1上架0下架, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_category_id (category_id), KEY idx_supplier_id (supplier_id), UNIQUE KEY uk_barcode (barcode) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 商品表; CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id INT NOT NULL COMMENT 收银员ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 商品总金额, actual_amount DECIMAL(10,2) NOT NULL COMMENT 实收金额, change_amount DECIMAL(10,2) DEFAULT 0 COMMENT 找零, pay_type TINYINT DEFAULT 1 COMMENT 支付方式1现金2微信3支付宝, status TINYINT DEFAULT 1 COMMENT 1正常2已退款, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 销售订单主表; CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT 订单ID, product_id INT NOT NULL COMMENT 商品ID, product_name VARCHAR(128) NOT NULL COMMENT 商品名称快照, sale_price DECIMAL(10,2) NOT NULL COMMENT 成交单价, quantity INT NOT NULL COMMENT 数量, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计金额, KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 销售订单明细表;有一类字段大家很容易忽略order_item里一定要存product_name和sale_price这份快照。为什么因为商品表里的名称和价格是允许管理员改的如果订单明细只存商品ID三个月后再查这张订单商品名字和当时卖的价格可能全变了对账就没法对。快照是订单系统的常规设计答辩时主动提这一点老师会觉得你真思考过业务。进价和售价我统一用DECIMAL(10,2)不用FLOAT和DOUBLE。浮点数在Java和数据库里都有精度问题商品价格算错一分钱在收银场景都是事故整数一般用来表达数量数量不进数据库再来除太别扭。DECIMAL精确到分程序里用BigDecimal接收这是标准姿势。库存字段为什么直接写在product表里严格来说库存应该单独建一张库存流水表但毕业设计主要的计算场景是销售扣减和入库增加库存冗余在商品表里update语句在同一个事务中对库存进行增减配合下面的条件更新写法完全可以兼顾简单和正确性。如果后面要支持多仓库再拆出库存明细表。3. 后端SSM分层实现业务逻辑才是真正得分的地方3.1 依赖与配置Spring MVC和MyBatis怎么串起来后端工程创建时需要注意版本兼容。很多同学一上来就加最新版本依赖结果Spring 6配MyBatis 3.5.x因为javax和jakarta命名空间不同直接启动不了这种问题特别让人崩溃。毕业设计我一般用Spring 5.2.x对应Java8MyBatis用3.5.5左右mysql-connector-java用8.0.xServlet和Tomcat配套即可。pom.xml里的关键依赖大概是这样一个组合!-- Spring核心容器、SpringMVC、JDBC事务按需引用 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.2.22.RELEASE/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependencySpring的配置方式有两种用applicationContext.xml还是用纯注解类。网上很多教程混着写最终导致Bean扫描两次或者Mapper扫描不到。我建议毕业设计直接用注解配置把Spring容器、SpringMVC、MyBatis、事务管理器全部放在config包下的配置类中代码量比XML少而且便于演示。Configuration ComponentScan(basePackages com.supermarket) MapperScan(com.supermarket.dao) EnableTransactionManagement public class AppConfig { Bean public DataSource dataSource() { DruidDataSource ds new DruidDataSource(); ds.setDriverClassName(com.mysql.cj.jdbc.Driver); ds.setUrl(jdbc:mysql://localhost:3306/supermarket_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4); ds.setUsername(root); ds.setPassword(你的数据库密码); return ds; } Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory new SqlSessionFactoryBean(); factory.setDataSource(dataSource); // 配置下划线转驼峰实体类里不需要起一大堆带下划线的字段 org.apache.ibatis.session.Configuration config new org.apache.ibatis.session.Configuration(); config.setMapUnderscoreToCamelCase(true); factory.setConfiguration(config); return factory; } Bean public PlatformTransactionManager txManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }3.2 统一返回格式与接口约定前端和后端分离之后首先要约定一个统一响应的结构。如果不做统一封装有人返回字符串、有人返回Map前端要写无数个判断后期维护就是噩梦。我的习惯是接口不管成功还是失败都返回一个Result对象public class ResultT { private Integer code; // 200成功500业务异常401未登录 private String message; private T data; public static T ResultT success(T data) { ... } public static T ResultT error(String message) { ... } }Controller层不要直接返回实体类或Map而是统一返回Result。业务异常通过自定义RuntimeException抛出再用RestControllerAdvice做一个全局异常处理器把异常信息转换成Result返回。这样前端拿到结果后只需要判断code是否等于200等于就展示data不等于就ElMessage.error(message)逻辑非常干净。3.3 登录拦截和权限控制的设计取舍用户登录成功后后端返回一个token可以是UUID字符串也可以直接签一个简单的JWT前端把token存在localStorage里每次请求在拦截器里加上Authorization请求头。后端写一个LoginInterceptor拦截除登录接口以外的请求从request里取出token查redis或数据库看是否有效。在实现资源级权限时最简单但有效的做法是把权限要求写进注解或直接在每个Controller方法入口判断当前登录用户的role。比如收银台相关的接口就在入口处判断role是否等于cashier或admin如果不是就返回无权限。这种方式看起来没有Spring Security专业但好处是代码量少、运行流程容易讲拦截器负责登录身份方法入口判断角色。答辩时如果你能说清楚“为什么选择拦截器而不是过滤器”就已经体现出差异性拦截器工作在Spring MVC的HandlerMapping之后能拿到HandlerMethod和Bean更适合做业务层的权限判断。3.4 收银结算事务的完整代码逻辑收银是整个超市系统里逻辑最重的一块也最适合在论文里作为核心模块重点分析。收银接口接收的数据是什么前端传过来一个购物车数组每个元素包括productId和quantity绝对不应该允许前端传单价和商品名过来。后端要做的事情有校验商品是否上架、重新从数据库读售价、计算总价、生成唯一订单号、插入订单主表、批量插入订单明细、扣减库存整个过程必须在一个事务里完成。Service实现的核心思路如下Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderDTO dto) { if (dto.getItems() null || dto.getItems().isEmpty()) { throw new BusinessException(购物车不能为空); } // 1. 从商品表查出本次参与结算的商品组装商品信息Map // 2. 校验商品状态遇到已下架商品直接中断 // 3. 遍历购物车用数据库里的salePrice计算subtotal和total // 4. 生成订单号DD yyyyMMddHHmmss 随机4位 // 5. 插入orders主表 // 6. 批量插入order_item明细表 // 7. 循环扣减库存扣减语句必须带库存充足条件 for (CartItemDTO item : dto.getItems()) { int rows productDao.deductStock(item.getProductId(), item.getQuantity()); if (rows 0) { throw new BusinessException(商品已卖完或库存不足); } } return orderVO; }这里扣库存要特别注意正确做法不是先select stock再update stock而是用一条条件更新完成UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}执行这条SQL返回的影响行数是0说明库存不够直接抛业务异常回滚整个事务。如果先查询再更新会出现两个线程都查到库存为1都认为库存足够最后卖出去两件只扣了一件的问题。我们把“校验库存”和“扣减库存”合并成一条原子SQL等于给关键资源上了一把隐式锁同时也能满足超卖校验的需求这就是答辩时最该讲清楚的细节。4. 前端Vue工程化从页面到数据连接4.1 前端项目初始化与依赖安装前端使用Vue CLI创建项目创建命令我用的是vue create supermarket-web。创建完安装Element UI、Axios、ECharts和Sass相关依赖。装依赖有一件事要提前规避Vue 2和Element UI的兼容最稳的方案是Vue 2.6 Element UI 2.15不要图新鲜去尝试Vue 3 Element Plus因为很多毕业设计模板都基于Vue 2网上可复制的代码和踩坑方案也最多。有些同学喜欢在index.html里通过CDN引入Vue和组件库这样做省安装但答辩时导师问你这个项目的前端工程化在哪工程化指项目的模块化拆分、构建编译、依赖管理不是把页面跑出来就叫工程化。所以尽量启用标准Vue CLI项目结构。npm run serve跑起来之后打开页面开发环境默认端口是8080后端接口跑在8081或者8080都要在配置文件里标清楚避免后面联调时自己都分不清。4.2 路由设计登录后如何做权限判断Vue Router本身只负责管路由是否登录、是否有权限需要自己实现。我在做这套系统时是这样设计的路由先分成两个层次一层是login不需要权限另一层是布局组件Layout下的所有业务页面路由都设置meta:{requiresAuth:true}并且meta里再标一个roles数组比如收银台的meta.roles [admin,cashier]。在全局前置守卫里每次跳转前先判断当前路由是否需要登录需要就检查localStorage里有没有token没有就跳转到/login有token但用户信息已经丢失可以从后端拉一次用户信息再放行。在菜单栏上根据本地存的用户角色动态过滤路由表只展示当前角色能看的菜单。这样处理虽然代码量多一些但体验像真正的后台管理员登录看到全功能菜单收银员登录看不到成本和报表。4.3 Axios封装统一处理token和错误码写管理系统的时候每个页面组件里直接调用this.$http.get请求路径写死token处理在每个请求里手动加这种做法不是不能用但后端一旦改了接口路径或者要加统一超时时间就能让你改到怀疑人生。我在项目里单独封装一个utils/request.js把所有请求统一走这个实例import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: /api, // 开发环境使用vue.config.js代理 timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.message)) } return res }, error { Message.error(网络连接异常) return Promise.reject(error) } ) export default service编码时有一个重要的思维转换axios的response拦截器里正常return的是res而不是response也就是说页面组件里调用接口拿到的直接是后端Result的data部分而不是整个HTTP响应这样业务里写代码会清爽非常多。这个约定最好在描述接口文档时写清楚否则前后端负责人不一样时容易产生误解。4.4 收银台、商品列表和报表页面的落地写法页面里功能复杂程度排序大概是收银台 商品管理 报表。以收银台为例页面总体分成三块左侧上下两个区域上面是商品搜索框下面是搜索结果表格右侧是购物清单和结算按钮。搜索商品时调用按关键词查询在售商品的接口后端返回商品名称、售价、剩余库存点击“加入购物车”时把商品推进当前页面维护的cart数组同一商品重复加入就把数量加一点击“结算”前先禁用所有按钮防止重复提交拿到订单号之后清空购物车并弹出找零信息。报表页面用ECharts展示。后端提供两个接口按月分组统计总销售额、按商品统计销售数量Top10。前端用接口返回的数组分别给折线图和柱状图提供数据。ECharts在项目中不要用全量导入而是按需从echarts/core里引BarChart、LineChart等可以明显减少打包体积同时启动时不会发出“图例未注册”的警告。如果我的ECharts初始化和销毁时机不对还可能出现在多个页面来回切换时图表不能自适应宽度的问题建议在组件mounted里初始化在beforeDestroy里调用dispose销毁并监听窗口resize事件触发chart.resize()。5. 从源码到运行数据库初始化、打包与部署5.1 三步完成数据库初始化无论哪一台电脑上把项目跑起来都绕不开数据库初始化。第一步先把MySQL服务启动用Navicat或命令行执行CREATE DATABASE IF NOT EXISTS supermarket_db DEFAULT CHARACTER SET utf8mb4。第二步打开源码目录下提供的supermarket_db.sql脚本直接导入整个数据库。SQL脚本如果是我来组织会严格包含建库语句、建表语句、初始数据三部分这样执行一次就能得到可以测试的完整数据。第三步验证一下导入结果重点看product表有没有数据、user表有没有初始账号如果user密码是加密存入的还需要先手工执行一次密码加密或者跑一个初始化类写入数据很多同学卡在这一步打不开登录页就是因为用户表里是空数据或者密码存储格式不匹配。5.2 改配置和后端启动拿到源码后需要修改的位置有两个数据库账号密码以及如果有文件上传功能会配置本地上传路径。数据库配置如果不改遇到Connection refused或者Access denied是必然的。改完后用IDEA以Maven方式导入项目等待依赖下载完然后在运行配置里选择Tomcat Server部署方式选择war exploded模式Application context设置为/supermarket。启动后端浏览器访问http://localhost:8080/supermarket/user/login这个接口能返回JSON就说明后端程序通了。如果你拿到或写的是前后端分离版的spring项目后端是独立的Spring MVC工程运行在Tomcat里面而前端独立打包成静态文件那么只要后端接口能访问到JSON前端就可以直接对接。中间联调最容易踩坑的是启动后控制器路径写错扫不到建议路径放在Controller类上统一加一个前缀比如/api。这样Tomcat的context-path、Controller前缀、前端baseURL三者规则清清楚楚出一个错看一眼URL就能定位。5.3 前端打包并解决刷新404问题前端开发联调通常用vue.config.js里的devServer配置代理把所有/api开头的请求转发给后端module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }前端打包时执行npm run build生成一个dist目录。这个目录直接扔给Nginx托管是最省心的。在nginx.conf里做两个关键配置server负责托管静态文件location /api/把请求反向代理到后端Tomcat地址。如果托管静态文件时用了history路由还需要加一个try_files配置将路径重写到index.html否则直接访问某个子路由时刷新就会404。server { listen 80; server_name localhost; location / { root /usr/share/nginx/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }没有Nginx环境、又懒得装的同学可以把dist目录里的静态文件直接复制到Tomcat的webapps/ROOT下面但这时要考虑路由模式问题如果Vue Router用的是history模式刷新子路由会404因为Tomcat不会像Nginx那样帮你重写路径。所以这种简单部署方案下路由模式改回hash模式即URL里带#刷新后不会向服务器发子路径请求问题就消失了。这是我在帮很多同学排错时总结的规律先确认你当前用的是history还是hash再决定要不要改Nginx配置。6. 常见问题排查与避坑清单6.1 后端起不来先查这5个地方SSM项目最常见的启动失败不是代码逻辑错了而是在环境集成上面。我归纳了一份排查清单遇到问题时按照这个顺序过一遍症状最可能原因处理方法启动就报ClassNotFoundExceptionpom依赖版本冲突确认Spring、MyBatis、Java版本兼容clean后再reimport报Error creating bean with name没有配置Mapper扫描检查MapperScan路径是否指向dao包LocalDateTime序列化格式不对jackson缺少JavaTimeModule注册Jackson的ObjectMapper或者把实体时间字段改为java.util.Date并配置日期格式连不上数据库URL、账号密码、驱动不一致检查MySQL版本用mysql-connector-java 8.x带上serverTimezone参数一个字段查出来一直是null实体属性与表字段下划线映射没开启设置mapUnderscoreToCamelCasetrue后端排查有个笨但有效的方法启动日志里找第一句红色ERROR不要看最后那一堆Caused by栈顶。红色ERROR下面一般会有一个更自然的描述它才是根因后面那串堆栈往往只是MyBatis或者Spring框架包的“连锁反应”。6.2 前端数据出不来按这个顺序排查前端最常见的尴尬是接口明明能通页面上表格一直空。我的建议是先看浏览器开发者工具的Network面板点开那条请求把响应体格式化看返回的是什么结构。如果返回的是code200且data是一个数组再检查页面里变量名是否正确如果返回的是HTML格式的404页面说明请求地址没匹配到后端就要先检查代理路径设置和后端接口前缀这属于联调路径问题而不是页面代码问题。第二个高频问题是跨域不过现在大部分联调已经通过vue.config.js代理避免掉了。如果某些环境必须直接请求后端域名比如后端跑在8080前端页面跑在3000就会触发跨域。解决方式是在后端全局配置一个CorsFilter允许本地的3000来源如果是在生产环境用Nginx则可以把前后端放在同一个server下通过location路径分发从根源上消除跨域。还有一个很低级但常见的错误请求成功了但页面显示乱码。数据库连接串里没带characterEncodingutf8mb4或者页面没写都可能造成乱码。出现中文乱码时我习惯从前端请求头到后端response的编码设置一路查下去因为这类问题定位不难但如果不知道在数据库连接时加上characterEncoding能折腾一晚上。6.3 答辩演示前最容易被暴露的问题按照我给身边同学模拟答辩的经验下面几个细节是最容易被现场问住或演示翻车的点第一没有初始化演示数据登录进去看到空荡荡的页面只能现场手工添加数据非常被动。我一般会准备一套商品分类、20个商品、3个用户、2个供应商的完整初始数据订单至少保留两天10笔以上演示报表时直接有图可看。第二为了演示方便让代码里没有做权限控制老师切到收银员账号后发现还能打开报表这就很尴尬所以前面说roles校验虽然要写几行代码但这个钱不能省。第三演示时只用同一个账号没有提前准备管理员、收银员两个浏览器的独立登录会话临时切换登录要退出再登录停顿一下就会显得对系统不熟悉。数据库这块也容易被问到退款单怎么处理我的处理方式不是从order_item里删除明细而是把主表status置为退款同时用一条SQL把库存加回去。如果删除订单明细报表统计历史数据就会缺失财务审计也会出问题。这种细节我没有写在代码注释里但会在论文的详细设计部分写清楚答辩时主动讲出来明显能看出是思考过业务的。最后再分享一个我认为很值得推荐的调试习惯前后端联调阶段不要只看前端控制台报错信息打开Network面板点开具体请求先看请求URL、请求方法和响应体。很多“页面没反应”“数据不显示”的问题八成是请求压根没发出去或者返回状态码401被拦截器拦住了。经常盯Network面板能明显减少到处乱改代码的时间消耗。做这种管理系统本身不难难就难在遇到问题时有没有一套稳定的排查思路而不是慌里慌张地重启、清缓存、删代码。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →