每年九十月份,总有一批学生为毕业设计选题挠头。找我咨询的十个人里有八个开口就是"图书馆管理系统""网上购物商城""校园二手交易",我听到这几个题就知道,答辩现场大概率又是一场撞车。不是说这些题目不行,而是做的人太多,评委连续看三四个同题项目,审美疲劳之后你再想拿高分就难了。今年我带学生完成了一套曲沃县农产品销售系统,SSM框架加Java语言,源码、数据库脚本、配套论文全部落地,整个流程走下来效果不错。这篇文章把项目从定题到答辩的完整思路拆开写清楚,既讲技术实现,也讲论文和答辩怎么配合,2026届正在纠结毕设题目的同学可以直接拿来做参考。
先交代一下这个系统到底是干什么的。曲沃县在山西南部,小麦、大蒜、苹果等农产品在周边市场一直有真实需求,但销售渠道多数还停留在批发商上门收购和赶集零售。系统做的事情,就是把这些农产品从线下搬到线上:农户或者合作社入驻之后发布商品,普通用户注册登录、浏览分类、搜索商品、加购物车、下单结算,管理员在后台维护商品分类、审核上架、处理订单和用户。从功能量级上看,它不比市面上的中小型电商复杂,但恰恰是"完整电商主链路加上县域农业特色字段"这个组合,让它在毕设里既不容易撞题,又有足够的业务逻辑可以展开写。
1. 定题逻辑:为什么"曲沃县农产品销售系统"是安全牌也是加分牌
1.1 县域农产品电商的真实痛点,恰好就是功能需求清单
我帮学生定题有一条原则:题目不能假大空,最好能对应一个真实场景,这样需求分析阶段才有东西可写,而不是堆一堆空话。农产品销售这个方向天然带痛点:信息不对称,农民不知道哪里有买家,买家不知道哪里能买到优质农产品;中间环节多,层层批发之后价格被抬高,农户利润被压薄;本地农产品缺少统一的线上展示入口,尤其是一些县域特色品类,出了本地就没什么声量。
把这些痛点翻译成系统功能,就是几个直白的模块:商品展示解决"看不到"的问题,分类和搜索解决"不好找"的问题,购物车和订单解决"交易链路"的问题,后台管理解决"谁审核谁发货"的问题。痛点转需求、需求转功能,这个逻辑链在论文的需求分析章节里可以写得很扎实,评委问起来也经得起推敲。相比之下,"网上商城"这种题很难答出"为什么需要它",而农产品销售系统天然自带答案。
1.2 功能边界怎么划:三个角色加一条销售主线
毕设最怕功能堆砌。有的学生为了显得工作量足,硬塞进秒杀、拼团、直播带货,结果每块都做不深,答辩时被追问两句就露馅。我们最终把系统定位得非常克制:三个角色,一条销售主链路。
- 管理员:用户管理、商品分类管理、商品审核与上下架、订单管理、公告管理。
- 商家(农户/合作社):商品发布与编辑、库存维护、订单发货、查看销售数据。
- 普通用户:注册登录、浏览商品、搜索筛选、购物车、下单结算、订单查询、商品评价、收藏。
三个角色刚好覆盖了RBAC(基于角色的访问控制)的知识点,也制造了多表联查的场景,比如订单表关联用户表、商品表、地址表。但整体功能量又控制在一个学生几周内能完成的范围。我经常提醒学生:毕设不是创业项目,功能宁缺毋滥,把订单这条主链路做通顺,比做出十个半成品页面有意义得多。
1.3 工作量估算:一个学期业余时间能不能扛住
说句实话,这套系统如果每天能抽两到三个小时,整体节奏大概是:数据库设计两到三天,环境搭建一到两天,核心代码三周左右,测试和修Bug三到五天,论文集中写作两周,答辩准备两三天。加起来五到六周的有效时间,分散在一个学期里完全能完成。这也是我选这个题的重要原因之一——它给"源码+论文"的完整交付留足了缓冲,不会出现最后一周通宵赶工的狼狈局面。
2. 技术选型:2026年还学SSM,学的到底是什么
2.1 SSM三件套在系统里各管哪一段
SSM指的是Spring、SpringMVC、MyBatis三件套,很多学生一听就皱眉,觉得这是老框架,2026年是不是该直接上Spring Boot。我的看法是,毕设恰恰是最后一段可以系统学习SSM的机会。Spring Boot确实把配置简化了,但如果你连自动配置背后帮你在做什么都不清楚,遇到问题就只能靠网上搜答案碰运气。
具体到这套系统:Spring负责对象管理和事务控制,所有Service层的Bean由容器创建,事务的开启和回滚交给AOP处理;SpringMVC负责请求路由,浏览器发来的HTTP请求先经过DispatcherServlet,再根据URL匹配到Controller;MyBatis负责持久层,把Java对象和数据库记录之间做映射。三个框架各司其职,分层清晰——表现层、业务层、持久层,这个金字塔结构也是论文里技术架构图的标准画法。学会了这套东西,再去看Spring Boot,会发现它只是把Spring生态的"工厂化配置"打包了,底层逻辑完全同源。
2.2 环境版本组合不折腾:这套配置实测最稳
环境配置是很多学生第一个翻车的点,网上教程版本又老又杂,照着敲就是起不来。这半年我们验证下来,这套组合最省心:
| 组件 | 版本建议 | 备注 |
|---|---|---|
| JDK | 1.8 | 稳定,兼容所有老教程 |
| Maven | 3.6.x | 3.8以上个别镜像源会出问题 |
| Tomcat | 9.0.x | 配合JDK8无坑 |
| MySQL | 8.0 | 注意驱动用com.mysql.cj.jdbc.Driver |
| Spring | 5.3.x | 不用5.0老版本,不用6.x |
| MyBatis | 3.5.x | 3.5.13实测稳定 |
Java环境配置这里单独提醒一句:很多人电脑里装了多个JDK版本,系统变量JAVA_HOME指向的还是旧路径,导致Maven编译时JDK版本对不上。建议把JAVA_HOME、PATH、MAVEN_HOME统一整理一遍,命令行里执行java -version确认无误再往下走,免得后面报各种看不懂的UnsupportedClassVersionError。pom.xml里也建议显式声明编译版本:
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>2.3 手写MyBatis还是上MyBatis-Plus
现在网上一搜,会有不少教程告诉你MyBatis-Plus可以"根据实体类自动生成建表SQL",听着很诱人,省掉了手写DDL的时间。这个功能确实存在,但我要泼盆冷水:毕设项目里我不建议你这么干。用MyBatis-Plus生成的建表语句,字段类型和长度经常跟你的需求对不上,外键关系、索引设计、默认值约束它不会替你想清楚,等你后面发现表结构不合理再改,迁移数据的成本比一开始手写高得多。
更关键的是答辩环节。评委看到你的持久层全是BaseMapper接口,问一句"这个SQL具体是怎么执行的",你如果答不上来,印象分会打折扣。我带学生做这个项目时用的是原生MyBatis,手写Mapper接口和XML映射文件。工作量并没有大多少,一张表的增删改查XML也就几十行,但对SQL的理解完全是两个层次。你写一次<where>动态SQL,下次遇到多条件搜索就不会慌;你写一次<resultMap>,就明白Java属性跟数据库字段名为什么要对齐。这些底层能力,恰恰是培训机构速成学员最缺的。
3. 数据库建模:12张表怎么把业务串起来
3.1 核心表清单与设计思路
数据库设计是毕设的骨架,骨架歪了后面写代码全是窟窿。我们最后定了12张表,不多不少刚好覆盖三个角色的全部需求:
| 表名 | 作用 | 核心字段 |
|---|---|---|
| t_user | 用户表,管理员/商家/买家都在里面 | id, username, password, role, phone, avatar, create_time |
| t_address | 收货地址表 | id, user_id, receiver, phone, province, city, detail |
| t_category | 商品分类表 | id, name, parent_id, sort |
| t_product | 商品表 | id, seller_id, category_id, name, price, stock, origin, image, status |
| t_cart | 购物车表 | id, user_id, product_id, quantity, checked |
| t_orders | 订单主表 | id, order_no, user_id, total_amount, status, address_snapshot, create_time |
| t_order_item | 订单明细表 | id, order_id, product_id, product_name, price, quantity |
| t_comment | 商品评价表 | id, user_id, product_id, order_id, content, score, create_time |
| t_favorite | 收藏表 | id, user_id, product_id, create_time |
| t_announcement | 公告表 | id, title, content, create_time |
| t_product_image | 商品图片表 | id, product_id, image_url, sort |
| t_login_log | 登录日志表 | id, user_id, login_time, ip |
这里就有几个关键决策。第一,用户表里用role字段区分三种身份,而不是拆成admin表和seller表,因为三个角色的公共字段高度重合,一张表加角色枚举最简单,权限拦截在代码层判断即可。第二,商品价格用DECIMAL(10,2),绝不能用FLOAT,浮点数的精度问题在金额计算上会爆炸。第三,订单表里存了address_snapshot,这是很多新手会忽略的点——收货地址必须在下单那一刻快照进订单,否则用户之后改了收货地址,历史订单的收货信息也跟着变,业务上就闹笑话了。
3.2 订单与明细的一对多:别在设计阶段就把路走死
订单表是整套系统里设计难度最高的一张表。一个订单可以包含多个商品,所以必须有订单主表和订单明细表两张表,通过order_id外键关联。主表存订单级别信息:订单编号、总金额、状态、下单时间;明细表存商品级别信息:商品ID、商品名称、单价、数量。这里要特别注意的是,商品名称和单价这两个字段在明细表里必须冗余存储,不能只存product_id。原因是商品随时可能改名或者调价,如果明细表只关联ID,等你查询历史订单时,显示出来的价格可能是最新的,而不是用户当时付的价格。
订单状态建议用整型枚举:0待支付、1已支付待发货、2已发货、3已完成、4已取消、5退款中。系统里所有判断状态的地方统一定义一个常量类,不要魔法数字散落各处。订单编号不要用数据库自增ID,业务上应该生成一个类似QW20260601001的字符串,前缀Q代表曲沃首字母,中间是下单日期,后缀是当天序列号,这样用户和客服一眼就能看懂。
3.3 农产品特有的字段:产地与季节怎么融入
既然定位是曲沃县农产品销售系统,商品表里就必须有农业特色字段,这是和普通电商区分开的关键。我们在t_product里设计了origin产地字段、season上架季节字段、quality_grade品质等级字段。产地字段既能满足"曲沃县本地优质农产品"的定位,也为以后做"按产地筛选"预留了空间;品质等级用A/B/C级枚举,让农产品区别于标准化的工业品。
以商品表为例,建表SQL核心部分长这样:
CREATE TABLE t_product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seller_id BIGINT NOT NULL COMMENT '商家用户ID', category_id BIGINT NOT NULL COMMENT '分类ID', name VARCHAR(100) NOT NULL COMMENT '商品名称', price DECIMAL(10,2) NOT NULL COMMENT '售价', stock INT NOT NULL DEFAULT 0 COMMENT '库存', origin VARCHAR(50) DEFAULT '曲沃县' COMMENT '产地', season VARCHAR(20) COMMENT '上架季节', quality_grade CHAR(1) DEFAULT 'B' COMMENT '品质等级 A/B/C', image VARCHAR(255) COMMENT '主图路径', status TINYINT DEFAULT 0 COMMENT '0草稿 1在售 2下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category (category_id), KEY idx_seller (seller_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';字段注释一定要写全,不然后面写论文的数据库设计章节时,你得回头一个一个猜字段含义,那滋味相当难受。字符集统一用utf8mb4,别用utf8,避免生僻字或者特殊表情符号写入报错。索引不需要多,商品表的category_id和seller_id建普通索引就够了,毕设级别的数据量索引健全即可,别过度设计。
4. 核心代码实现:登录、分页、下单三个关键环节写透
4.1 登录态保持与权限拦截:Session方案在SSM里最顺
电商系统里很多页面都需要用户登录之后才能访问,比如购物车、订单、个人中心。SSM项目里最顺手的方案就是Session加拦截器,而不是引入JWT或者Spring Security。JWT是无状态方案的典型代表,但它对Token过期、刷新、注销的处理很繁琐,在服务端渲染的JSP项目里属于自找麻烦。Spring Security功能强大但学习曲线陡,毕设答辩讲不清楚还容易被反噬。
我们用一个LoginInterceptor实现登录拦截,在SpringMVC配置里声明哪些路径需要过滤:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/register"/> <mvc:exclude-mapping path="/css/**"/> <mvc:exclude-mapping path="/js/**"/> <mvc:exclude-mapping path="/images/**"/> <bean class="com.quwo.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>拦截器里的核心逻辑:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { // 未登录,重定向到登录页,并记录来源以便登录后跳回 response.sendRedirect(request.getContextPath() + "/login"); return false; } // 管理端接口额外校验角色 if (request.getRequestURI().startsWith("/admin") && !"ADMIN".equals(user.getRole())) { response.setStatus(403); return false; } return true; }这里有个容易被忽略的细节:静态资源放行一定要做,否则CSS和JS全部被拦截,页面打开就是一堆裸标签。另外,用户密码存储不要用明文,MD5加盐是最低要求。盐值可以取用户ID加一个固定字符串,注册时算好再入库,登录时同样规则计算后比对。
4.2 商品分页查询:先用SQL把道理讲明白
商品列表页和后台的商品管理页都离不开分页。最原始的方式是手写LIMIT #{offset}, #{pageSize}加一条SELECT COUNT(*),两步拼接出总页数。这种方式逻辑透明,但每次都要写两条SQL,多条件筛选时还要保证两条SQL的where条件一致,容易漏改。
我在项目里引入PageHelper做分页插件,用法极其简单:
PageHelper.startPage(pageNum, pageSize); List<ProductVO> list = productMapper.selectByCondition(categoryId, keyword, minPrice, maxPrice); PageInfo<ProductVO> pageInfo = new PageInfo<>(list);PageHelper在底层借助拦截器自动改写SQL,把limit和count语句都补齐。但我还是建议学生先手写一遍分页,再引入插件,这样答辩时被问到"分页原理",你能说清楚PageHelper帮你在哪个环节做了SQL改写,而不是只会说"我用了插件"。排序需求也用SQL解决,价格升序降序、销量优先,都是ORDER BY后面拼条件,没有任何必要在Java内存里做排序。
4.3 下单事务:这里就是把扣库存和写订单框在一起
下单是整个系统最核心、也最容易出Bug的环节。一个完整的下单操作包含五个步骤:校验商品和库存、计算总金额、扣减库存、生成订单主记录、生成订单明细。这五步必须在一个数据库事务里完成,任何一步失败,前面所有的操作都要回滚,绝不能出现"订单建了但库存没扣"或者"库存扣了但订单没生成"的中间状态。
Service层代码如下:
@Override @Transactional(rollbackFor = Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { List<CartItemVO> items = cartMapper.selectCheckedItems(dto.getUserId()); if (items == null || items.isEmpty()) { throw new BusinessException("没有选中的商品"); } BigDecimal total = BigDecimal.ZERO; for (CartItemVO item : items) { // 扣库存时利用库存充足条件防超卖 int rows = productMapper.deductStock(item.getProductId(), item.getQuantity()); if (rows == 0) { throw new BusinessException("商品[" + item.getProductName() + "]库存不足"); } total = total.add(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); } Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setTotalAmount(total); order.setStatus(0); order.setAddressSnapshot(orderMapper.selectAddressById(dto.getAddressId())); orderMapper.insert(order); // 插入订单明细、清空购物车,此处代码省略 return buildOrderVO(order); }扣库存的SQL值得单独拿出来说,这是防止超卖的关键:
<update id="deductStock"> UPDATE t_product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity} </update>AND stock >= #{quantity}这一步非常重要,它保证扣减操作只会发生在库存充足的前提下。如果库存不够,受影响行数是0,程序就能感知到并抛异常回滚。这比先SELECT再判断库存的方式安全得多,因为数据库层面的条件判断是原子性的,不会出现两个请求同时读到相同库存然后同时扣成功的并发问题。毕设答辩时,评委很爱问"超卖怎么办",把这个SQL逻辑讲清楚就是漂亮的加分回答。
5. 实测踩坑记录:这几个问题不处理,演示当场翻车
源码写出来只是第一步,真正让它稳定跑起来,中间有不少坑。下面这几个是我们实测过程中都踩过、也都解决了的问题,每个都说清楚现象、原因和修复方法。
5.1 后端返回JSON,日期字段却变成一串数字
后端接口返回订单列表时,前端拿到的时间字段是一长串数字。原因是Java的java.util.Date默认序列化成时间戳,而不是可读的日期字符串。修复方式是在日期字段上加注解:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime;注意timezone一定要指定为GMT+8,否则序列化出来的时间比北京时间少8小时,排查起来很隐蔽。如果项目里日期字段多,也可以配置一个全局的ObjectMapper,但这套系统里用注解逐个标记更直观。
5.2 商品图片上传成功,页面里却一片空白
图片能传到服务器本地磁盘,但前端访问不到,因为上传目录在工程目录之外,Tomcat默认的静态资源映射覆盖不到。这需要在SpringMVC配置里加一个资源映射,把/upload/**这个URL路径指向磁盘上的真实上传目录:
<mvc:resources mapping="/upload/**" location="file:D:/projects/quwo/upload/"/>同时注意,数据库里存的应该是相对路径,比如/upload/202606/xxx.jpg,不要存绝对路径。否则换一台机器部署,所有图片地址全废。受保护目录映射到外网访问本身有安全风险,但毕设项目只要做好上传文件类型白名单校验就够用,比如只允许jpg、png、gif。
5.3 明明加了@Transactional,数据还是写进去了
这是个经典问题。后台Admin在发货操作里更新了订单状态,同时更新了库存扣减,结果测试发现订单执行到一半抛出异常,数据库里却留下了半截状态。排查到最后发现,方法内部用try-catch把异常吞掉了:
public void shipOrder(Integer orderId) { try { updateOrderStatus(orderId, 2); updateStock(...); } catch (Exception e) { e.printStackTrace(); // 异常没有抛出,事务感知不到,不会回滚 } }Spring声明式事务默认只对RuntimeException和Error回滚,对CheckedException不回滚。而这个案例里问题更严重——异常在try里被捕获后根本没有向上抛,事务管理器完全不知道出错了,于是在finally里正常提交。修复方法是:要么不捕获,要么捕获后重新抛出;同时在@Transactional上显式声明rollbackFor = Exception.class,把所有异常都纳入回滚范围。这是一个非常高频的"自以为加了事务实际上没加"的案例,排查时一定要先确认异常到底有没有离开方法边界。
5.4 分页总数不对,总数永远等于第一页条数
引入PageHelper之后,列表页点击第二页时数据正常,但总页数显示只有1页。原因是对PageHelper的调用时机理解错了——startPage必须紧接着放在查询语句的前一行,中间不能插入任何其他数据库操作。更隐蔽的错误是XML里写了多条SQL,PageHelper不知道给哪条语句分页。解决方案是确保每个查询对应独立的Mapper方法,startPage到select之间不要做任何查询动作。
5.5 表单提交中文乱码,数据库里存的全是问号
乱码问题通常来自三层:请求、响应、数据库连接。我在web.xml里配置了CharacterEncodingFilter,强制所有请求和响应使用UTF-8;数据库连接URL加useUnicode=true&characterEncoding=utf8参数;MySQL表结构统一utf8mb4。这三层都覆盖之后,乱码基本不会再出现。顺带一提,如果你用Tomcat 9,server.xml里连接器的URIEncoding没必要再手动设置,9.0默认UTF-8。
6. 论文编排:源码之外,论文才是拿分大头
很多学生有个误解:代码写完,项目就算做完了。实际上毕业答辩的评分里,论文和代码往往各占半壁江山,代码写得再好,论文像流水账一样,照样拿不到好成绩。这套系统的论文我们是这样编排的。
6.1 目录结构:六章制,层层递进
- 摘要:概括系统背景、技术路线、实现功能,中文摘要加英文摘要。
- 第1章 绪论:研究背景与意义、国内外研究现状、研究内容与论文结构安排。
- 第2章 关键技术介绍:SSM框架、Java、MySQL、前端技术,每项写清楚作用和选型理由。
- 第3章 需求分析:可行性分析(技术、经济、操作)、功能性需求、非功能性需求、用例图。
- 第4章 系统设计:总体架构设计、功能模块设计、数据库设计(E-R图、表结构)。
- 第5章 系统实现:按模块展示界面截图和核心代码,配文字说明实现逻辑。
- 第6章 系统测试:测试环境、功能测试用例表格、性能测试结果。
- 结束语、参考文献、致谢。
这个结构是本科毕设的标准范式,只要按这个骨架填充,论文的完整度不会有问题。关键在每章的具体写法:第3章的需求分析要能追溯到第5章的每个功能,第4章的设计要和第3章的需求一一对应,第5章讲的代码逻辑要和第4章的设计吻合。论文最怕的就是章节之间各说各话,评委交叉一看,发现设计里写的功能实现里根本没有,直接就是大错。
6.2 图表规范:哪些图必须有
图表是论文的颜值担当。我们这套系统里,以下图是必须有的:系统总体架构图、功能结构图、管理员和用户用例图、数据库E-R图、下单流程图。画图工具用draw.io或者ProcessOn就行,风格统一为黑白线条加深蓝色点缀,避免花哨配色。图的编号和标题要规范,"图4-1 系统总体架构图",在正文中先出现引用语句,再放图。这里提醒一句:画架构图时,SSM三层结构一定要画明白,表现层、业务层、持久层加数据库,这叫技术选型在架构上的落地,评委扫一眼就知道你有没有真正理解自己的项目。
6.3 测试章节:不要只写"测试通过"
测试章节是很多学生敷衍的重灾区,写来写去就是"经测试,系统运行正常"。这样写等于没写。正确做法是设计功能测试用例表,把每个功能模块的测试输入、预期结果、实际结果列出来:
| 编号 | 测试模块 | 测试步骤 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|---|
| TC-01 | 用户登录 | 输入正确用户名、密码 | 跳转首页,显示用户名 | 与预期一致 | 通过 |
| TC-02 | 用户登录 | 输入错误密码 | 提示"用户名或密码错误" | 与预期一致 | 通过 |
| TC-03 | 下单 | 购物车选两件商品结算 | 库存扣减,订单状态待支付 | 与预期一致 | 通过 |
| TC-04 | 下单 | 商品库存为0时购买 | 提示"库存不足" | 与预期一致 | 通过 |
每张测试用例表10到20条用例,功能覆盖每个模块的关键路径和异常路径,这个工作量花不了太多时间,但论文的可信度会明显提升。性能测试简单用JMeter跑一个并发脚本,比如100个线程同时访问商品列表,记录响应时间和吞吐量,写一段结果分析,这块内容在论文里的观感会非常加分。
7. 答辩实战:评委高频问题与演示节奏
代码和论文都交上去之后,最后的战场是答辩现场。根据我带过的多届学生反馈,评委对这套农产品销售系统的提问高度集中在几个方向,提前准备答案比临场发挥靠谱得多。
7.1 高频问题与应答思路
"为什么用SSM,不用Spring Boot?"这个问题的核心不是让你贬低Spring Boot,而是考察你对框架的理解。可以这样答:毕设的目的是理解Web开发的分层原理,SSM把Spring的IOC和AOP、SpringMVC的请求分发、MyBatis的数据映射分得清清楚楚,用它能更直观地掌握框架底层机制;同时在项目里也做了通用配置抽取,理解了这些配置,迁移到Spring Boot只是模板化的事。
"你的密码安全怎么处理的?"答:用户密码采用MD5加盐存储,每次注册生成随机盐值,和密码拼接后计算哈希,数据库里不存明文。如果想说更高级一点,可以提一句Spring Security或者加盐迭代,但点到为止,别把话题引向自己不懂的加密算法。
"数据库SQL注入怎么防?"答:MyBatis的Mapper全部使用#{}预编译参数,由JDBC的PreparedStatement执行,参数不会参与SQL拼接;动态排序、动态字段这类必须拼接的场景,用了白名单校验,不允许外部字符串直接进入SQL片段。
"库存超卖怎么处理?"答:扣减库存使用UPDATE t_product SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity},利用受影响行数判断是否库存不足,数据库层面保证原子性,配合事务回滚,从根上避免超卖。这个问题只要答到"更新时加库存条件判断"这个点,基本就过关了。
"系统有哪些不足?"这种问题不要直接说"没有不足"。坦诚提一个可改进点,比如:当前支付是模拟支付,后续可以对接微信支付或支付宝沙箱;权限控制基于Session和拦截器,生产环境可以考虑引入Spring Security和JWT。回答了改进方向,反而显得你有独立思考能力。
7.2 演示环节的顺序安排
答辩演示通常只有五到十分钟,节奏不能乱。我的建议是:先花一分钟讲系统定位和角色,然后从用户端登录开始演示——登录后浏览商品分类、搜索一个具体商品、查看详情、加入购物车、结算下单,这一步走完整条主链路大概两分钟;接着切到后台演示订单管理,把刚才下的订单进行发货处理,同时展示商品管理和用户管理界面。演示的核心原则是:把最熟练的主链路放在中间状态最饱满的时刻,后台管理点到为止,不要漫无目的地到处点页面。如果现场网络或服务器状态不佳,提前准备一份录屏视频备份,这是最稳妥的措施。
8. 交付与复盘:一份合格的毕设应该包含哪些文件
最后说一个很多人忽略的问题:交上去的工程目录应该是干净、可复现的,而不是一团乱麻。我们最后交付的成果包含四部分:可直接导入IDE的Maven工程、初始化数据库脚本quwo_agriculture.sql、部署说明文档README.md、以及论文一份。工程里每个包的命名清晰,controller、service、mapper、entity、interceptor、config各归其位,界面层用JSP加JSTL,没有把HTML散落一地。
数据库脚本一定要包含测试数据,用户、分类、商品、订单各准备几条有代表性的记录,保证答辩演示时一打开页面就有内容看得见。README里写清楚环境要求、部署步骤、默认管理员账号密码,这既是方便自己复盘,也是让评委或指导老师能快速跑起来的专业体现。我见过太多学生代码写在桌面上,论文交上去之后自己都忘了怎么运行,这种状态很容易在答辩现场被拷问。
这套系统做完,学生最大的收获不是"会写SSM"这件事本身,而是完整走了一遍从一个业务想法到可运行系统再到论文成稿的全流程。回头再看,选对题目、控制功能边界、把核心链路的原理理解到位,比堆砌任何花哨技术都重要。如果你也正在为毕设选题发愁,农产品销售这个方向可以认真考虑,它不惊艳,但扎实、完整、有真实业务支撑,而且源码和论文都容易做得规范。等你做完跑通的那一刻,大概率也会觉得,这事比想象中有意思。