
简介这份基于微信小程序的校园二手交易平台源码包面向毕业设计、期末大作业或课程设计场景适合计算机专业学生下载后快速部署、二次开发与答辩演示。项目以微信小程序为前端配合Java后端与数据库覆盖商品发布、浏览、搜索、交易等校园二手交易核心功能。压缩包258个文件、3.67MB包含104张png界面图、42个java后端类、20个js逻辑、18个json配置文件、13个vue组件、12个wxss与11个wxml小程序页面文件以及sql数据库脚本和xml配置目录按前端、后端、数据库划分方便定位与对照学习。其中用户控制器与微信控制器等文件展示了用户登录和微信交互接口的实现思路。已有456人学习下载完整的高分毕设源码可直接作为项目模板能帮助快速理解小程序Java后端数据库的整体架构与联调方法。1. 校园二手交易小程序这个“高分毕设”题里真正值钱的是数据库设计和整链路排查毕设题目列表里只要出现“校园二手交易平台”八成就是微信小程序前端加一套后端的组合。很多同学第一反应是页面难写实际上二手交易最花时间的不是界面而是商品状态流转、登录态维护和图片文件处理这些恰好是数据库设计与接口设计的地盘。这个方案能覆盖一条完整链路小程序端浏览、搜索、下单后端管理商品与订单状态MySQL 存用户、商品、订单三张核心表。适合准备毕设、想练手小程序项目以及要快速交付一个可演示 demo 的开发者参考。2. 从标题拆技术方案前端为什么选原生小程序后端怎么配才省事2.1 原生小程序还是 uni-app毕设场景我为什么选原生标题明确写了“微信小程序”没有提跨端需求所以第一选型是原生小程序。原生方案由 WXML、WXSS 和 JavaScript 组成微信开发者工具打开就能跑报错信息直接对应到页面文件社区里能搜到的项目实例也最多。uni-app 的优势是同一套代码以后还能编译到 Android、iOS 或鸿蒙端但代价是多一层编译链路和 HBuilderX 工具链。毕设通常两到三个月交付把时间花在业务逻辑上比花在跨端适配上有价值得多。如果以后要扩展常见做法是保留小程序原生目录后端接口设计成 RESTful 风格这样将来换 uni-app 或者做管理后台时接口可以复用不需要重写。换句话说原生不代表锁死真正锁死项目的是后端接口和数据库设计。后端我一般推荐 Spring Boot。原因不是它性能最强而是二手交易平台这类增删改查密集项目Spring Boot 的资料和现成代码最多遇到问题找得到答案。Node.js 也能做但 Java 课程设计和毕设的评分体系里Spring Boot 加 MyBatis 是多数评委熟悉的组合代码结构也更容易解释清楚。2.2 后端分层与项目目录Controller、Service、Mapper 怎么划分常见的做法是把后端分成四层Controller 接收请求Service 写业务规则Mapper 访问数据库entity 对应表结构。这样拆的好处是答辩时可以说清楚分层架构也方便定位问题。比如下单接口报错先看 Controller 参数是否绑定成功再看 Service 里的事务和状态判断最后才查 SQL。campus-market/ ├── src/main/java/com/campus/market/ │ ├── controller/ # 暴露 REST 接口 │ ├── service/ # 业务逻辑事务控制 │ ├── mapper/ # MyBatis 或 MyBatis-Plus 的 Mapper │ ├── entity/ # 与表对应的实体类 │ └── config/ # 拦截器、WebMvc 配置 ├── src/main/resources/ │ ├── mapper/ # XML 文件放复杂 SQL │ └── application.yml └── miniprogram/ # 微信小程序前端 ├── pages/ # index、detail、publish、profile、order ├── components/ # 商品卡片、空状态等复用组件 └── utils/request.js这个结构里有两点值得注意。第一controller 里不要写 SQL 或业务判断否则后面加一个管理员下架商品功能时你会发现还要去改下单接口。第二前端每个页面一个目录页面之间通过 wx.navigateTo 跳转不要用全局变量传对象页面刷新后全局变量会丢要从接口重新拉。2.3 请求封装给 wx.request 加统一 header 和错误拦截小程序开发里最容易翻车的就是 wx.request 回调嵌套。一个列表页要加载商品、加载分类、检查登录态如果每个接口都单独写 success 和 fail代码会膨胀到没法维护。我一般会在 utils/request.js 里封装一个 promise 版本的请求方法统一带 token统一处理 401。// utils/request.js const BASE_URL https://your.domain.com/api function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, // 从本地缓存取 token登录成功后写入 Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 res.data res.data.code 0) { resolve(res.data.data) } else if (res.statusCode 401) { // 登录态过期跳回登录页 wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) reject(new Error(unauthorized)) } 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 }这段封装的逻辑是所有接口走同一入口成功时判断业务 code 是否为 0token 失效时统一清理缓存并跳登录网络层错误也集中提示。参数上BASE_URL 建议按环境区分开发环境用 http://127.0.0.1:8080体验版和正式版用 https 域名写在同一个文件里方便替换。提示开发时可以在微信开发者工具里勾选“不校验合法域名”但真机预览和发布版本必须配置合法域名否则所有请求直接失败。这个坑在第 5 章还会详细展开。3. MySQL 数据库设计用户、商品、订单三张表的状态与索引3.1 用户表和地址表openid 要存手机号别乱存微信小程序登录的核心是 openid它是微信用户在小程序维度下的唯一标识。后端通过 wx.login 的 code 调微信接口换 openid这套交换逻辑在第 4 章写代码这里先说表结构。CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信 openid唯一, nickname varchar(64) DEFAULT COMMENT 昵称, avatar varchar(255) DEFAULT COMMENT 头像 URL, phone varchar(20) DEFAULT COMMENT 手机号可空, role tinyint NOT NULL DEFAULT 1 COMMENT 1 普通用户 2 管理员, status tinyint NOT NULL DEFAULT 1 COMMENT 1 正常 0 禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;openid 一定要加唯一索引这是登录逻辑里判断新老用户的依据。nickname 和 avatar 可以从微信的 wx.getUserProfile 拿但微信已经收紧了这类接口拿不到时后端可以按 openid 后几位生成默认昵称。phone 不是必须字段只有在用户发布商品希望买家联系时才要求填写毕设阶段可以做成发布时选填。地址表建议做成单独一张表还是挂在订单上要看你是否做站内私信加线下交易模式。校园二手交易大多是当面交易不需要收货地址那就别建 address 表省掉一个没用的关联。如果做了配送功能再单独建 user_address 表用户 id 做普通索引。3.2 商品表和分类表状态字段决定整个交易流程商品表是核心状态字段直接决定谁能看到、谁能操作。常见做法是用 tinyint 存状态机1 在售、2 已被下单、3 已售出、4 下架删除。这里的关键是不要用 0 和 1 表示上下架然后单独用一个字段表示交易状态两个字段互相牵扯查询时容易漏条件。CREATE TABLE goods ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 发布者, category_id bigint NOT NULL COMMENT 分类, title varchar(100) NOT NULL, description text COMMENT 描述, price decimal(10,2) NOT NULL COMMENT 价格单位元, original_price decimal(10,2) DEFAULT NULL COMMENT 原价可空, images json DEFAULT NULL COMMENT 图片 URL 列表, status tinyint NOT NULL DEFAULT 1 COMMENT 1 在售 2 锁定 3 已售 4 下架, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_create (status, create_time), KEY idx_category (category_id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;images 用 JSON 类型存图片 URL 数组比建一张图片子表省事列表页取封面时直接images-$[0]。价格用 decimal(10,2)不要用 float浮点数的精度问题在商品金额上会直接丢分。索引方面列表页的查询条件永远带status 1所以(status, create_time)的联合索引是必须的只建 status 索引或只建 create_time 索引都会让分页查询走全表。分类表比较简单id、name、sort 三个字段二手交易常见分类是数码、教材、生活用品、运动器材毕设阶段 4 到 8 个分类足够。不要搞成无限级分类那样前台分类选择器和小程序端的 picker 都要跟着复杂化。3.3 订单表和收藏表状态流转与重复下单约束订单表记录一次交易状态建议分成 1 待确认、2 已完成、3 已取消。买家下单后卖家在我的发布里能看到订单并确认成交状态流转是下单、卖家确认、完成。不需要做发货物流因为校园交易是线下见面。CREATE TABLE trade_order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号业务唯一, goods_id bigint NOT NULL COMMENT 商品, buyer_id bigint NOT NULL COMMENT 买家, seller_id bigint NOT NULL COMMENT 卖家, price decimal(10,2) NOT NULL COMMENT 成交价下单时快照, status tinyint NOT NULL DEFAULT 1 COMMENT 1 待确认 2 已完成 3 已取消, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_goods (goods_id), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;一个商品只能被一个买家成功下单这个约束靠应用层判断是不够的。两个用户同时点下单先查到status 1的都能通过然后一起更新就出问题。正确做法是下单时执行一条带条件的 UPDATEUPDATE goods SET status 2 WHERE id ? AND status 1受影响行数为 1 才算抢到否则提示商品已被下单。这条 SQL 是防超卖最简单也最可靠的写法。收藏表结构最简单id、user_id、goods_id、create_time唯一索引加在(user_id, goods_id)上。收藏不计入交易流程但它是个人中心页的一个亮点功能代码量不大答辩时能体现功能完整度。4. 核心流程落地登录、发布、分页、下单的代码与参数4.1 微信登录链路wx.login 的 code 怎么换成 openid微信小程序登录的标准流程是前端 wx.login 拿到临时 code把 code 发给后端后端用 code 调微信接口换 openid 和 session_key。code 五分钟内有效且只能用一次所以后端拿到 code 后要立刻调用微信接口。// pages/login/login.js wx.login({ success: async (res) { if (!res.code) { wx.showToast({ title: 登录失败, icon: none }) return } const data await request(/auth/login, POST, { code: res.code }) wx.setStorageSync(token, data.token) wx.setStorageSync(userInfo, data.userInfo) wx.navigateBack() } })PostMapping(/auth/login) public Result login(RequestBody MapString, String body) { String code body.get(code); // 调微信接口appid secret code 换 openid MapString, String wxResp wxClient.code2Session(code); String openid wxResp.get(openid); if (openid null) { return Result.error(code 无效或已过期); } User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(用户 openid.substring(openid.length() - 6)); userMapper.insert(user); } String token JwtUtil.createToken(user.getId()); return Result.success(Map.of(token, token, userInfo, user)); }这段链路里有几个参数值得注意。code2Session 返回的 openid 是身份依据session_key 是微信加密数据的解密密钥毕设没用到电话和微信步数等加密数据就不用存 session_key。token 我一般用 JWT把 userId 放进去有效期设 7 天拦截器里解析 token 拿到当前用户不要直接把 openid 当前端请求参数传openid 泄露后任何人都能冒充用户。注意新用户首次登录时后端要自动注册前端不需要单独的注册页。这是小程序登录和传统账号密码登录最大的区别答辩时评委常问这一点。4.2 商品发布图片上传和表单提交的先后顺序发布页是表单页包含标题、描述、价格、分类、图片。图片先用 wx.chooseMedia 选择再用 wx.uploadFile 逐张传到后端全部上传成功后再把返回的 URL 拼进表单数据提交商品接口。顺序不能反如果先提交商品再传图片商品详情页会有一段时间是没有图的空壳状态。// pages/publish/publish.js async function uploadImages() { const res await wx.chooseMedia({ count: 9, mediaType: [image], sizeType: [compressed] }) const uploadTasks res.tempFiles.map((file) { return new Promise((resolve, reject) { wx.uploadFile({ url: BASE_URL /upload, filePath: file.tempFilePath, name: file, success: (res) { // 后端返回 JSON 字符串需要解析 const data JSON.parse(res.data) resolve(data.url) }, fail: reject }) }) }) return Promise.all(uploadTasks) }chooseMedia 的count: 9限制最多九张图这是小程序端的限制sizeType: [compressed]表示优先压缩图二手商品不需要原图精度压缩图可以明显降低上传耗时。后端上传接口保存文件时文件名用 UUID 拼上原扩展名防止中文文件名和重复文件名覆盖旧文件。商品提交接口的数据结构是{ title, description, price, categoryId, images: [...] }后端在 Service 层补上 userId从 token 解析status 默认 1。价格字段前端输入是字符串提交前要转成数字并且校验大于 0否则会出现price 这种直接让数据库报错的数据。4.3 列表分页page 和 pageSize 的参数设计列表页是流量最大的接口首页、分类页、搜索结果、个人中心我的发布都复用它。分页参数统一用 page 和 pageSizepage 从 1 开始pageSize 固定 10 或 20后端返回{ list, total, hasMore }hasMore 给小程序的触底加载判断用。-- 首页列表只看在售商品按发布时间倒序 SELECT id, title, price, images, create_time FROM goods WHERE status 1 AND category_id #{categoryId} -- 分类筛选可空 AND title LIKE CONCAT(%, #{keyword}, %) -- 搜索可空 ORDER BY create_time DESC LIMIT #{offset}, #{pageSize}// 后端计算 offset int page Math.max(1, dto.getPage()); int pageSize Math.min(20, dto.getPageSize()); int offset (page - 1) * pageSize;这组参数里有三个容易忽略的边界。第一pageSize 要做上限保护用户传 10000 时直接截断到 20防止一次拉全表。第二keyword 搜索必须用%keyword%的 LIKE这意味着全表扫描数据量大时性能差但毕设规模下可接受答辩时可以主动说后续可以用全文索引或搜索引擎优化。第三order by 的字段要和索引匹配(status, create_time)联合索引正好覆盖首页查询。前端翻页时需要维护 page 状态首次加载 page1触底时 page1返回的 list 长度小于 pageSize 时置 hasMorefalse。很多人在这个环节踩坑回调里直接赋值而不是拼接 list导致第二页数据把第一页覆盖了。4.4 下单流程没有微信支付时怎么做成完整闭环校园二手交易平台要接入微信支付需要企业主体资质个人开发者和学生很难申请。毕设的标准做法是下单后线下交易买家下单商品状态锁定卖家在订单列表确认成交交易闭环完成不需要真实支付。这个设计要在答辩时说清楚是功能边界不是缺陷。Transactional public Order createOrder(Long goodsId, Long buyerId) { // 条件更新锁住商品防止重复下单 int updated goodsMapper.lock(goodsId); if (updated 0) { throw new BizException(手慢了商品已被下单); } Goods goods goodsMapper.selectById(goodsId); Order order new Order(); order.setOrderNo(genOrderNo()); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getUserId()); order.setPrice(goods.getPrice()); order.setStatus(1); orderMapper.insert(order); return order; }-- goodsMapper.lock 对应的 SQL UPDATE goods SET status 2 WHERE id #{goodsId} AND status 1这段代码的核心是Transactional和条件 UPDATE 配合。事务保证商品锁定和订单创建要么都成功要么都失败条件 UPDATE 用数据库的行锁解决并发下单。orderNo 的生成规则我一般用yyyyMMddHHmmss 四位随机数脱敏展示时长度刚好也可以直接存时间戳加自增序列。卖家确认成交时复用同一套状态机订单状态改成 2商品状态改成 3买家卖家在订单详情页都能看到完成信息。5. 避坑与排查这个项目最容易翻车的 5 个地方5.1 图片上传成功但详情页打不开现象是发布时图片上传接口返回成功数据库里也存了 URL但小程序页面上图片裂开、加载不出来。原因基本是路径问题。开发时如果用本地磁盘路径确认了上传成功数据库里存的是后端机器上的绝对路径如/usr/share/images/xxx.jpg小程序端无法访问后端服务器文件。解决方法是上传接口返回可被外网访问的完整 URL后端用https://你的域名/images/xxx.jpg拼接如果后端没有单独的文件服务就把图片放到 Nginx 静态目录用 Nginx 托管/images/路径。本地开发时为了调联也可以让后端接口直接返回包含本机局域网 IP 的 URL但发布前必须统一改成域名这一步最容易在验收前一晚翻车。5.2 真机调试正常体验版白屏现象是开发者工具里一切正常扫码体验版后页面能打开但所有接口数据为空或者直接白屏。原因是体验版和正式版走真实网络请求微信要求所有 request 和 uploadFile 的域名必须在小程序后台配置为合法域名而且必须是 HTTPS。开发工具里勾选的不校验合法域名只对本机开发有效不影响手机端。解决方法是到微信公众平台的小程序后台在开发管理的开发设置里配置服务器域名request 合法域名和 uploadFile 合法域名都要配。域名要有 HTTPS 证书ICP 备案状态也要正常。这里的排查顺序是先看手机端控制台有没有url not in domain list有就直接去配域名不用查后端。5.3 登录态丢失或不同设备账号错乱现象是用户在小程序里操作一段时间后突然跳回登录页或者两台手机登录后看到的是同一个人的数据。原因分两种一种是 token 过期但前端拦截器没有做好续期401 后直接清缓存跳登录体验很差另一种是后端把 token 和用户的关系做错了比如用全局静态变量存用户服务重启后变量清空所有用户都变成新用户。解决方法是 token 里只存 userId每次请求从数据库查最新用户信息不缓存整个 user 对象token 有效期设 7 天用户在自然使用周期内不需要重新登录。还有一个小细节wx.setStorageSync 存用户信息时key 要统一覆盖不要在多账号切换时互相覆盖否则会出现一个手机登录两个号后个人中心显示的还是上一个号的信息。5.4 接口偶发假死数据库连接池耗尽现象是操作一段时间后所有接口都转圈不返回重启后端就好了过一会又卡死。原因是数据库连接池参数没有配置。Spring Boot 默认的 HikariCP 连接池如果不显式配置在并发不高时没问题但小程序端频繁请求加慢 SQL 会让连接被占满新的请求排队等连接表现就是接口假死。解决方法是把连接池参数写进配置并找到慢 SQL 加索引。spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000排查时先看数据库侧SHOW PROCESSLIST里有没有大量 Sleep 或长时间未提交的事务。没有连接池概念的项目最常见的翻车点是查询接口里写了大循环逐条查库N1 查询在商品列表这种接口上会把数据库拖垮。商品列表查出来后如果还要查每个商品的收藏数和评论数应该用一条 GROUP BY 聚合查询而不是在循环里再查十次。5.5 列表越翻越慢分页深翻页问题现象是首页前几页加载很快翻到第 20 页时明显变慢甚至超时。原因是LIMIT 200, 20这样的深分页会让 MySQL 扫描前 200 行再丢弃偏移量越大扫描越多。毕设数据量几千条时感觉不明显但答辩时如果导入了上万条演示数据这个问题会暴露。解决方法是把分页方式从 offset 改成游标分页首页传 create_time 和 id 作为游标后端用WHERE create_time #{lastTime} OR (create_time #{lastTime} AND id #{lastId})的方式取下一页这样每次查询都走索引翻页深度不影响性能。这个优化在论文里是一个很好的技术亮点比写一堆业务功能更容易拿分。6. 从能跑到能答辩演示数据、演示路径和亮点表达6.1 造一份能讲故事的演示数据空数据库跑通功能后直接演示会显得单薄。我一般会写一个数据填充脚本生成 20 个用户、8 个分类、每个分类 5 到 10 件商品、若干条订单和收藏记录。商品标题用贴近校园的话术“九成新高数教材划重点版”“宿舍小冰箱出毕业急出”“机械键盘青轴用了三个月”。演示时按分类点进去每一页都有内容比只有一条测试数据要有说服力得多。填充脚本用 SQL 写循环或者直接在测试类里跑都可以。注意商品图片不要用后端随机拼出来的假 URL直接用几张真实可访问的图片地址或者上传几张本地图片到服务器否则演示时图片裂开反而扣分。6.2 答辩演示的路径顺序演示不要从登录开始而要从首页开始。顺序是首页列表、分类筛选与搜索、点进商品详情、登录并讲解 openid 机制、发布一件商品、下单、切换卖家身份确认成交、个人中心看我的发布和收藏。这条路径覆盖了登录、商品增删改查、订单状态机、图片上传所有核心功能全程三到五分钟评委不需要等接口转圈。最后补充一个我在多个项目里反复确认的习惯开发期间每周导出一份完整数据库备份文件名带日期改表结构前先把旧的 schema 存一份否则改字段改到一半想回退时没有后悔药。小程序项目和别的后端项目一样数据库结构变更是最危险的步骤备份比任何框架经验都重要。希望帮到你。本文还有配套的精品资源点击获取