尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

微信小程序+SSM+MySQL校园二手交易平台毕业设计实战:从建表到联调避坑

发布时间:2026/9/26 4:25:02

资讯中心
01
ARTICLE

微信小程序+SSM+MySQL校园二手交易平台毕业设计实战:从建表到联调避坑

微信小程序+SSM+MySQL校园二手交易平台毕业设计实战:从建表到联调避坑
简介这份资源是面向高校计算机相关专业学生与Java初学者的大学生闲置物品交易小程序毕业设计全套资料基于微信小程序、SSM框架与MySQL数据库开发可用于课程设计、毕业设计选题参考或小程序全栈入门练习。压缩包共1180个文件约51.16MB包含127个Java后端源码、137个Vue前端组件、160个JavaScript脚本、74个wxss与72个wxml小程序页面文件以及2个SQL数据库脚本、4个mp4演示视频和毕业论文等文档覆盖后端接口、管理后台、小程序端与数据库设计。系统区分管理员、卖家、用户三种角色管理员负责学生、卖家、商品类型与商品信息管理学生可购买商品卖家可发布商品功能链路完整。目前已有162人学习下载适合需要完整赛题方案、可运行源码、数据库脚本与演示录屏的读者帮助快速理解SSM与微信小程序的整合开发流程。1. 从一份毕业设计需求说起大学生闲置交易小程序到底要交付什么每年到了三四月计算机相关专业的群里就会密集出现同一类需求想做一个校园二手交易平台技术栈锁定微信小程序加 SSM 加 MySQL最后要能交出一套完整的东西——能跑起来的小程序、能启动的后端、能导入的数据库脚本外加一篇查重能过的论文和一段演示视频。这个标题背后其实是一份典型的本科毕业设计交付清单而不是一个要上线的商业产品。它解决的核心问题是让一个还没进过公司的学生在有限时间里独立走完「需求分析、数据库设计、接口开发、前端联调、文档撰写」这条完整链路。适合看这篇的人有三类正在做同类选题、卡在某个环节的应届生需要快速判断这套技术栈能不能撑起答辩的指导老师视角的读者以及想拿一个真实小项目练手 SSM 和微信小程序的初级开发者。需要先摆正预期这套组合的技术含量不在算法而在工程完整度和细节自洽。答辩老师真正会追问的往往是「你的订单状态怎么流转」「并发下单会不会超卖」「数据库为什么这么建表」而不是你用了多新的框架。所以整篇的落点会放在能复现的工程细节上而不是堆概念。2. 技术选型与整体架构为什么是微信小程序 SSM MySQL2.1 三端各自的职责边界先把这套架构拆成三层来看理解每一层为什么存在后面写代码才不会乱。微信小程序负责的是「用户能看见和点到的部分」商品列表、详情、发布、下单、个人中心。它跑在微信客户端里用 WXML 描述结构、WXSS 描述样式、JS 处理逻辑通过wx.request调用后端接口。选它的现实理由是不用装 App、有现成的登录态体系、开发工具开箱即用对毕业设计这种周期短、演示要求高的场景非常合适。SSM 是 Spring SpringMVC MyBatis 的缩写负责后端。Spring 管对象和事务SpringMVC 管 HTTP 请求路由MyBatis 管 SQL 映射。它比 SpringBoot 啰嗦但正因为配置都摊在明面上反而适合用来讲清楚「一个请求从进来到落库」的完整过程这也是很多学校指定 SSM 的原因。MySQL 负责持久化存用户、商品、订单、分类这些数据。选它的理由很朴素免费、资料多、和 MyBatis 配合成熟mysql安装配置教程这类内容网上一抓一大把出问题好查。2.2 一次「下单」请求的完整链路把三层串起来看一次真实调用比看十张架构图有用。用户在商品详情页点「立即购买」小程序发一个 POST 请求到/order/create带上商品 id 和买家 id。请求到达 SpringMVC 的 ControllerController 调用 ServiceService 里开启事务先查商品是否还在售、库存是否足够再插入订单记录、更新商品状态最后返回结果。MyBatis 负责把这几步翻译成 SQL 打到 MySQL。这条链路里最容易出问题的是事务边界和状态判断。如果 Service 没加Transactional或者先更新商品再判断库存就会出现「商品卖掉了但订单没生成」或者「同一件商品被两个人同时买走」的经典翻车。所以下面这张表把核心数据表先定清楚后面所有代码都围绕它展开。表名关键字段说明userid, openid, nickname, avatar, phone用户表openid 是小程序登录的唯一标识goodsid, seller_id, title, price, category_id, status, create_time商品表status 区分在售/已售/下架ordersid, goods_id, buyer_id, seller_id, amount, status, create_time订单表status 走待付款/已完成/已取消categoryid, name, sort分类表给首页做筛选2.3 环境准备与依赖版本动手前先把环境对齐版本不一致是新手最常见的第一个坑。JDK 用 1.8SSM 老项目对高版本 JDK 兼容性差Maven 用 3.6 以上Tomcat 用 8.5MySQL 用 5.7 或 8.0 都行但要注意驱动包版本对应。MySQL 8 要用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver写错了启动就报错。!-- pom.xml 核心依赖版本按自己环境微调 -- dependencies !-- Spring 核心管 IOC 和事务 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.20/version /dependency !-- SpringMVC处理 web 请求 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency !-- MyBatis 与 Spring 整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency !-- MySQL 驱动8.0 用 cj 版本 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.29/version /dependency /dependencies这段依赖里spring-context和spring-webmvc是 SSM 的骨架mybatis-spring是把 MyBatis 的 SqlSession 交给 Spring 管理的关键少了它事务不生效。MySQL 驱动版本要和本地数据库大版本匹配这是参数说明里最容易被忽略的一条。装 MySQL 时如果卡在mysql安装配置教程里的初始化步骤记住 root 密码一定要记下来后面applicationContext.xml里要填。3. 数据库设计与后端接口实现从建表到跑通第一个接口3.1 建表脚本与几个必须想清楚的设计点数据库是整个项目的根表建错了后面全是返工。下面给出核心建表脚本重点看字段类型和索引的选择。-- 用户表openid 加唯一索引防止重复注册 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信登录唯一标识, nickname VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表status 用 tinyint 表示状态price 用 decimal 不用 float CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, seller_id INT NOT NULL, title VARCHAR(100) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, category_id INT DEFAULT NULL, status TINYINT DEFAULT 1 COMMENT 1在售 2已售 3下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_seller (seller_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个设计点值得展开。第一价格字段用DECIMAL(10,2)而不是FLOAT因为浮点数做金额运算会出现0.10.20.30000000000000004这种精度问题答辩时被问到能答上来是加分项。第二status字段加了索引因为首页列表几乎每次查询都要按状态过滤数据量上来后没索引会明显变慢。字符集统一用utf8mb4否则用户昵称里的 emoji 存进去会变问号。3.2 MyBatis 映射与商品列表接口建完表开始写后端。先写 Mapper 接口和对应的 XML这是 MyBatis 的核心。// GoodsMapper.java 接口只声明方法SQL 写在 XML 里 public interface GoodsMapper { // 按状态分页查询商品offset 是起始行limit 是每页条数 ListGoods selectByStatus(Param(status) Integer status, Param(offset) Integer offset, Param(limit) Integer limit); // 根据 id 查询单个商品 Goods selectById(Param(id) Integer id); // 更新商品状态用于下单后把商品置为已售 int updateStatus(Param(id) Integer id, Param(status) Integer status); }!-- GoodsMapper.xmlnamespace 必须指向接口全限定名 -- mapper namespacecom.demo.mapper.GoodsMapper select idselectByStatus resultTypecom.demo.entity.Goods SELECT * FROM goods WHERE status #{status} ORDER BY create_time DESC LIMIT #{offset}, #{limit} /select select idselectById resultTypecom.demo.entity.Goods SELECT * FROM goods WHERE id #{id} /select update idupdateStatus UPDATE goods SET status #{status} WHERE id #{id} /update /mappernamespace写错是新手高频错误一旦和接口路径对不上启动就报Invalid bound statement。#{}是预编译占位符能防 SQL 注入${}是字符串拼接除非做动态排序否则别用。分页这里用的是LIMIT offset, limit的物理分页简单直接数据量不大时够用。3.3 Service 层事务与下单逻辑下单是整套系统里唯一必须保证原子性的操作Service 层要写清楚。Service public class OrderServiceImpl implements OrderService { Autowired private GoodsMapper goodsMapper; Autowired private OrderMapper orderMapper; // Transactional 保证查库存、插订单、改状态三步要么全成要么全回滚 Override Transactional(rollbackFor Exception.class) public String createOrder(Integer goodsId, Integer buyerId) { Goods goods goodsMapper.selectById(goodsId); // 先判断商品是否存在且在售这是防超卖的第一道闸 if (goods null || goods.getStatus() ! 1) { return 商品已售出或不存在; } // 卖家不能买自己的东西 if (goods.getSellerId().equals(buyerId)) { return 不能购买自己发布的商品; } // 插入订单 Orders order new Orders(); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getSellerId()); order.setAmount(goods.getPrice()); order.setStatus(1); orderMapper.insert(order); // 把商品状态改为已售这一步和上面插入在同一事务里 goodsMapper.updateStatus(goodsId, 2); return success; } }Transactional的rollbackFor Exception.class很关键默认只对运行时异常回滚如果中途抛了受检异常事务不会回滚订单插了但商品状态没改数据就脏了。判断顺序也有讲究先查状态再操作把校验放在写操作之前。这套逻辑在单机低并发下没问题但如果答辩老师追问「两个人同时点购买怎么办」可以答加数据库行锁SELECT ... FOR UPDATE或者用乐观锁在 update 时带上 status 条件判断影响行数。3.4 小程序端请求封装与列表渲染后端接口通了前端要能调。小程序里建议把wx.request封装一层避免每个页面重复写。// utils/request.js 统一封装请求方便加 baseUrl 和错误处理 const BASE_URL http://localhost:8080/api; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { content-type: application/json }, success: (res) { // 后端约定 code 为 200 表示成功 if (res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data.msg); } }, fail: (err) reject(err) }); }); } module.exports { request };封装的好处是 baseUrl 只改一处联调时从本地切到服务器地址不用满项目找。注意小程序开发阶段要在开发者工具里勾选「不校验合法域名」否则localhost请求会被拦。上线才需要配置真实域名和 HTTPS毕业设计演示阶段用本地即可。4. 小程序端页面开发与前后端联调把功能真正跑起来4.1 首页列表与分页加载首页是演示时第一眼看到的东西列表要能滚动加载。用小程序原生的onReachBottom做触底加载更多。// pages/index/index.js 首页商品列表 const { request } require(../../utils/request.js); Page({ data: { goodsList: [], page: 0, pageSize: 10, hasMore: true }, onLoad() { this.loadGoods(); }, loadGoods() { if (!this.data.hasMore) return; // 计算偏移量第几页就跳过多少条 const offset this.data.page * this.data.pageSize; request(/goods/list?status1offset offset limit this.data.pageSize, GET) .then(list { this.setData({ goodsList: this.data.goodsList.concat(list), page: this.data.page 1, // 返回条数小于 pageSize 说明没有更多了 hasMore: list.length this.data.pageSize }); }); }, onReachBottom() { this.loadGoods(); } });hasMore这个标志位是分页的关键没有它会出现「一直往下拉一直请求空数据」的情况。concat而不是直接赋值是为了保留已加载的数据。这套分页逻辑和前面 MyBatis 的LIMIT offset, limit是配套的前后端参数名要对齐否则查出来永远是第一页。4.2 发布商品与图片上传发布页涉及表单和图片上传是小程序里相对复杂的部分。图片上传用wx.uploadFile后端用 MultipartFile 接收。// pages/publish/publish.js 发布商品 const { request } require(../../utils/request.js); Page({ data: { title: , price: , description: , imageUrl: }, // 选择图片并上传 chooseImage() { wx.chooseImage({ count: 1, success: (res) { const filePath res.tempFilePaths[0]; wx.uploadFile({ url: http://localhost:8080/api/upload, filePath: filePath, name: file, success: (up) { // 后端返回图片访问路径 const data JSON.parse(up.data); this.setData({ imageUrl: data.data }); } }); } }); }, submit() { // 简单校验价格必须填且为数字 if (!this.data.title || !this.data.price) { wx.showToast({ title: 请填写完整, icon: none }); return; } request(/goods/publish, POST, { title: this.data.title, price: this.data.price, description: this.data.description, imageUrl: this.data.imageUrl }).then(() { wx.showToast({ title: 发布成功 }); wx.navigateBack(); }); } });上传接口后端要配置MultipartResolver否则MultipartFile收不到文件。图片存本地磁盘时路径要能通过 URL 访问到常见做法是存到 Tomcat 的 webapp 目录下或者配一个静态资源映射。这里有个坑小程序uploadFile的name必须和后端RequestParam(file)的名字一致不一致就报 400。4.3 登录态与 openid 获取小程序的登录不是账号密码而是wx.login拿 code后端换 openid。这是整套系统里唯一需要调用微信官方接口的地方。// 登录流程前端拿 code后端换 openid 并返回自定义 token wx.login({ success: (res) { // res.code 是一次性凭证五分钟内有效 request(/user/login, POST, { code: res.code }) .then(userInfo { // 把后端返回的用户信息存本地缓存 wx.setStorageSync(user, userInfo); }); } });后端拿到 code 后用 appid 和 secret 去请求微信接口换 openid再查库判断是否新用户是就插入。这里要注意code只能用一次重复使用会失败。appid 和 secret 在小程序后台获取写在后端配置文件里不要硬编码在代码中。演示阶段如果不想真调微信接口可以在后端写个 mock用固定 openid 返回但论文里要说明这是测试方案。4.4 联调时的接口约定与跨域前后端联调最容易卡在接口格式不统一。建议一开始就约定好统一响应结构{ code: 200, msg: success, data: {...} }前端只认code判断成败。跨域问题在小程序里其实不存在因为小程序不是浏览器环境没有同源策略但如果你用浏览器直接测后端接口就需要在 SpringMVC 里配 CORS 或者用 Postman 测。联调顺序建议是先用 Postman 把每个接口单独测通再接到小程序里这样出问题能快速定位是前端还是后端。5. 避坑与排查那些让答辩翻车的细节5.1 中文乱码数据库存进去变问号现象发布商品时标题是中文存到数据库里变成???或者接口返回的 JSON 里中文乱码。原因通常是三处字符集没统一数据库连接 URL 没加characterEncodingutf8、建表时没指定utf8mb4、Tomcat 的server.xml没配 URIEncoding。解决方法是逐层检查JDBC URL 写成jdbc:mysql://localhost:3306/db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai建表统一utf8mb4Tomcat 连接器加URIEncodingUTF-8。三处都对了才不会乱码。5.2 事务不生效订单插了商品状态没改现象下单后订单表有记录但商品还是「在售」能被人重复购买。原因多半是Transactional加在了 Controller 上而不是 Service 上或者类没有被 Spring 扫描到或者方法内部自己try-catch把异常吞了导致不回滚。解决方法是确认注解加在 Service 实现类的方法上确认包扫描路径包含该类异常要往外抛不要自己吃掉。这是答辩高频追问点务必自己测一遍。5.3 小程序请求报「不在以下 request 合法域名列表中」现象开发者工具里请求后端接口控制台报域名不合法。原因是小程序默认只允许请求已备案的 HTTPS 域名。解决方法是开发阶段在开发者工具「详情 - 本地设置」里勾选「不校验合法域名、web-view、TLS 版本以及 HTTPS 证书」演示时用本地地址即可。上线才需要真实域名毕业设计不用折腾这个。5.4 MySQL 8 连接报时区错误现象启动后端时报The server time zone value xxx is unrecognized。原因是 MySQL 8 的时区配置和驱动默认值不匹配。解决方法是在 JDBC URL 后面加serverTimezoneAsia/Shanghai或者去 MySQL 里执行SET GLOBAL time_zone 8:00。这个错误在mysql安装后第一次连库时特别常见记住加参数就行。5.5 图片上传后访问 404现象图片上传成功数据库里也存了路径但小程序里显示不出来。原因是上传的物理路径和 URL 访问路径没对应上或者 Tomcat 没配静态资源映射。解决方法是把图片存到 Tomcat webapps 下的固定目录或者用 SpringMVC 的mvc:resources把访问路径映射到磁盘目录。检查时先在浏览器直接访问图片 URL能打开说明是前端问题打不开就是映射没配好。6. 论文与演示视频的收尾技巧让交付物经得起追问到了最后阶段代码能跑只是及格线论文和演示视频才是决定分数的部分。论文里最容易被挑刺的是「需求分析」和「数据库设计」两章前者别写成功能罗列要按角色分买家能做什么、卖家能做什么、管理员能做什么每个用例配一张用例图。数据库设计章要把 E-R 图放上去说明实体之间的关系是一对多还是多对多比如用户和商品是一对多商品和订单是一对一。表结构别只贴建表语句加一段文字解释每个字段为什么这么设计前面提到的价格用 DECIMAL、状态加索引都是可以写进去的设计理由。演示视频建议按「登录 - 浏览 - 发布 - 下单 - 查看订单」这条主线录控制在三到五分钟。录之前把数据库清一遍用干净的测试数据避免录到一半列表里全是乱码标题。录屏时把开发者工具的 Network 面板也开着展示一次完整的请求响应比单纯点页面更有说服力。如果答辩老师问「你这个和市面上的二手平台有什么区别」老实回答这是教学项目、聚焦核心交易流程即可不要硬吹。我自己的习惯是代码提交前一定把applicationContext.xml里的数据库密码改成占位符把target目录和node_modules这类不该进仓库的东西写进.gitignore论文里的截图统一分辨率。这些细节不写进论文但答辩老师翻你代码仓库时一眼就能看出来。这套技术栈不新但把每个环节都做扎实本身就是一种能力。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。