ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

校园二手交易小程序毕业设计:从环境搭建到答辩的完整落地指南

校园二手交易小程序毕业设计:从环境搭建到答辩的完整落地指南 简介这是一套面向计算机相关专业毕业设计与课程设计场景的校园二手交易平台小程序源码采用微信小程序前端配合Java后端与数据库实现适合正在准备毕设、期末大作业或需要完整项目实战练习的学生参考使用。资源包共258个文件整体约3.68MB其中包含42个Java后端源码、20个JavaScript脚本、13个Vue组件、12个WXSS样式与11个WXML页面结构文件另有18个JSON配置、9个XML配置、1个SQL数据库脚本以及大量PNG、JPG界面截图素材覆盖前端页面、后端接口与数据存储的完整链路。该资源为经导师指导并认可的高分毕业设计项目评审分99分代码完整可运行对新手较为友好。目前已有135人学习下载读者可据此快速理解校园二手交易业务的整体架构掌握小程序页面搭建、后端接口编写与数据库表结构设计思路并借助现成目录结构完成环境部署与二次开发练习。1. 校园二手交易小程序从“能跑”到“能交差”的完整落地路径每年毕业季总有一批同学卡在同一个地方想做一个校园二手交易平台当毕业设计结果要么卡在微信开发者工具的环境配置上要么卡在数据库表设计上要么代码跑起来了但一打开就报错。这个标题背后其实包含三层东西微信小程序前端、后端服务、数据库设计外加一个“毕业设计项目”的交付要求——它不仅要能跑还得有文档、有结构、能讲清楚。我见过太多类似的翻车现场前端页面画得挺漂亮结果商品列表加载不出来数据库建了十几张表结果外键关系一团乱本地调试一切正常换台电脑就崩。这篇笔记就是把这些坑一个个填上从环境搭建到数据库设计再到核心功能实现和最后的论文答辩技巧给你一条能直接抄作业的路径。适合正在做毕业设计、需要一套完整可运行源码参考的同学也适合想快速了解小程序全栈开发流程的开发者。2. 环境搭建与项目骨架别在第一步就卡住2.1 微信开发者工具与后端运行环境的版本选择很多教程上来就让你装最新版但血泪经验是微信开发者工具的稳定版和 Nightly 版差别很大毕业设计项目千万别用 Nightly 版否则调试基础库时会出现各种玄学问题。我一般会锁定在某个稳定版本比如基础库选 2.30 以上的稳定版即可具体版本号以你下载时的稳定版为准。后端方面如果你选 Java 技术栈JDK 用 8 或 11 都行但 Spring Boot 版本要和 JDK 匹配。JDK 8 配 Spring Boot 2.7.x 是最稳的组合别上来就 JDK 17 配 Spring Boot 3.x虽然新但很多校园项目的教程和依赖包还没跟上遇到问题搜都搜不到。数据库用 MySQL 5.7 或 8.0 都可以但 8.0 的默认字符集和认证插件有变化后面会专门讲这个坑。如果你选 Node.js 技术栈Node 版本建议 16.x 或 18.x LTS别用 20.x 以上的最新版有些依赖包还没适配。数据库同样 MySQL或者用 MongoDB 也行但校园二手交易涉及订单和用户关系关系型数据库更合适。提示所有环境变量配好后先在命令行里验证一遍比如java -version、node -v、mysql --version确认输出正常再往下走。2.2 小程序项目目录结构与后端接口分层微信小程序的目录结构是有官方约定的但很多同学直接拿网上的模板改改到最后自己都找不到文件。我建议按功能模块划分而不是按文件类型划分。下面是一个我常用的目录结构miniprogram/ ├── pages/ │ ├── index/ # 首页-商品列表 │ ├── detail/ # 商品详情 │ ├── publish/ # 发布商品 │ ├── user/ # 个人中心 │ └── order/ # 订单管理 ├── components/ # 自定义组件 ├── utils/ # 工具函数 ├── services/ # 接口请求封装 ├── images/ # 静态图片 ├── app.js ├── app.json └── app.wxss后端接口按 RESTful 风格分层Controller 层只负责接收参数和返回结果Service 层写业务逻辑Mapper/Repository 层操作数据库。这样分层的好处是答辩的时候老师问“你的业务逻辑写在哪”你能清晰地说出来而不是所有代码堆在一个文件里。// services/goods.js - 商品相关接口封装 const BASE_URL http://localhost:8080/api; // 统一请求方法处理 token 和错误码 function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { content-type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else { // 401 表示未登录或 token 过期 if (res.data.code 401) { wx.navigateTo({ url: /pages/login/login }); } reject(res.data.msg || 请求失败); } }, fail: (err) { reject(err.errMsg || 网络异常); } }); }); } // 获取商品列表支持分页和关键词搜索 function getGoodsList(page 1, size 10, keyword ) { return request(/goods/list?page${page}size${size}keyword${keyword}); } // 发布商品 function publishGoods(data) { return request(/goods/publish, POST, data); } module.exports { getGoodsList, publishGoods };这段代码的关键点在于统一封装了请求方法把 token 放在 header 里并且统一处理了 401 未登录的情况。参数说明page是当前页码size是每页条数keyword是搜索关键词。后端返回的数据结构约定为{ code, msg, data }code为 0 表示成功非 0 表示各种错误。这样前端只需要判断code就能知道请求结果不用每个接口都写一遍判断逻辑。2.3 数据库连接配置与初始化脚本数据库连接配置是新手最容易翻车的地方。MySQL 8.0 的驱动类名和 5.7 不一样连接 URL 也要加时区参数。下面是一个 Spring Boot 的application.yml配置示例spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_trade?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password注意driver-class-name在 MySQL 8.0 里是com.mysql.cj.jdbc.Driver5.7 是com.mysql.jdbc.Driver写错了会报ClassNotFoundException。serverTimezone必须加否则会报时区错误。useSSLfalse在本地开发时加上避免 SSL 警告。初始化数据库的 SQL 脚本我一般会单独放在resources/db/init.sql里项目启动时手动执行一次。下面是一个简化的建表语句CREATE DATABASE IF NOT EXISTS campus_trade DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE campus_trade; -- 用户表 CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, openid VARCHAR(64) NOT NULL COMMENT 微信openid, nickname VARCHAR(64) DEFAULT COMMENT 昵称, avatar VARCHAR(255) DEFAULT COMMENT 头像地址, phone VARCHAR(20) DEFAULT COMMENT 手机号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 商品表 CREATE TABLE goods ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 商品ID, user_id BIGINT NOT NULL COMMENT 发布者ID, title VARCHAR(100) NOT NULL COMMENT 商品标题, description TEXT COMMENT 商品描述, price DECIMAL(10,2) NOT NULL COMMENT 价格, category VARCHAR(32) DEFAULT COMMENT 分类, images VARCHAR(1024) DEFAULT COMMENT 图片地址逗号分隔, status TINYINT DEFAULT 1 COMMENT 状态1在售 2已售 3下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;字符集用utf8mb4而不是utf8因为utf8在 MySQL 里是残缺的存不了 Emoji 和一些特殊字符。商品图片我用逗号分隔的字符串存多个地址而不是单独建一张图片表因为校园项目图片数量有限这样查起来更简单。如果要做正规项目建议单独建goods_image表但毕业设计阶段简单直接更重要。3. 核心功能实现商品发布、列表加载与订单流程3.1 商品发布页面的图片上传与表单校验商品发布是二手交易平台最核心的功能之一。这里有两个关键点图片上传和表单校验。微信小程序的图片上传用wx.chooseImage和wx.uploadFile但很多同学不知道wx.uploadFile一次只能传一个文件多图需要循环调用。// pages/publish/publish.js Page({ data: { images: [], // 本地图片临时路径 title: , price: , description: , category: }, // 选择图片最多 9 张 chooseImage() { const remain 9 - this.data.images.length; wx.chooseImage({ count: remain, sizeType: [compressed], sourceType: [album, camera], success: (res) { this.setData({ images: this.data.images.concat(res.tempFilePaths) }); } }); }, // 提交表单 async submit() { // 前端校验标题和价格必填 if (!this.data.title.trim()) { wx.showToast({ title: 请输入商品标题, icon: none }); return; } if (!this.data.price || parseFloat(this.data.price) 0) { wx.showToast({ title: 请输入有效价格, icon: none }); return; } wx.showLoading({ title: 发布中... }); try { // 先上传所有图片拿到服务器返回的图片地址 const uploadTasks this.data.images.map(path { return new Promise((resolve, reject) { wx.uploadFile({ url: http://localhost:8080/api/upload, filePath: path, name: file, header: { Authorization: wx.getStorageSync(token) || }, success: (res) { const data JSON.parse(res.data); if (data.code 0) { resolve(data.data.url); } else { reject(data.msg); } }, fail: reject }); }); }); const imageUrls await Promise.all(uploadTasks); // 再提交商品信息 const goodsService require(../../services/goods); await goodsService.publishGoods({ title: this.data.title, price: this.data.price, description: this.data.description, category: this.data.category, images: imageUrls.join(,) }); wx.hideLoading(); wx.showToast({ title: 发布成功, icon: success }); setTimeout(() { wx.navigateBack(); }, 1500); } catch (err) { wx.hideLoading(); wx.showToast({ title: err || 发布失败, icon: none }); } } });这段代码的逻辑是先让用户选图片图片存在本地临时路径里提交时先把所有图片上传到服务器拿到服务器返回的 URL 数组再把商品信息和图片 URL 一起提交。参数说明count是还能选几张sizeType用compressed压缩图避免上传原图太慢。Promise.all保证所有图片都上传成功后才提交商品信息任何一张失败都会进入 catch。注意wx.uploadFile返回的res.data是字符串需要JSON.parse转成对象这一点和wx.request不一样很多同学在这里翻车。3.2 商品列表的分页加载与下拉刷新商品列表页需要处理分页、下拉刷新和上拉加载更多。核心逻辑是维护一个page变量每次加载更多时page 1直到后端返回空列表为止。// pages/index/index.js const goodsService require(../../services/goods); Page({ data: { goodsList: [], page: 1, size: 10, keyword: , hasMore: true, loading: false }, onLoad() { this.loadGoods(); }, // 加载商品列表 async loadGoods(refresh false) { if (this.data.loading) return; if (!refresh !this.data.hasMore) return; this.setData({ loading: true }); try { const page refresh ? 1 : this.data.page; const list await goodsService.getGoodsList(page, this.data.size, this.data.keyword); this.setData({ goodsList: refresh ? list : this.data.goodsList.concat(list), page: page 1, hasMore: list.length this.data.size, loading: false }); } catch (err) { this.setData({ loading: false }); wx.showToast({ title: err || 加载失败, icon: none }); } }, // 下拉刷新 onPullDownRefresh() { this.loadGoods(true).then(() { wx.stopPullDownRefresh(); }); }, // 上拉加载更多 onReachBottom() { this.loadGoods(); }, // 搜索 onSearchInput(e) { this.setData({ keyword: e.detail.value }); }, onSearchConfirm() { this.loadGoods(true); } });参数说明refresh为 true 时重置到第一页hasMore根据返回列表长度是否等于size来判断。onPullDownRefresh和onReachBottom是小程序页面的生命周期函数需要在app.json或页面配置里开启enablePullDownRefresh。搜索功能通过keyword参数传给后端后端用LIKE查询实现模糊匹配。3.3 订单状态流转与数据库事务处理订单流程是校园二手交易里最容易出 bug 的地方。一个典型的订单状态流转是买家下单 → 卖家确认 → 买家付款或线下交易→ 交易完成。每个状态变更都要保证数据库一致性尤其是涉及商品状态变更时。// OrderService.java - 订单服务层 Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private GoodsMapper goodsMapper; // 创建订单同时把商品状态改为已售 Transactional(rollbackFor Exception.class) public Order createOrder(Long goodsId, Long buyerId) { // 1. 查询商品判断是否在售 Goods goods goodsMapper.selectById(goodsId); if (goods null) { throw new RuntimeException(商品不存在); } if (goods.getStatus() ! 1) { throw new RuntimeException(商品已售出或已下架); } if (goods.getUserId().equals(buyerId)) { throw new RuntimeException(不能购买自己发布的商品); } // 2. 创建订单 Order order new Order(); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getUserId()); order.setAmount(goods.getPrice()); order.setStatus(1); // 1待确认 orderMapper.insert(order); // 3. 更新商品状态为已售 goods.setStatus(2); goodsMapper.updateById(goods); return order; } }这段代码的关键是Transactional注解它保证创建订单和更新商品状态要么都成功要么都回滚。如果没有这个注解可能出现订单创建了但商品状态没改或者商品状态改了但订单没创建的情况。参数说明goodsId是商品 IDbuyerId是买家 IDstatus为 1 表示待确认。异常抛出后 Spring 会自动回滚事务前端收到错误提示。4. 避坑与排查那些让你熬夜的常见问题4.1 微信登录 code 换取 openid 失败现象小程序端调用wx.login拿到 code后端请求微信接口换取 openid 时返回errcode: 40029或invalid code。原因code 只能用一次且有效期只有 5 分钟。很多同学在调试时反复用同一个 code 去换或者前端重复调用wx.login导致 code 失效。解决每次登录都重新调用wx.login获取新 code后端换到 openid 后立即生成自己的 token 返回给前端后续请求用 token 鉴权不再用 code。另外检查后端请求微信接口时appid和secret是否配对grant_type是否为authorization_code。4.2 MySQL 8.0 时区与字符集导致的乱码和连接失败现象后端启动时报The server time zone value ?D1ú±ê×?ê±?? is unrecognized或者存进去的中文变成乱码。原因MySQL 8.0 默认时区是 UTC且字符集配置和连接 URL 不匹配。解决连接 URL 加serverTimezoneAsia/Shanghai数据库和表的字符集用utf8mb4连接 URL 加characterEncodingutf-8。如果已经建了表且字符集不对用ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4;修改。4.3 图片上传后本地能显示、线上不显示现象开发时图片存在本地磁盘前端用http://localhost:8080/upload/xxx.jpg能访问部署到服务器后图片 404。原因本地路径和服务器路径不一致或者服务器没有配置静态资源映射。解决后端配置静态资源映射把上传目录映射到 URL 路径。Spring Boot 里可以在application.yml里配置spring.web.resources.static-locations或者写一个WebMvcConfigurer实现类。另外图片 URL 建议存相对路径前端拼接完整域名这样换域名时不用改数据库。4.4 小程序请求域名未配置导致的合法域名错误现象真机调试时请求后端接口报request:fail url not in domain list。原因微信小程序正式版要求所有请求域名必须在后台配置合法域名且必须是 HTTPS。解决开发阶段在微信开发者工具里勾选“不校验合法域名”真机预览时可以在右上角菜单里开启调试模式。正式发布前把后端接口部署到 HTTPS 域名并在小程序后台配置request合法域名。如果毕业设计只是演示用开发者工具的“预览”功能即可不一定要发布。4.5 数据库外键约束导致的删除失败现象删除商品时报表关联错误或者删除用户时提示有外键约束。原因建表时加了外键约束但删除主表数据时从表还有关联数据。解决校园项目建议逻辑删除用status字段标记删除而不是物理删除。如果一定要物理删除先删从表数据再删主表数据或者建表时不加外键约束在代码里保证数据一致性。我一般会在goods表加is_deleted字段查询时过滤掉已删除的数据。5. 从能跑到能答辩源码整理与演示技巧5.1 源码目录的规范化整理答辩前老师通常会要求看源码如果目录一团乱印象分直接扣一半。我一般会按这样的结构整理campus-trade/ ├── miniprogram/ # 小程序前端源码 ├── server/ # 后端源码 │ ├── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/campus/trade/ │ │ │ │ ├── controller/ │ │ │ │ ├── service/ │ │ │ │ ├── mapper/ │ │ │ │ └── entity/ │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── db/init.sql │ └── pom.xml ├── docs/ # 文档和截图 │ ├── 数据库设计.md │ ├── 接口文档.md │ └── 运行说明.md └── README.mdREADME.md里写清楚环境要求、启动步骤、默认账号密码。docs目录里放数据库 E-R 图、接口列表和运行截图。这些材料在答辩时直接展示比口头解释有效得多。5.2 答辩演示的流程设计与常见提问准备演示流程建议按“用户注册登录 → 发布商品 → 浏览商品 → 下单 → 订单管理”走一遍每个步骤提前准备好测试数据避免现场操作时卡顿。常见提问包括为什么选这个技术栈、数据库表为什么这样设计、如何保证数据一致性、有没有考虑并发问题。提前准备好答案比如“选 Spring Boot 是因为生态成熟、资料多”“订单创建用了事务保证一致性”“并发量小暂时没做分布式锁但可以通过乐观锁优化”。5.3 一个提高演示成功率的技巧我自己的习惯是答辩前把整个流程在本地完整跑三遍每一遍都从零开始——清空数据库、重启后端、重新编译小程序。第三遍能顺利跑通答辩基本不会出问题。另外准备一个“后悔药”把数据库初始化脚本和测试数据 SQL 单独存一份万一现场数据乱了直接重新执行脚本恢复。这个习惯帮我省过好几次尴尬。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进