
简介这份压缩包是基于微信小程序的校园二手物品交易系统毕业设计资源包含完整前端小程序源码、Java后端与MySQL数据库脚本面向计算机专业学生及小程序开发者可用于毕业设计、课程设计或校园闲置交易项目实战参考。包体共941个文件大小约30.07MB覆盖WXML/WXSS页面结构、JavaScript交互逻辑、Java服务端接口、XML与properties配置、SQL数据库建表脚本以及150个GIF和57个PNG等演示素材目录结构清晰便于按模块检索。系统服务端实现后台首页、用户信息管理、商品信息管理、违规投诉管理和订单管理前端提供首页全类别展示、电子产品/服装等分类筛选、二手物品发布及个人中心并集成资料修改、密码重置与收藏功能形成完整交易闭环。已有2597人学习附演示视频可直观查看运行流程源码与数据库导入微信开发者工具和IDEA即可运行适合二次开发与论文撰写。1. 校园二手交易小程序这个毕业设计起点在需求拆解毕业设计选到“基于微信小程序的校园二手物品交易系统”第一反应很容易是“这不就是个发帖功能加列表页吗”。可一旦开工“用户登录后能发布商品”这一句话就要拆成微信授权、会话保持、用户表设计、图片上传、后端接口、数据落库六条链路。标题里的“源码数据库演示视频”正好对应评审关心的三件事代码能不能跑、表结构是否合理、系统演示得像不像真实用户在操作。这个方向适合正在做毕设的学生也适合想在课程设计里快速搭通一套完整交易类小程序的人。2. 服务端技术栈怎么定Spring Boot MySQL 是最省心的一条路2.1 为什么服务端选 Spring Boot 而不是 Node.js 或 PHP很多同学做这类选题时会纠结后端到底用什么。常见选项有三个Spring Boot、Node.jsExpress、PHPThinkPHP。我一般会直接建议 Spring Boot原因很现实毕业答辩的评审老师几乎都认识 Spring 那一套分层源码检查时他看到 controller/service/mapper 就知道代码组织是有章法的。Node.js 和 PHP 本身没有问题但数据库连接、事务管理、异常处理这些都要自己搭得比较仔细稍有不慎就会在细节上被追问。小程序端我建议用原生微信小程序框架不要为了“跨平台”上 uni-app。这个二次交易的页面数量通常不超过十个原生框架完全够用而且微信开发者工具对原生代码的调试支持最直接。你搜“微信小程序 开发者工具”相关的排错技巧绝大多数都是基于原生框架讲的遇到问题更容易查到答案。原生小程序的 app.json 里可以直接配页面路由评审演示时点起来路径清晰不容易迷路。选型对比我用一张表说清楚后端方案上手成本与微信小程序对接适合的场景Spring Boot中等需要懂依赖注入和分层用 RESTful API 很顺需要一个有说服力的 Java 后端Node.js/Express低前后端同语言也顺但要自己处理事务前端底子好想少写 JavaPHP/ThinkPHP低虚拟主机部署方便接口也好写目标环境是老式虚拟主机选 Spring Boot 还有一个隐性好处项目里能自然地拆出 entity、mapper、service、controller 四层答辩 PPT 上写“分层架构”四个字是有实际代码可以指的被追问到细节时也不会无话可说。2.2 统一返回体与接口规范前后端联调的第一约定微信小程序端发 wx.request拿到的是一段 JSON。如果每个接口返回的结构都不一样前端写起来会非常痛苦。我做这类系统时第一件事不是写业务接口而是先定义统一返回体。这个类在源码里通常叫 ResponseResult 或者 Result核心字段就三个code、message、data。code 为 0 表示成功非 0 表示业务错误例如 1001 表示未登录、1002 表示参数校验失败。下面这个就是最常见的写法public class ResponseResultT { private int code; private String message; private T data; public static T ResponseResultT success(T data) { ResponseResultT result new ResponseResult(); result.code 0; result.message success; result.data data; return result; } public static T ResponseResultT error(int code, String message) { ResponseResultT result new ResponseResult(); result.code code; result.message message; result.data null; return result; } // getter 和 setter 按需要生成 }这段代码的核心是泛型 data登录接口返回用户对象、商品列表接口返回数组、详情接口返回单个对象前端拿到后不用做额外类型转换。这里有一个容易踩的坑error 里的 code 不要直接用 HTTP 状态码代替。接口地址错了返回 404那是 HTTP 层的错误前端拦截器应该区分两层先看 HTTP status 是否为 200再看返回体的 code 是否为 0两层校验缺一不可。2.3 最小可运行配置application.yml 里必须写对的三个参数数据库配置是项目跑起来的第一道门槛。有很多人下载源码后改完数据库密码启动结果报一堆错。问题往往不在密码而在另外三个参数。第一连接地址要确认后端监听的是 0.0.0.0 而不是只监听 localhost否则小程序真机无法通过局域网访问。第二连接串一定要带 serverTimezoneAsia/Shanghai否则写入时间字段会差八个小时。第三MySQL 8.x 默认的认证插件是 caching_sha2_passwordSpring Boot 内置的 mysql-connector-java 版本如果不兼容要在连接串里加 allowPublicKeyRetrievaltrue。一份能跑通的基础配置大概是这样的server: port: 8080 address: 0.0.0.0 spring: datasource: url: jdbc:mysql://localhost:3306/campus_trade?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true重点解释三个配置项allowPublicKeyRetrievaltrue 解决 MySQL 8.x 首次连接时获取公钥的问题不加会直接报 Public Key Retrieval is not alloweduseSSLfalse 只在本地开发环境用生产环境要配证书map-underscore-to-camel-case 开启后数据库字段 user_id 自动映射成 Java 属性 userId能少写一大片 TableField 注解。第一次启动如果还是连不上先用 mysql 命令行客户端确认数据库账号能正常登录再回头检查配置文件不要一上来就怀疑代码。3. 数据库设计四张表把交易闭环立住3.1 用户表openid 是唯一键别拿自增 ID 去判重校园二手交易系统的用户来源只有一个微信小程序登录。这就决定了用户表的核心字段不是 username而是 openid。openid 是微信用户在小程序维度下的唯一标识同一个用户在你的小程序里永远只有一个 openid。所以建表时要把 openid 设为唯一索引登录逻辑先查 openid 是否存在不存在就插入存在就更新昵称和头像这就是常见的“登录即注册”。用户表字段控制在八个以内CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL, nickname varchar(32) DEFAULT 微信用户, avatar_url varchar(255) DEFAULT , phone varchar(20) DEFAULT , campus varchar(32) DEFAULT , create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个细节要注意。第一字符集用 utf8mb4 而不是 utf8。MySQL 的 utf8 最多存三个字节而微信昵称里经常出现 Emoji四个字节存不进去插入时会报 “Incorrect string value”。第二create_time 和 update_time 用数据库默认值自动维护业务代码不用传时间字段。nickname 给了默认值“微信用户”避免前端没传昵称时写入 null。phone 和 campus 字段不是摆设。校园二手交易讲究同校线下交付详情页可以展示“仅限 X 校区当面交易”这比一个通用的聊天框更有校园场景说服力。答辩时被问“系统怎么保证交易安全”回答“支持同校当面验货”比讲一堆权限设计更有说服力。3.2 商品表与订单表状态字段用 tinyint 还是 varchar商品表是整个系统的中枢。它要承载发布、浏览、下架、卖出四种状态。我见过不少新手把 status 直接存成 varchar比如“selling”“sold”代码看着直观但查询和统计时效率不高而且容易拼写不一致。更稳的方案是用 tinyint 枚举0 在售、1 下架、2 已售Java 端再用常量类或枚举类做映射。商品表建法CREATE TABLE goods ( id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL, title varchar(64) NOT NULL, description text, price decimal(10,2) NOT NULL, original_price decimal(10,2) DEFAULT NULL, category tinyint NOT NULL DEFAULT 0, images varchar(1000) DEFAULT , status tinyint NOT NULL DEFAULT 0, view_count int NOT NULL DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category (category), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段说明重点看 images 和 price。二手商品需要多张实拍图常见做法是图片路径用逗号拼接存在一个字段里比如 “/uploads/1.jpg,/uploads/2.jpg”取出来用 split 转成数组不需要额外建一张图片表。price 用 decimal(10,2) 而不是 floatfloat 有精度损失标价 25.9 元存成 25.899999展示出来很尴尬。category 用数字 0 书籍教材、1 数码电子、2 生活用品、3 其他前端用 radio 单选框对应。订单表在二手交易里承担的功能和电商不一样它记录的是交易意向而不是真实支付。校园二手交易很少接微信支付一方面个人主体的小程序开不了微信支付另一方面线下当面交易更符合这个场景。订单表只需要记录买家、卖家、商品、价格、状态CREATE TABLE trade_order ( id int NOT NULL AUTO_INCREMENT, goods_id int NOT NULL, buyer_id int NOT NULL, seller_id int NOT NULL, amount decimal(10,2) NOT NULL, status tinyint NOT NULL DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_buyer_id (buyer_id), KEY idx_seller_id (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单状态建议这样定义0 已下单待联系、1 双方已联络、2 已完成、3 已取消。状态流转必须由后端控制不能让前端传任意数字。普通做法是在 service 层做状态判断比如 status0 时允许取消status1 时允许置为完成。这里有个加分操作创建订单前先把商品 status 置为 2 已售防止同个商品被两个人同时下单也就是“一物一卖”这个点老师很爱问。3.3 收藏表与图片路径存法小表反而最考习惯收藏表的 SQL 很简单容易踩坑的地方在联合唯一索引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), UNIQUE KEY uk_user_goods (user_id, goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;uk_user_goods 是防重复的关键。没有这个联合唯一索引用户快速点两下收藏表里会插入两条记录前端“是否已收藏”的判断也跟着出错。有了唯一索引重复插入会抛 Duplicate entry业务层捕获异常后返回“已经收藏过了”即可。图片路径存法这里有个分界线。小项目可以存在服务器本地目录通过 Spring Boot 静态资源映射或者 nginx 访问要上一点规模就接 OSS 对象存储。对毕设来说本地存储完全够用关键是路径规范。我习惯配置一个 upload-dir 属性数据库里只存相对路径 /uploads/xxx.jpg不要存完整的磁盘路径将来迁移服务器时不会出现路径失效。后端拼接访问地址时用当前的请求域名去拼这样从开发者工具切到真机调试时图片地址也能跟着变。4. 小程序端核心链路登录、发布、列表与详情的编码实现4.1 wx.login 换取 openid从前端 code 到后端入库的完整链路微信小程序登录的完整流程是小程序端调用 wx.login 拿到一个临时 code把 code 发给后端后端拿 code 向微信接口换取 openid 和 session_keyopenid 写入用户表同时给小程序端返回一个自定义登录态 token。这段逻辑不能放在纯前端完成因为 session_key 是敏感信息必须在服务端留存。后端 controller 很薄真正的逻辑在 serviceRestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public ResponseResultUserVO login(RequestBody LoginRequest request) { String code request.getCode(); if (StringUtils.isBlank(code)) { return ResponseResult.error(1002, code is empty); } UserVO user userService.loginByWechat(code); return ResponseResult.success(user); } }service 内部要依次做四件事调用微信接口换 openid、按 openid 查用户表、查不到就创建新用户、生成随机 token 并返回。token 可以直接用 UUID存到一张 token 表或 Redis 里小程序端每次请求都带上 token后端用拦截器校验。校验失败就返回 1001 未登录前端收到后跳转到登录页。小程序端代码wx.login({ success: (res) { if (res.code) { wx.request({ url: http://localhost:8080/api/user/login, method: POST, data: { code: res.code }, success: (resp) { if (resp.data.code 0) { wx.setStorageSync(token, resp.data.data.token); wx.setStorageSync(userInfo, resp.data.data); } } }); } } });这里有一个常见误用有人直接用 wx.getUserProfile 来登录。getUserProfile 只能拿到头像昵称拿不到 openid而且授权弹窗在最新版本的小程序里触发条件越来越严。登录入口必须是 wx.login头像昵称只是资料完善两个接口职责完全不同。常见做法是登录页放两个按钮“微信一键登录”和“完善资料”前者静默登录后者让用户填昵称和校区。注意获取微信绑定手机号的能力需要企业主体的小程序个人开发者申请的小程序没有这个接口权限。毕设里 phone 字段建议做成手动输入不要依赖微信的 getPhoneNumber。4.2 发布商品页radio 类别、图片上传与表单校验发布页面是交互最重的页面需要的元素包括类别选择、标题、描述、价格、图片、提交按钮。类别控件用微信小程序单选框组件 radiovalue 对应数据库 category 字段的数字view classform-item text分类/text radio-group bindchangeonCategoryChange label radio value0 checkedtrue / 书籍教材 /label label radio value1 / 数码电子 /label label radio value2 / 生活用品 /label label radio value3 / 其他 /label /radio-group /viewradio-group 的 bindchange 事件拿到的是字符串形式的 value提交时要转成 Number否则后端 Integer 类型接参会报错。radio 在真机和开发者工具上的样式渲染有差异文字和圆圈经常对不齐我一般会给 label 加 display:inline-flex 和 align-items:center 解决。这个属于典型的“微信小程序单选框样式适配”问题搜索关键词可以直接踩到答案。图片上传是最容易出岔子的环节。wx.chooseMedia 选择图片后临时路径在小程序重启后就会失效必须用 wx.uploadFile 把图片传到后端拿到服务器地址后再随表单提交wx.chooseMedia({ count: 3, mediaType: [image], sourceType: [album, camera], success: (res) { const filePath res.tempFiles[0].tempFilePath; wx.uploadFile({ url: http://localhost:8080/api/upload, filePath: filePath, name: file, success: (resp) { const data JSON.parse(resp.data); if (data.code 0) { this.setData({ imageUrls: [...this.data.imageUrls, data.data] }); } } }); } });这里有个非常隐蔽的坑wx.uploadFile 返回的 resp.data 是字符串而不是对象必须先 JSON.parse否则 data.code 永远取不到控制台却显示有返回值。count 参数控制最大选图数我一般限制三张超过五张会让请求体太过沉重详情页渲染也会明显变慢。表单校验要做两层。前端用简单的 if 判断提醒比如价格必须大于 0、标题不能为空后端用 javax.validation 的 NotBlank、DecimalMin 兜底再配合全局异常处理器把校验信息统一变成 code1002 的返回结构。这样用 Postman 直接绕开小程序调接口也进不来脏数据。4.3 商品列表的分页与触底加载页码设计里的边界商品列表页是用户打开后的第一屏不能一次把全表数据全查出来必须分页。分页参数一般叫 pageNum 和 pageSize后端返回总条数、总页数和当前页数据。最经典的翻车场景是“第三页查出来是空的但列表后面明明还有数据”原因往往是前端停止加载的条件写成了 data 为空而不是 pageNum 大于等于总页数。后端用 MyBatis-Plus 可以省去手写分页 SQLpublic PageResultGoodsVO getGoodsList(int pageNum, int pageSize, Integer category, Integer status) { LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(status ! null, Goods::getStatus, status) .eq(category ! null, Goods::getCategory, category) .orderByDesc(Goods::getCreateTime); PageGoods page goodsMapper.selectPage(new Page(pageNum, pageSize), wrapper); return toPageResult(page); }LambdaQueryWrapper 的 eq 第一个参数是布尔条件status 或 category 为 null 时自动忽略这个条件前端不传分类也能查全量。orderByDesc 按发布时间倒序让最新商品排前面。selectPage 返回的 Page 对象里有 total、pages、records前端判断停止加载的条件是 pageNum pages而不是 records 为空。前端触底加载用 onReachBottomonReachBottom() { const { pageNum, pages, loading } this.data; if (pageNum pages || loading) return; this.setData({ loading: true }); const nextPage pageNum 1; wx.request({ url: http://localhost:8080/api/goods/list, data: { pageNum: nextPage, pageSize: 10 }, success: (res) { this.setData({ goodsList: this.data.goodsList.concat(res.data.data.records), pageNum: nextPage, pages: res.data.data.pages, loading: false }); } }); }loading 标志位用来防止重复请求。没有它用户快速滑动时 onReachBottom 会被连续触发第一个请求还没返回又发第二个会出现同一批数据被 append 两次的页面错乱。另一个容易忽略的是 onReachBottom 在页面内容不足一屏时不会触发所以 onLoad 加载第一页后要在 onReady 里判断内容高度不足一屏就继续请求下一页直到填满或到达总页数。5. 微信小程序联调避坑登录、图片、数据库连接的五次翻车5.1 现象一开发者工具能登录真机预览一直转圈原因大概率是 wx.request 的域名校验。微信开发者工具有一个“不校验合法域名”的开关勾选后可以直接请求 http://localhost:8080。但在真机上这个开关不生效小程序不允许请求非 HTTPS 域名除非在小程序后台的 request 合法域名里配置。毕设阶段没有条件配 HTTPS 的话可以勾选“不校验合法域名”后重新预览但要注意每次重新编译后都要再勾选一次。解决方式更简单把后端接口地址从 localhost 换成本机局域网 IP比如 http://192.168.1.100:8080手机和电脑连同一个 Wi-Fi真机预览就能通。注意这个 IP 不是固定的电脑重启或换网络后要重新确认最省事的做法是专门写一个 config.js把 baseUrl 集中放一个变量换环境只改这一个文件。5.2 现象二图片在开发者工具能显示真机全是裂图裂图说明图片 URL 在手机网络下访问不到。测试阶段图片存在电脑本地目录返回的 URL 是 http://localhost:8080/uploads/xxx.jpg。开发者工具运行在电脑上localhost 就是这台电脑当然能显示手机上的 localhost 指向手机自己自然裂掉。解决办法是让前端拼图片地址时改用电脑的局域网 IP。更规范的做法是图片上传成功后后端只返回相对路径前端在展示时用 config.js 里的全局 baseUrl 去拼接。这样以后换服务器、换域名只需要改 baseUrl不需要回头批量更新数据库里的绝对路径算是一个值得养成的习惯。5.3 现象三后端启动成功第一次请求数据库报 CommunicationsExceptionCommunicationsException 最常见的两个诱因一是时区没配置二是 MySQL 8.x 的认证插件不兼容。MySQL 8 默认的 caching_sha2_password 认证插件在旧版本驱动下不被识别连接会被掐断这个报错容易被当成玄学其实排查方向很明确。解决分两步第一步在 MySQL 里把该用户的认证方式改成 mysql_native_password或者升级驱动到 8.x 并在连接串加 allowPublicKeyRetrievaltrue第二步确认连接串带了 serverTimezoneAsia/Shanghai。排错顺序是先看异常栈出现在驱动层还是 Spring 层驱动层多半是认证插件问题Spring 层多半是依赖版本或配置加载问题。数据库连接这个东西参数差一个都跑不起来配置检查清单要比业务代码优先。5.4 现象四改了后端代码小程序里还是旧数据这个问题的原因多半不在小程序而在后端启动方式。Spring Boot 项目用 java -jar 启动的话改完代码必须重新打包再启动否则生效的仍是旧 jar。前后端分离开发时我建议引入 spring-boot-devtools改完代码自动重启省去手动敲命令的疲劳感。另一种情况是小程序端把列表数据缓存到了 storage它的缓存又没有过期机制导致每次进入页面都是旧数据。商品列表这种实时性要求不强的数据可以做缓存但一定要有拉取时机比如下拉刷新时强制更新。我习惯列表页每次都从接口拉取不做本地缓存只有 token 和个人资料才存 storage这样最不容易出现“前后端数据对不上”的诡异问题。5.5 现象五演示视频里的数据太假答辩被追问“真实用户是多少”这个问题不在代码逻辑里但在答辩中很致命。很多同学为了演示效果往数据库里塞“iPhone 15 卖 100 元”这样的假数据老师一眼就看穿是编的。校园二手交易的合理数据应该符合学生消费水平教材、台灯、自行车、风扇价格从几块到几百块商品图片最好是生活中拍的实景图不要用电商官网的宣传图。演示视频里的人设也要合理。用一个“2019 级张同学”的账号发布三件物品另一个账号购买或者收藏形成一条完整的故事线。被问到“这个系统有没有人用过”时回答“开发阶段邀请了同班同学做了模拟交易测试”比虚报用户量更稳。数据造假是毕设答辩里的头号翻车点宁可少展示几个功能也要让展示出来的数据站得住脚。6. 录好演示视频、答好辩让系统看起来是被真实使用过的6.1 按真实使用顺序录制而不是按菜单功能录制演示视频是评审老师认识你系统的第一窗口。最常见的错误是打开小程序后从 tab 栏第一个页面挨个划一遍每个页面停留两秒就切走看完不知道这个系统到底能干什么。更合理的录制顺序是模拟一个真实用户先微信登录浏览首页二手列表进详情页看物品描述和卖家校区然后发布一件自己的物品再回个人中心查看发布记录和收藏数。整个过程控制在三到四分钟每个操作之间停留一两秒让老师看清数据加载和页面跳转。视频开头说一句“这是一个校园二手交易系统主要解决同校学生闲置物品流转问题”比放十秒加载动画有用。6.2 答辩前的自测清单自测按老师大概率会问的点来准备。第一项是数据一致性数据库里的商品数、订单数、收藏数能相互对上不要出现某商品状态是“已售”但订单表查不到任何记录。第二项是异常输入发布一个标题为空或价格为 0 的商品前端有没有提示后端在 Postman 里的返回是不是正常的 1002。第三项是会话过期token 失效后访问受保护接口前端是否跳回登录页。第四项是后端服务停掉后小程序会不会优雅报错而不是闪退。第五项是演示环境整洁度提前清理测试产生的脏数据不要出现同一个物品被发布五遍的记录。6.3 两个值得做的扩展点如果还有时间我建议优先加两个低成本高性价比的功能。第一个是关键字搜索goods 表 title 字段加一个 LIKE 查询小程序首页加一个搜索框前后端改动量都很小但演示效果非常直观。第二个是按分类筛选后端已经留了 category 参数前端加一个 picker 组件联动请求就能实现。这两个功能做完功能完整度会明显比同题目的其他同学高。做这类选题最大的感受是不要在技术选型上花过多时间犹豫把 Spring Boot MySQL 原生小程序这条路走通再往里填业务比反复换框架要踏实得多。装环境、调接口、截图排错是绕不开的过程翻一次车就少踩一个坑最后那句“能用”比“完美”更实际。希望帮到你。本文还有配套的精品资源点击获取