news 2026/10/10 9:27:53

大学生闲置交易小程序毕设全解析:SSM+MySQL+微信小程序架构与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大学生闲置交易小程序毕设全解析:SSM+MySQL+微信小程序架构与避坑指南

简介:面向高校毕业设计及课程设计场景,这是一份基于微信小程序+SSM+MySQL的大学生闲置物品交易平台完整项目,涵盖小程序前端、后台管理端、数据库脚本和毕业论文文档,可直接作为设计参考或二次开发基线。资源共1180个文件,js/vue/java负责前后端业务逻辑,wxss/wxml与png/svg构成小程序界面资源,json/xml/sql提供配置和数据库初始化脚本,整体约51.16MB,分类清晰,便于按模块导入微信开发者工具和IDEA运行调试。系统内设管理员、卖家、用户三种角色,实现商品信息发布、在线购买、广场管理、学生管理、系统设置等核心功能;后端基于SSM框架与MySQL数据库设计,前端由微信开发者工具搭建,兼顾稳定性和易用性,数据关系与接口划分明确。已有162人浏览学习,适合需要掌握小程序+SSM整合开发、完成交易类毕业设计或撰写系统开发论文的学生。压缩包内还提供毕业论文文档、视频演示及数据库SQL文件,能从环境部署、功能实现、页面交互到项目演示,全流程对照练习并快速产出可答辩成果。

1. 毕业设计做「大学生闲置物品交易小程序」:这可能是最稳的选题,也是最容易做砸的选题

答辩季一到,总能看到有同学抱着「微信小程序 + SSM + MySQL」这个组合走上讲台。为什么这个选题这么常见?因为它切中了校园里真实存在的场景:换季的旧衣、毕业带不走的台灯、考研结束的参考书,这些东西在跳蚤群和朋友圈里流动效率极低,而小程序恰好是大学生最顺手的使用入口。技术上,SSM 是后端教学里覆盖最全的框架组合,微信小程序前端又能独立成章,数据库设计、接口开发、前端交互、论文写作、演示视频全都能自圆其说。一套做下来,工作量饱满但不至于失控,这确实是毕业设计的「安全牌」。

但这门课我也见过太多翻车现场:有人把 SSM 写成了 Spring Boot 的变体,有人用微信云开发绕开了所有数据库设计,还有人把交易功能做成了一句「联系卖家」的假按钮。安全牌打不好,照样挂。这篇文章我会从数据库表结构、小程序端请求链路、SSM 后端接口分层、再到并发和图片存储这些容易翻车的细节,把整个项目拆开讲,按这套思路你不仅能交出一套能跑通的毕设,还能在答辩时讲清每一行代码为什么这么写。

2. 先从架构和表结构说起:这套系统到底分几层,数据怎么落

2.1 为什么现在还有人用 SSM,而不是直接上 Spring Boot

打开任何招聘网站的 Java 岗位需求,Spring Boot 几乎成了标配,但毕业设计选 SSM 完全不丢人。SSM 是 Spring、SpringMVC、MyBatis 三个框架的组合,Spring 管对象和依赖注入,SpringMVC 管请求路由和参数绑定,MyBatis 管 SQL 和结果映射。这三层在 Spring Boot 里其实都还在,只是被自动配置和注解简化了。毕设选题要求的是把原理讲清楚,SSM 反而更容易展示「请求进来之后发生了什么」——DispatcherServlet 怎么分发、HandlerMapping 怎么找 Controller、MyBatis 怎么把 ResultSet 变成对象,这些都是答辩时容易出彩的展开点。

从开发效率上说,SSM 确实比 Spring Boot 繁琐,需要自己维护配置文件、自己配事务管理器。但这也是它的优势:配置文件写明白了,说明你真的理解这套东西在干什么。我一般会建议这样的分工:SpringMVC 处理小程序端发来的 HTTP 请求,Service 层处理业务逻辑和事务,MyBatis 负责和 MySQL 交互。数据库放在本机 MySQL 8.0,开发期用 Navicat 观察数据变化,真机预览时再把接口地址从 localhost 改成局域网 IP。

2.2 小程序端和 SSM 后端怎么连起来:HTTP + JSON + Token

小程序端和后端是完全分离的两个项目,小程序跑在微信的 WebView 环境里,后端跑在你的 Tomcat 上。两边的通信只有一条路:小程序用wx.request发 HTTP 请求,后端用 SpringMVC 的@RestController接收并返回 JSON。小程序不能直接连 MySQL,也没有 JDBC 驱动,所以所有数据库操作都必须发生在后端。这个模型要在一开始就建立起来,否则写着写着就容易把业务逻辑堆在小程序前端,后端的 Service 层变成空壳。

登录状态用 Token 维护。小程序端调用wx.login拿到一个临时 code,发给后端某个登录接口,后端拿 code 换 openid,再把这个 openid 查出来或注册成用户,生成一个随机的 UUID 字符串作为 token 存到 Redis 或数据库表里,返回给小程序。之后小程序每次请求在 header 里带token: xxx,后端用一个拦截器统一校验。注意不要在数据库里存明文密码,大学生闲置交易场景里没有密码体系,直接靠微信 openid 做唯一标识更合理。

2.3 数据库设计:5 张核心表,字段怎么定才能撑起整个业务

数据库是整个项目的地基,表结构设计得烂,后期写 Mapper 时会处处别扭。闲置交易的核心主链路是:用户打开小程序 → 看到商品列表 → 点进详情 → 对商品感兴趣(收藏或留言) → 买下它(生成订单)。围绕这条链路,我一般会设计 5 张表:用户表、商品表、订单表、收藏表、留言表。

用户表的关键字段是openid、nickname、avatar_url、create_time,其中openid必须加唯一索引,这是微信生态里的身份证。商品表是业务核心,字段包括goods_name、description、price(用 decimal(10,2),别用 float)、original_price、images(多张图片用逗号分隔或 JSON 字符串存)、status(1 上架、2 已预约、3 已售出、0 下架)、seller_id、buyer_id、views、category、create_time。订单表则要记录goods_id、seller_id、buyer_id、order_status、create_time、finish_time。

下面是一组可以直接执行的建表 SQL,我在模拟项目X中用这套结构跑通过:

CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信openid,每个用户唯一', `nickname` VARCHAR(64) DEFAULT '', `avatar_url` VARCHAR(500) DEFAULT '', `phone` VARCHAR(20) DEFAULT '', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `goods` ( `id` INT NOT NULL AUTO_INCREMENT, `goods_name` VARCHAR(100) NOT NULL, `description` TEXT, `price` DECIMAL(10,2) NOT NULL, `original_price` DECIMAL(10,2) DEFAULT 0.00, `images` VARCHAR(2000) DEFAULT '' COMMENT '多个图片URL用逗号分隔', `category` VARCHAR(30) DEFAULT '其他', `status` TINYINT DEFAULT 1 COMMENT '1上架 2已预约 3已售出 0下架', `seller_id` INT NOT NULL, `buyer_id` INT DEFAULT NULL, `views` INT DEFAULT 0, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_seller` (`seller_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闲置商品表'; CREATE TABLE `orders` ( `id` INT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号,时间戳+随机数生成', `goods_id` INT NOT NULL, `seller_id` INT NOT NULL, `buyer_id` INT NOT NULL, `order_status` TINYINT DEFAULT 0 COMMENT '0已下单待交易 1已成交 2已取消', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `finish_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_buyer` (`buyer_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; CREATE TABLE `favorite` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `goods_id` INT NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收藏表'; CREATE TABLE `message` ( `id` INT NOT NULL AUTO_INCREMENT, `goods_id` INT NOT NULL, `from_user_id` INT NOT NULL, `to_user_id` INT NOT NULL, `content` VARCHAR(500) DEFAULT '', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='留言表';

这里有三个细节值得注意。第一,utf8mb4是必须的,普通的utf8存 emoji 会乱码,小程序端用户昵称和商品描述里很容易出现 emoji。第二,goods表里加status字段并建索引,是因为业务里最频繁的查询就是「按状态拉商品列表」,MySQL 在数据量变大后会走idx_status索引,避免全表扫描。第三,订单表不直接做物理删除,而是用order_status区分状态,这样从商品到订单的流转过程在论文里能用一张状态图讲清楚。

数据库表设计完后,建议先写一个init.sql脚本文件放在项目根目录,同时把 ER 图导出放到论文里。答辩老师最喜欢问的问题就是「表之间的关联关系是怎么设计的」,提前画好字段说明表,这一问就稳了。

3. 小程序前端:这 4 个页面怎么写,才能把体验和逻辑兼顾

3.1 全局配置与底部导航:4 个 Tab 对应哪些页面

小程序端一般做 4 个底部 Tab:首页(商品列表)、发布、消息、个人中心。每个 Tab 对应一个主包页面,发布页用于表单提交,消息页展示留言和订单状态变更,个人中心展示我发布的和我买到的。在app.json里配置tabBar,图标可以用纯色占位,颜色改一下即可。页面结构不建议用navigationStyle: custom自定义导航,毕设阶段用默认导航栏更省事,也多一套页面主题色可以写进论文。

全局请求封装是前端的核心基础设施。写一个utils/request.js,把wx.request封装成 Promise,统一带上 token,统一处理 401 和 500:

// utils/request.js const BASE_URL = 'http://192.168.1.100:8080/api'; // 开发时改成你的局域网IP function request(path, method, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 401) { wx.redirectTo({ url: '/pages/login/login' }); reject(new Error('未登录')); return; } if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(new Error(res.data.msg)); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { request, BASE_URL };

封装之后,页面里调接口只需要request('/goods/list', 'GET', { page: 1 })这样一行。BASE_URL 在开发阶段可以直接用局域网 IP,真机预览时手机和电脑连同一个 Wi-Fi 才能访问。后端api前缀是为了和 SpringMVC 拦截器的路径规则对齐,接口路径统一以/api开头,方便统一做 token 鉴权。

3.2 首页商品流:列表怎么渲染、下拉刷新怎么接

首页不是静态页面,它模拟的是「进入校园跳蚤市场」的感觉。用onLoad调商品列表接口,onPullDownRefresh重新拉第一页,onReachBottom加载下一页。小程序端的列表渲染用wx:for,需要给每个 item 设置key为商品的id。WXML 里要注意price保留两位小数,可以在 JS 里提前格式化成字符串再渲染。

这里容易踩的坑是setData的数据量。如果一次性把 20 条商品数据全部塞进一个字段,然后 WXML 里用wx:for渲染,性能没问题;但如果你把商品嵌套在对象里,每次下拉刷新都setData({ goods: [] })然后再赋新值,会闪烁。建议分页数据结构固定为{ list: [], page: 1, hasMore: true },追加时用this.setData({ 'list': this.data.list.concat(res.list) }),hasMore用来控制底部是否显示「没有更多了」。

3.3 发布闲置页:图片上传和表单校验的配合

发布页是小程序端最复杂的表单。除了商品名、描述、价格、分类,图片选择要调用wx.chooseMedia(新版 API,旧版chooseImage已废弃)。选完图片后逐张上传到后端,上传接口返回一个 URL 后把 URL 存到表单的 images 字段。注意不要把所有图片同时一次性上传,小程序并发连接数有限,图片过大时容易失败,用 for 循环逐张传更稳妥。

其中价格字段有个细节:小程序端<input type="digit">拿到的是字符串,提交前必须parseFloat并且判断是否为NaN,否则后端Decimal类型解析时会报 500。描述字段建议做长度限制,maxlength="500",中文环境下 500 字足够。发布成功后wx.navigateBack回首页,并且用wx.showToast提示「发布成功」。整个流程要让同学在演示视频里操作得顺畅,两个页面之间的切换动效不要留空白页。

3.4 商品详情与下单动作:状态可见性是怎么控制的

商品详情页是买家决策的核心,上面的操作按钮需要根据商品状态和登录身份动态变化。如果status === 1并且当前用户不是卖家,显示「立即下单」;如果status === 2,说明已被其他人预约,显示「已被预约」且按钮置灰;如果status === 3,显示「已卖出」,此时直接跳过下单接口。这些判断在 JS 里通过计算属性或者函数返回,不要在 WXML 里写复杂的wx:if嵌套,不然改一个状态要改三处模板。

下单动作在点击时调到后端/api/order/create,传入goodsId,后端在校验状态后把商品变成「已预约」,同时生成订单。买家下单之后,卖家在个人中心的「我卖出的」里能看到该订单的状态,并能确认成交。这个「双方确认」的交互闭环,是论文里可以写的亮点,也是和普通商品展示项目的核心区别。

4. SSM 后端三层落地:Controller、Service、Mapper 之间怎么分工

4.1 后端目录结构与配置文件:别把代码全堆在一个类里

SSM 后端如果只有一个 Controller 类加一堆 SQL 字符串,答辩时会被一眼看穿。标准做法是分五层:Controller(接请求)、Service(写业务逻辑)、Mapper(写 SQL 接口)、entity(对应数据表的实体类)、common(全局返回结果、异常处理、工具类)。目录结构固定下来,后面写代码就是往里面填空。

SpringMVC 的配置集中在spring-mvc.xml里,需要声明注解驱动、静态资源放行、Json 转换器。MyBatis 的配置在mybatis-config.xml,开启驼峰映射:

<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>

mapUnderscoreToCamelCase这个属性很关键。数据库字段是goods_name,Java 实体类是goodsName,不开启驼峰映射,查询结果里goods_name会映射不到属性上,导致商品名为 null。logImpl设为 STDOUT_LOGGING 后,控制台能看到 MyBatis 实际执行的 SQL,排查问题效率翻倍。

4.2 Controller 怎么写才规范:统一返回体、接收参数、返回状态码

接口层最容易犯的错是每个 Controller 返回不同的 JSON 结构,小程序端解析起来一团乱。我一般全局用一个Result类包装:

public class Result<T> { private Integer code; // 0 成功,1 业务失败,401 未登录,500 系统异常 private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 0; r.msg = "success"; r.data = data; return r; } public static <T> Result<T> error(Integer code, String msg) { Result<T> r = new Result<>(); r.code = code; r.msg = msg; return r; } }

以「商品分页列表」为例,Controller 接收page和pageSize,调用 Service 层查数据,返回Result.success(pageResult)。Service 层里做参数校验和业务逻辑,Mapper 只负责 SQL。这里要注意分页查询不要自己拼LIMIT字符串去执行,用 PageHelper 插件更规范,一个PageHelper.startPage(page, pageSize)就能自动给下一条查询加LIMIT,配合PageInfo拿到总数和当前页数据。

下面是一个完整的商品列表 Controller 写法,包含当前登录用户和商品状态的联动:

@RestController @RequestMapping("/api/goods") public class GoodsController { @Autowired private GoodsService goodsService; @GetMapping("/list") public Result<PageResult<GoodsVO>> list( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Integer category, @RequestParam(required = false) String keyword, @RequestParam(required = false) Integer sortType) { // sortType: 1按最新 2按价格升序 3按浏览量降序 PageResult<GoodsVO> result = goodsService.pageQuery(page, pageSize, category, keyword, sortType); return Result.success(result); } @PostMapping("/create") public Result<Long> create(@RequestBody Goods goods, @RequestAttribute("userId") Long userId) { goods.setSellerId(userId); Long goodsId = goodsService.createGoods(goods); return Result.success(goodsId); } }

@RequestAttribute("userId")是从拦截器里塞进去的,这样 Controller 里不需要再拿 token 重新解析用户,避免每个接口都写重复的鉴权代码。@RequestBody接收 JSON 对象,SpringMVC 会自动把前端传来的字段绑定到Goods实体上。这里建议用DTO而不是直接暴露实体类,否则前端多传一个sellerId就能伪造卖家身份,这是个安全常识。

4.3 MyBatis 动态 SQL:列表筛选和订单更新怎么同时满足性能和正确性

商品列表的筛选条件是可选的(分类、关键词、排序方式),SQL 必须用 MyBatis 的动态 SQL 拼出来,不能为每个组合写一个固定语句。下面这个selectByCondition是核心:

<select id="selectByCondition" resultType="com.xxx.entity.Goods"> SELECT g.*, u.nickname AS sellerName, u.avatar_url AS sellerAvatar FROM goods g LEFT JOIN user u ON g.seller_id = u.id <where> g.status = 1 <if test="category != null and category != ''"> AND g.category = #{category} </if> <if test="keyword != null and keyword != ''"> AND (g.goods_name LIKE CONCAT('%', #{keyword}, '%') OR g.description LIKE CONCAT('%', #{keyword}, '%')) </if> </where> <choose> <when test="sortType == 2">ORDER BY g.price ASC</when> <when test="sortType == 3">ORDER BY g.views DESC</when> <otherwise>ORDER BY g.create_time DESC</otherwise> </choose> </select>

LEFT JOIN user一次性把卖家昵称和头像带出来,前端详情页就不用再根据sellerId单独发一次请求。<where>标签会自动处理第一个条件前的多余AND,<choose>则是排序分支。注意keyword为空时CONCAT('%', NULL, '%')会返回NULL,所以LIKE条件必须放在<if>里面,否则搜索为空时会把所有商品过滤掉。

下单操作必须用「条件更新」来防并发,这是全系统最容易出 bug 的点。两个人同时对一件商品下单,两个请求都先查询出status = 1,然后都执行更新,结果这件商品被卖了两遍。正确做法是在UPDATE goods SET status = 2, buyer_id = #{buyerId} WHERE id = #{goodsId} AND status = 1,然后判断影响行数,如果返回 0 说明商品状态已变,下单失败:

<update id="lockGoods"> UPDATE goods SET status = 2, buyer_id = #{buyerId} WHERE id = #{goodsId} AND status = 1 </update>

Service 层拿到int rows = goodsMapper.lockGoods(...),如果rows == 0就抛一个业务异常,事务回滚,订单也不会生成。这条 SQL 是论文里可以重点讲的技术亮点,比在 Java 代码里加synchronized靠谱得多,因为synchronized只在一个 Tomcat 实例内有效,而数据库的行锁天然支持多实例。

4.4 拦截器和全局异常处理:没写这两块,小程序端会到处报错

Token 校验用 SpringMVC 的HandlerInterceptor拦截/api/**请求,放行登录接口。拦截器里拿到request.getHeader("token"),查 Redis 或数据库,把 userId 塞进 request 属性。这里的关键点是:如果 token 不存在或已过期,直接返回 JSON 而不是重定向,response.setStatus(401),小程序端request.js会统一跳转登录页。

全局异常处理用@RestControllerAdvice,把 Service 层抛出的业务异常统一转成Result.error(...)。不写这个,MyBatis 抛的异常会变成一大段堆栈信息返回给小程序,前端拿到statusCode 500后res.data.msg取不到值,页面只会白屏。有了全局异常处理器,前端就能稳定拿到「商品已被预约」这类中文提示:Service 层主动抛一个自定义的BizException("商品已被预约"),由全局异常处理器拦截并打包成code=1的 JSON,小程序端弹 toast 展示msg字段。

5. 避坑指南:这 6 个问题,几乎每个做这个题目的人都会撞上

5.1 图片上传后真机显示,PC 端正常但手机打不开

现象:在开发者工具里图片显示正常,发布到真机预览后图片全部裂开,控制台报 403。

原因:开发者在工具里用的是本地路径,http://localhost:8080/upload/xxx.jpg,真机上 localhost 指向的是手机自己,自然访问不到你电脑上的 Tomcat。另外,小程序对网络请求有域名白名单限制,IP 地址和端口不是 443/80 时,真机上默认不允许访问。

解决:开发阶段,在微信开发者工具的「详情 → 本地设置」里勾选「不校验合法域名」,真机预览时把 BASE_URL 改成电脑的局域网 IP,例如http://192.168.1.100:8080。生产环境则必须备案域名、走 HTTPS、在小程序管理后台配置 request 合法域名。图片存放不要放在 Tomcat 的临时目录里,要单独配置上传路径并映射成静态资源,否则 Tomcat 重启后图片全丢。

5.2 LocalDateTime 返回前端变成一串数字

现象:后端返回的商品创建时间是{"year":2024,"month":5,"day":20}或一串毫秒数,前端怎么格式化都不对。

原因:SpringMVC 默认的 JSON 序列化器对 Java 8 时间类型支持不友好,LocalDateTime被序列化成对象或时间戳,和数据库里的DATETIME对不上。

解决:在spring-mvc.xml里配置 Jackson 的时间格式化:

<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg value="yyyy-MM-dd HH:mm:ss"/> </bean> </property> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>

或者更简单,在LocalDateTime字段上直接加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),实体类上逐个标注。我个人的习惯是后者,不想动全局配置,只标注需要的字段,但这要求后端每个时间字段都不能漏。

5.3 LIKE 查询在 MySQL 5.7 下中文关键词查不到

现象:搜索「自行车」返回空,但数据库里明明有商品描述包含「自行车」。

原因:MySQL 5.7 及以下版本默认字符集可能是latin1,或者连接串没有指定characterEncoding=utf8;也有可能是description字段存储的字符集与客户端连接字符集不一致。

解决:连接 JDBC 时在 URL 里显式指定编码:

jdbc:mysql://localhost:3306/campus_trade?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

同时建表时用utf8mb4,并且like查询的关键词后端先做 URL 解码,小程序端encodeURIComponent过的中文参数默认到后端可能是乱码。一个排查技巧:在 MyBatis 配置里把logImpl打开,看控制台打印的 SQL 和参数值,确认#{keyword}传入的到底是不是正常中文。

5.4status判断顺序写反,导致商品被重复下单

现象:商品明明已经卖出了,详情页还能看到「立即下单」按钮,点下去后端也没有拦截,生成了一条无效订单。

原因:Service 层下单逻辑先查商品status,再更新状态。两个用户同时发起下单,两个查询都看到status = 1,然后都执行更新,最后商品被卖给两个人。这是典型的不加锁并发问题。

解决:用前面说的条件更新UPDATE goods SET status = 2 WHERE id = ? AND status = 1,通过返回的影响行数判断是不是第一个抢到的人。同时详情页按钮判断和后端校验双写,后端永远作为最后一道防线,前端按钮置灰只是体验优化,不能当作业务约束。

5.5 MyBatis 多表查询返回的 Map 类型取不到值

现象:一个新功能需要临时从订单表 JOIN 商品表查点数据,Mapper 方法返回值写成List<Map<String, Object>>,控制台 SQL 没问题,但map.get("goods_name")拿到了 null。

原因:没开驼峰映射,或者 Map 的 key 用的是数据库原始字段名goods_name,而不是实体属性名goodsName。用Map接收结果时,MyBatis 不会做驼峰转换,Map 的 key 是 SQL 查询列的 label。

解决:用实体类接收结果,并且在 SQL 里给列名取别名:SELECT g.goods_name AS goodsName。如果非要用 Map,就按数据库列名取:map.get("goods_name"),但这写起来很脆弱,SQL 一改别名就乱了。规范做法是一个查询对应一个 VO 类,字段类型明确,前端拿到的 JSON 结构也可控。

5.6 部署时 Tomcat 能启动但接口全部 404

现象:本地 IDEA 里跑得好好的,把 war 包丢进云服务器的 Tomcat 后启动成功,但任何/api请求都返回 404。

原因:最常见的是路径前缀不对。IDEA 里默认没用 context path,访问路径是http://localhost:8080/api/...,但 Tomcat 默认把 war 包解压后的目录作为 context path,比如你的 war 包叫campus.war,访问路径就变成了http://ip:8080/campus/api/...。

解决:把 war 包改名为ROOT.war部署,或者访问路径带上项目名。另外application.properties里的server.servlet.context-path如果在本地配了/campus,部署到服务器也要保持一致。这个小问题能卡一下午,建议部署之前把本地和线上两块配置拉一个清单逐项对比。

6. 从能用到好用:把这套毕设做成答辩加分项的 3 个进阶技巧

很多同学做完这套系统就能交差,但如果想让答辩老师眼前一亮,可以在三个方向做低成本优化。

第一个是商品推荐排序。不要再按create_time倒序一股脑输出,可以加一个简单的热度权重:浏览量 * 0.3 + 收藏数 * 0.5 + 发布天数衰减。实现不需要引入 Elasticsearch 或 Redis,只需要在商品表加一个hot_score字段,定时任务每小时更新一次,列表默认按hot_score DESC排序。论文里可以解释这个公式的来历,比如为什么收藏权重高于浏览,因为收藏代表更强的购买意向。

第二个是交易状态机。下单后不要只给一个状态字段,可以定义完整的流转路径:上架中 → 已预约 → 已成交 / 已取消 → 已下架。每个状态迁移都对应一个明确的接口动作:下单走lockGoods,卖家确认成交走confirmOrder,买家取消走cancelOrder。把这些动作封装成 Service 方法,Controller 层只做参数接收,状态机判断全部下沉到 Service,代码路径展示非常清晰。

第三个是记录操作日志。在小程序端上报页面进入次数和按钮点击事件,后端建一张operation_log表存用户行为。答辩时老师会问「你这个系统有没有数据支撑改进方向」,你可以直接调出日志数据,说首页商品点击率高但成交率低,说明商品描述或价格设置有问题,或者登录流程有流失。就这一句话,就证明了你不只是写了功能,而是做了数据分析。

最后分享一个我自己的习惯:任何时候加新接口,先想清楚「这个数据最终展示在哪个页面、前端需要的 JSON 结构是什么样」,然后倒推着把 Mapper 和 Service 层写出来。不要在 Controller 里直接查 Mapper,也不要让前端收到一堆用不到的字段。这套系统做完后,请务必在手机上用 4G 网络完整走一遍流程,Wi-Fi 环境下的成功会在网络切换时暴露很多问题,比如 token 失效、图片加载超时、接口超时重试。把这些坑在答辩前亲手踩一遍,比写一万字论文都有说服力。希望这篇笔记能帮到你,祝你的毕设顺利过关。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 9:26:56

AngularJS双向绑定实战:从零搭建宠物商城全记录

搞过几个电商类的单页应用之后&#xff0c;我一直觉得AngularJS是个“爱恨交织”的选择——它的双向绑定确实爽&#xff0c;可一旦数据流不按套路走&#xff0c;排查起来也真让人头疼。这次用AngularJS做宠物商城&#xff0c;前前后后花了差不多两个月&#xff0c;从商品列表到…

作者头像 李华
网站建设 2026/10/10 9:26:50

wangEditor扩展实战:Excel表格公式保真导入全流程

做国产化办公系统&#xff0c;最膈应人的需求不是权限多复杂&#xff0c;也不是流程多难调&#xff0c;而是那种看似简单、一碰全是坑的小功能。比如业务方某天丢过来一句话&#xff1a;“我在Excel里算好的表&#xff0c;粘到你们网页上&#xff0c;公式怎么全没了&#xff1f…

作者头像 李华
网站建设 2026/10/10 9:26:19

PyTorch图像分类实战:从数据预处理到CNN训练推理的完整指南

简介&#xff1a;这份资源是一套基于PyTorch搭建猫狗公鸡三分类卷积神经网络的完整项目包&#xff0c;适合已了解深度学习基础、希望上手PyTorch实战的初学者。内容覆盖数据预处理、模型结构设计、损失函数与优化器选择、训练验证、模型保存加载以及可视化等关键环节&#xff0…

作者头像 李华
网站建设 2026/10/10 9:26:04

基于图异常检测的自闭症脑功能连接分析方法

简介&#xff1a;本资源是一项面向人工智能与机器学习方向研究者及高年级本科生的ASD&#xff08;自闭症谱系障碍&#xff09;辅助诊断实践项目&#xff0c;聚焦图异常检测等前沿机器学习方法在神经影像分析中的应用&#xff0c;依托公开ABIDE功能磁共振数据集开展建模与验证。…

作者头像 李华
网站建设 2026/10/10 9:23:22

埃尔法商务租车服务商力荐,靠谱公司省心不踩坑

广州旅顺商务服务有限公司&#xff0c;是广州本土全场景租车服务商&#xff0c;专注埃尔法商务租车、商务车包车、带司机租车及豪华车型租赁服务。公司以车况新、收费透明、租期灵活、售后极速为核心服务理念&#xff0c;一句话精准定位&#xff1a;做广州本地靠谱、省心、可长…

作者头像 李华
网站建设 2026/10/10 9:23:07

烤鸭加盟店日常运营揭秘:老板不在店也能稳住的标准化管理机制

烤鸭加盟店日常运营揭秘&#xff1a;老板不在店也能稳住的标准化管理机制 这两年&#xff0c;烤鸭加盟赛道明显升温。街边社区门口、学校门口、商场负一层&#xff0c;随处可见主打外卖外带的小型烤鸭门店。数据显示&#xff0c;一只烤鸭从堂食到外卖的多元消费场景&#xff0c…

作者头像 李华