☰
微信小程序+SSM校园二手交易平台毕业设计:从架构到避坑全指南
2026/9/26 4:24:58 网站建设 项目流程

简介:这份毕业设计资源面向高校计算机相关专业学生与Java全栈初学者,提供一套完整的大学生闲置物品交易小程序实现方案,可用于课程设计、毕业设计或小程序开发练手。项目采用微信小程序前端搭配SSM框架与MySQL数据库,划分管理员、卖家、用户三类角色,覆盖商品发布、购买、商品类型与广场管理等核心业务模块,并附有毕业论文与视频演示,便于理解系统分析与数据库设计思路。资源包共1180个文件,约51.16MB,包含127个Java后端源码、137个Vue组件、160个JS脚本、72个WXML与74个WXSS小程序页面文件,以及PNG、SVG等界面素材和SQL脚本、配置文件,结构完整便于二次开发。目前已有162人学习,适合需要完整赛题方案、可运行源码与配套文档的读者参考借鉴。

1. 大学生闲置物品交易小程序:从选题到跑通,一套能写进毕业设计的技术方案

每年毕业季,计算机相关专业的选题里总有一类特别稳的方向——校园二手交易平台。需求真实、场景清晰、技术栈成熟,答辩时老师也容易理解。但真正动手做的时候,很多人卡在第一步:微信小程序前端怎么和 SSM 后端对接?MySQL 表怎么设计才够用又不臃肿?论文里的系统架构图到底该画哪些模块?

这个标题对应的是一套完整的毕业设计交付物:微信小程序端 + SSM 后端 + MySQL 数据库 + 论文 + 演示视频。我做过几套类似的东西,也帮人排查过不少翻车现场。常见的问题不是技术本身有多难,而是前后端联调时参数对不上、数据库字段类型选错导致中文乱码、小程序请求封装没做好导致每个页面都在重复写 wx.request。

这套方案适合谁?如果你是计算机、软件工程、信息管理相关专业的应届生,需要在一个月内完成一个能演示、能写论文、能答辩的项目,那这条路线是经过验证的。下面按实际开发顺序,把每个环节拆开讲。

2. 技术选型与整体架构:为什么是微信小程序 + SSM 而不是别的

2.1 小程序端选型:原生开发还是 uniapp

微信小程序开发目前主流两条路:原生 WXML/WXSS/JS 和 uniapp 跨端框架。毕业设计场景下,我一般建议用原生。原因很直接:SSM 后端返回的是 JSON,原生小程序的 wx.request 直接就能用,不需要额外适配层。uniapp 虽然能一套代码多端发布,但它的条件编译和生命周期在小程序端有坑,调试时容易把简单问题复杂化。

原生小程序的目录结构清晰:pages 放页面,utils 放工具函数,app.js 管全局状态。对于闲置物品交易这种功能模块(首页列表、发布商品、商品详情、个人中心、订单管理),原生足够覆盖。而且答辩时老师问“你这个请求怎么发的”,你能直接指出 wx.request 的封装文件,比解释 uniapp 的编译产物要轻松。

2.2 后端选型:SSM 的适用边界

SSM 是 Spring + SpringMVC + MyBatis 的缩写。这套组合在毕业设计里出现频率极高,原因是:Spring 的 IOC 和 AOP 概念好写论文,SpringMVC 的注解开发上手快,MyBatis 的 XML 映射适合展示 SQL 能力。但要注意,SSM 不是 Spring Boot。SSM 需要手动配置 web.xml、applicationContext.xml、spring-mvc.xml,Tomcat 要单独部署。很多教程直接给 Spring Boot 的代码,你复制过来发现没有启动类,这就是没分清。

如果你的学校要求必须体现“框架整合能力”,SSM 是合适的。如果只是想做功能,Spring Boot 更省事。但既然标题写的是 SSM,下面所有配置都按 SSM 来。

2.3 数据库设计:几张表能撑起一个交易流程

MySQL 表设计不需要太复杂,但几个核心实体必须覆盖:用户、商品、分类、订单、收藏。下面是我常用的最小可用表结构,字段类型和索引都经过实际跑通。

-- 用户表 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `openid` varchar(64) DEFAULT NULL COMMENT '微信openid', `nickname` varchar(64) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `phone` varchar(20) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 商品表 CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `category_id` int(11) DEFAULT NULL, `title` varchar(128) NOT NULL, `description` text, `price` decimal(10,2) DEFAULT '0.00', `images` varchar(1024) DEFAULT NULL COMMENT '逗号分隔图片路径', `status` tinyint(4) DEFAULT '1' COMMENT '1在售 2已售 3下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单表 CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL, `goods_id` int(11) NOT NULL, `buyer_id` int(11) NOT NULL, `seller_id` int(11) NOT NULL, `amount` decimal(10,2) DEFAULT '0.00', `status` tinyint(4) DEFAULT '1' COMMENT '1待确认 2已完成 3已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字符集统一用 utf8mb4,不要用 utf8。utf8 在 MySQL 里是阉割版,存 emoji 会报错,而微信昵称里带 emoji 太常见了。价格用 decimal 不用 float,避免 0.1+0.2 这种精度问题。images 字段用逗号分隔存多张图路径,毕业设计够用,不用单独建图片表。

注意:openid 字段必须加唯一索引。同一个微信用户重复插入会导致数据混乱,这个坑我在早期项目里踩过。

3. 后端接口开发:从 MyBatis 映射到 Controller 返回

3.1 MyBatis 映射文件与 DAO 层

SSM 里 MyBatis 负责 SQL 映射。先写实体类,再写 Mapper 接口,最后写 XML。以商品查询为例:

// GoodsMapper.java public interface GoodsMapper { List<Goods> selectByCategory(@Param("categoryId") Integer categoryId, @Param("keyword") String keyword); int insertGoods(Goods goods); Goods selectById(@Param("id") Integer id); }
<!-- GoodsMapper.xml --> <mapper namespace="com.campus.dao.GoodsMapper"> <select id="selectByCategory" resultType="com.campus.entity.Goods"> SELECT * FROM goods WHERE status = 1 <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND title LIKE CONCAT('%', #{keyword}, '%') </if> ORDER BY create_time DESC </select> <insert id="insertGoods" useGeneratedKeys="true" keyProperty="id"> INSERT INTO goods (user_id, category_id, title, description, price, images) VALUES (#{userId}, #{categoryId}, #{title}, #{description}, #{price}, #{images}) </insert> </mapper>

useGeneratedKeys="true"和keyProperty="id"必须加,否则插入后拿不到自增主键,前端发布商品后跳转详情页会没有 ID。<if>标签做动态 SQL,categoryId 为空时查全部,不为空时按分类过滤。LIKE 用 CONCAT 拼接,不要用'%${keyword}%',那是 SQL 注入的入口。

3.2 Controller 层与统一返回格式

Controller 负责接收请求、调用 Service、返回 JSON。建议封装一个统一返回类,避免每个接口返回结构不一致。

// Result.java public class Result<T> { private int code; private String msg; private T data; // getter/setter 省略 public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "ok"; r.data = data; return r; } public static <T> Result<T> error(String msg) { Result<T> r = new Result<>(); r.code = 500; r.msg = msg; return r; } }
// GoodsController.java @RestController @RequestMapping("/api/goods") public class GoodsController { @Autowired private GoodsService goodsService; @GetMapping("/list") public Result<List<Goods>> list(@RequestParam(required = false) Integer categoryId, @RequestParam(required = false) String keyword) { List<Goods> list = goodsService.findByCondition(categoryId, keyword); return Result.success(list); } @PostMapping("/publish") public Result<Integer> publish(@RequestBody Goods goods) { if (goods.getTitle() == null || goods.getTitle().trim().isEmpty()) { return Result.error("标题不能为空"); } int id = goodsService.publish(goods); return Result.success(id); } }

@RestController等于@Controller + @ResponseBody,返回对象自动转 JSON。@RequestBody接收前端 JSON 数据,对应小程序的Content-Type: application/json。如果前端用 form 表单提交,这里要改成@RequestParam逐个接收。

3.3 跨域配置:小程序请求为什么报 403

小程序开发时,本地调试的域名是localhost,但微信开发者工具默认校验合法域名。解决办法是在开发者工具里勾选“不校验合法域名”。但后端也要配 CORS,否则浏览器端测试会 403。

// 在 spring-mvc.xml 里加 <mvc:cors> <mvc:mapping path="/api/**" allowed-origins="*" allowed-methods="GET,POST,PUT,DELETE" allowed-headers="*" allow-credentials="true"/> </mvc:cors>

allowed-origins="*"和allow-credentials="true"同时用会冲突,浏览器会拒绝。如果不需要携带 cookie,把allow-credentials设为 false。小程序请求不涉及浏览器同源策略,但开发者工具里的网络请求走的是 Chromium 内核,所以 CORS 还是要配。

4. 小程序端开发:请求封装、页面跳转与图片上传

4.1 请求封装:一次写好,处处调用

原生小程序的wx.request是回调风格,每个页面都写一遍太啰嗦。封装成 Promise 后,配合 async/await 会清爽很多。

// utils/request.js const BASE_URL = 'http://localhost:8080/api'; function request(options) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { get: (url, data) => request({ url, method: 'GET', data }), post: (url, data) => request({ url, method: 'POST', data }) };

BASE_URL在开发阶段指向本地 Tomcat,上线要换成备案域名。token从缓存取,登录后写入。success里判断code === 200才 resolve,否则统一弹提示。这样页面里只需要const res = await request.get('/goods/list', { categoryId: 1 })。

4.2 图片上传:wx.uploadFile 与后端 MultipartFile

发布商品要传图,小程序用wx.uploadFile,后端用MultipartFile接收。

// 选择图片并上传 wx.chooseImage({ count: 3, success(res) { const tempPaths = res.tempFilePaths; tempPaths.forEach(path => { wx.uploadFile({ url: BASE_URL + '/upload', filePath: path, name: 'file', success(upRes) { const data = JSON.parse(upRes.data); if (data.code === 200) { images.push(data.data); } } }); }); } });
// UploadController.java @PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("文件为空"); } String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename(); String filePath = "/upload/" + fileName; File dest = new File(filePath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return Result.success("/static" + filePath); } catch (IOException e) { return Result.error("上传失败"); } }

name: 'file'必须和后端@RequestParam("file")一致,不一致会报 400。上传后的路径要映射成静态资源访问,在 spring-mvc.xml 里配<mvc:resources mapping="/static/**" location="/upload/"/>。

4.3 页面跳转与参数传递

小程序页面跳转用wx.navigateTo,参数拼在 URL 上。详情页接收参数在onLoad里。

// 列表页跳详情 wx.navigateTo({ url: '/pages/detail/detail?id=' + item.id }); // 详情页 onLoad onLoad(options) { const id = options.id; this.loadDetail(id); }

options.id拿到的是字符串,如果后端接口需要 int,记得parseInt。这个细节不注意,后端会报类型转换异常。

5. 避坑与排查:那些答辩前夜还在改的 bug

5.1 中文乱码:数据库、连接串、响应编码三处都要查

现象:商品标题存进去是问号,或者返回给小程序变成\u4e2d\u6587。

原因:MySQL 表字符集不是 utf8mb4,或者 JDBC 连接串没指定编码,或者 SpringMVC 的 StringHttpMessageConverter 默认用 ISO-8859-1。

解决:三处同时改。数据库ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4;;JDBC URL 加?useUnicode=true&characterEncoding=utf8;spring-mvc.xml 里配<mvc:annotation-driven><mvc:message-converters><bean class="org.springframework.http.converter.StringHttpMessageConverter"><property name="defaultCharset" value="UTF-8"/></bean></mvc:message-converters></mvc:annotation-driven>。

5.2 小程序请求返回 404:路径拼错了还是 Tomcat 没起

现象:开发者工具 Network 面板显示 404。

原因:BASE_URL 写的是http://localhost:8080/api,但后端 Controller 的@RequestMapping是/goods,实际路径应该是/api/goods/list。或者 Tomcat 的 context path 不是/,而是/ssm。

解决:先确认 Tomcat 启动日志里的 context path,再核对 Controller 的完整路径。在浏览器直接访问http://localhost:8080/api/goods/list,能返回 JSON 说明后端没问题,问题在小程序端。

5.3 MyBatis 查询返回 null:字段名和属性名没对上

现象:selectById返回对象,但某个字段是 null。

原因:数据库字段是create_time,实体类属性是createTime,但 MyBatis 默认不开启驼峰映射。

解决:在 mybatis-config.xml 里加<setting name="mapUnderscoreToCamelCase" value="true"/>。或者手动在 XML 里写<resultMap>做映射。推荐开驼峰,省事。

5.4 图片上传后访问 404:静态资源映射没配

现象:上传成功返回路径,但小程序<image src>加载不出来。

原因:上传的文件存在服务器磁盘,但 SpringMVC 没有把该目录暴露为静态资源。

解决:spring-mvc.xml 加<mvc:resources mapping="/static/**" location="file:/upload/"/>。注意 location 用file:前缀表示文件系统路径,不是 classpath。

5.5 订单重复提交:前端按钮没防抖,后端没做幂等

现象:用户快速点两次“立即购买”,生成两笔订单。

原因:前端按钮没有 loading 状态,后端没有唯一约束。

解决:前端点击后立即wx.showLoading并禁用按钮;后端 order_no 用userId + timestamp生成,加唯一索引,插入冲突时捕获异常返回“请勿重复提交”。

6. 论文写作与答辩演示的收尾技巧

论文部分,系统架构图不要画得太花。三层架构(表现层、业务层、数据层)加一个部署图就够了。数据库表用 ER 图展示,字段说明用表格列清楚。核心代码不要贴全文,挑请求封装、MyBatis 动态 SQL、图片上传这三段,每段配一段文字说明“为什么这样写”。

演示视频建议按这个顺序录:打开小程序首页 → 切换分类 → 搜索商品 → 查看详情 → 发布商品(含选图上传)→ 个人中心查看我的发布 → 下单 → 订单列表。全程控制在 3 分钟内,不要录调试过程。

答辩时老师最常问的三个问题:你的 token 怎么生成的?图片存在哪里?订单状态怎么流转?提前准备好答案。token 可以用 UUID 存 Redis,毕业设计用 session 也行;图片存本地磁盘,路径写进数据库;订单状态用 tinyint,1 待确认、2 已完成、3 已取消,前端根据状态显示不同按钮。

我自己的习惯是:答辩前一晚把整个流程在本地跑三遍,每遍都从清空数据库开始。这样能发现自增 ID 不连续、时间格式不对、图片路径带中文等问题。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询