ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序校园二手交易平台毕业设计:数据库表设计与全栈开发实战

微信小程序校园二手交易平台毕业设计:数据库表设计与全栈开发实战 简介这份资源是面向计算机相关专业毕业设计学生与项目实战学习者的微信小程序校园二手交易平台完整源码包包含服务端与小程序端代码及配套数据库适合作为毕设选题或课程设计参考。项目经导师指导并通过评审本地编译运行无误难度适中助教老师已审定内容可直接用于学习与二次开发。压缩包共127个文件约2.31MB涵盖Java后端源码、XML配置、JS与JSON小程序逻辑、WXSS与WXML页面样式、SQL建库脚本以及PNG、JPG界面素材和YML、properties等配置文件结构完整、层次清晰。目前已有415人学习下载。读者可从中获得一套可运行的校园二手交易系统实现方案包括用户信息、商品店铺、图片上传、志愿服务等模块的接口与控制器代码便于理解前后端交互流程、数据库表设计与小程序页面组织方式也能为撰写论文、准备答辩和排查运行问题提供直接参考。1. 从一份能跑通的校园二手交易小程序说起每年毕业季总有一批同学卡在同一个地方想做一个微信小程序校园二手交易平台当毕业设计代码写了一半数据库表建得七零八落答辩前一周还在改登录逻辑。这个标题背后其实是一套完整的、可复现的工程方案——微信小程序前端 后端接口 关系型数据库三块拼起来就是一个能演示、能答辩、能继续迭代的系统。它解决的核心问题是让校内学生发布闲置物品、按分类浏览、下单或留言、管理自己的商品同时让管理员能审核和下架违规内容。适合谁适合正在做计算机毕业设计、需要一套结构清晰且能讲清楚技术选型的同学也适合想练手微信小程序全栈开发的新手。我见过太多人一上来就堆页面结果数据库连表都理不顺最后功能全靠假数据撑着答辩老师一问「订单和用户怎么关联的」就露馅。所以这篇不聊虚的从表结构到接口到页面一步步把这条链路走通。2. 先把数据库表设计对校园二手交易平台的六张核心表2.1 为什么表结构决定了这个项目能不能讲清楚很多同学做毕业设计习惯先画页面再想数据怎么存这是典型的翻车起点。校园二手交易平台的数据关系其实不复杂但如果你一开始不把用户、商品、分类、订单、留言、收藏这几条线理清楚后面写接口时就会不断加字段、改类型最后数据库里一堆冗余列答辩时自己都说不清每张表干嘛的。我一般会先把实体关系画在纸上一个用户能发布多个商品一个商品属于一个分类一个买家能对多个商品下订单一个商品能有多条留言一个用户能收藏多个商品。这五组关系定下来六张表就出来了用户表、商品表、分类表、订单表、留言表、收藏表。其中订单表是连接买家和商品的中间表收藏表是连接用户和商品的中间表这两张表最容易漏掉外键约束导致后面查「我买过的商品」时数据对不上。2.2 建表 SQL 与字段说明下面这套建表语句是我在 MySQL 8.0 上跑通的版本字符集用 utf8mb4方便存 emoji 和特殊符号。每张表都加了 create_time 和 update_time方便后面做「最新发布」排序和「我的发布」列表。-- 用户表存微信 openid 和基础资料 CREATE TABLE user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 用户主键, openid VARCHAR(64) NOT NULL COMMENT 微信 openid唯一标识, nickname VARCHAR(64) DEFAULT COMMENT 昵称, avatar_url VARCHAR(255) DEFAULT COMMENT 头像地址, phone VARCHAR(20) DEFAULT COMMENT 联系电话, role TINYINT DEFAULT 0 COMMENT 0 普通用户 1 管理员, 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 COMMENT用户表; -- 分类表二手书、电子产品、生活用品等 CREATE TABLE category ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(32) NOT NULL COMMENT 分类名称, sort INT DEFAULT 0 COMMENT 排序值越小越靠前, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表; -- 商品表核心表关联发布者和分类 CREATE TABLE goods ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL COMMENT 发布者 id, category_id INT UNSIGNED NOT NULL COMMENT 分类 id, title VARCHAR(128) NOT NULL COMMENT 商品标题, description TEXT COMMENT 详细描述, price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 价格, cover_img VARCHAR(255) DEFAULT COMMENT 封面图, status TINYINT DEFAULT 1 COMMENT 1 在售 2 已售 3 下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 订单表买家对商品下单 CREATE TABLE order ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, goods_id INT UNSIGNED NOT NULL COMMENT 商品 id, buyer_id INT UNSIGNED NOT NULL COMMENT 买家 id, seller_id INT UNSIGNED NOT NULL COMMENT 卖家 id, amount DECIMAL(10,2) NOT NULL COMMENT 成交金额, status TINYINT DEFAULT 1 COMMENT 1 待确认 2 已完成 3 已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_buyer (buyer_id), KEY idx_goods (goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 留言表商品下的留言 CREATE TABLE message ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, goods_id INT UNSIGNED NOT NULL, user_id INT UNSIGNED NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_goods (goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT留言表; -- 收藏表用户收藏商品 CREATE TABLE favorite ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL, goods_id INT UNSIGNED NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_goods (user_id,goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表;这段 SQL 里几个关键点值得单独说。第一user 表的 openid 加了唯一索引因为微信登录时同一个用户可能多次授权没有唯一约束就会插重复数据。第二goods 表的 status 字段用 TINYINT 而不是字符串查询时WHERE status 1比WHERE status 在售快也省空间。第三order 表同时存了 buyer_id 和 seller_id这样查「我卖出的」和「我买到的」都不用再连 goods 表去取 user_id少一次 join。第四favorite 表的联合唯一索引防止同一个用户重复收藏同一个商品插入时用INSERT IGNORE就能静默跳过重复。这些细节答辩时能讲出来比你说「我用了 MySQL」有说服力得多。2.3 初始化分类数据和测试数据表建好之后别急着写接口先塞几条分类数据进去不然前端下拉框是空的联调时容易误判成接口挂了。INSERT INTO category (name, sort) VALUES (教材书籍, 1), (电子数码, 2), (生活用品, 3), (运动户外, 4), (美妆护肤, 5); -- 插一个测试用户和商品方便前端先跑通 INSERT INTO user (openid, nickname, role) VALUES (test_openid_001, 测试同学, 0); INSERT INTO goods (user_id, category_id, title, description, price, status) VALUES (1, 1, 高等数学教材 第七版, 九成新无笔记, 15.00, 1);测试数据不用多够你验证「列表能查到、详情能打开」就行。后面接口写完再批量造数据也不迟。3. 微信小程序端登录、列表、发布三个页面的最小实现3.1 用 wx.login 换 openid 的完整链路微信小程序的登录不是传统账号密码而是前端调wx.login拿到临时 code传给后端后端拿 code 去微信接口换 openid 和 session_key。很多同学卡在这一步是因为不知道后端要用 appid 和 secret 去请求微信的jscode2session接口。我一般会在后端封装一个 login 接口前端只负责传 code。// 小程序端pages/login/login.js Page({ data: { userInfo: null }, onLoad() { // 页面加载时静默登录 wx.login({ success: (res) { if (res.code) { // 把 code 发给自己的后端 wx.request({ url: https://your-domain.com/api/login, method: POST, data: { code: res.code }, success: (resp) { // 后端返回自定义 token 和用户信息 const { token, userInfo } resp.data; wx.setStorageSync(token, token); this.setData({ userInfo }); }, fail: () { wx.showToast({ title: 登录失败, icon: none }); } }); } } }); } });这段代码的逻辑是wx.login拿到 code 后立刻发给自己的后端后端用 code 换 openid再生成一个自定义 token 返回给小程序小程序存到 storage 里后续请求都带这个 token。参数上要注意wx.request的 url 必须是 https且在小程序后台配置了合法域名否则真机上会报「不在以下 request 合法域名列表中」。开发阶段可以在开发者工具里勾选「不校验合法域名」但答辩演示前一定要配好不然现场翻车很尴尬。3.2 商品列表页的下拉刷新与分页列表页是用户打开小程序看到的第一屏性能直接影响体验。我一般用onReachBottom做上拉加载用onPullDownRefresh做下拉刷新每页 10 条。// pages/index/index.js Page({ data: { goodsList: [], page: 1, pageSize: 10, hasMore: true }, onLoad() { this.loadGoods(); }, onPullDownRefresh() { this.setData({ page: 1, goodsList: [], hasMore: true }); this.loadGoods(() wx.stopPullDownRefresh()); }, onReachBottom() { if (!this.data.hasMore) return; this.setData({ page: this.data.page 1 }); this.loadGoods(); }, loadGoods(callback) { const { page, pageSize } this.data; wx.request({ url: https://your-domain.com/api/goods/list, data: { page, pageSize }, success: (res) { const list res.data.list || []; this.setData({ goodsList: page 1 ? list : this.data.goodsList.concat(list), hasMore: list.length pageSize }); callback callback(); } }); } });这里的关键参数是 page 和 pageSize后端用LIMIT (page-1)*pageSize, pageSize来查。hasMore的判断逻辑是「本次返回条数等于 pageSize 就认为还有下一页」简单但够用。注意下拉刷新时要重置 page 和 goodsList否则会出现数据重复。我见过有人忘了重置结果下拉一次列表里同样的商品出现两遍排查了半天以为是后端去重没做。3.3 发布商品页的图片上传与表单校验发布页涉及图片上传微信小程序用wx.chooseImage选图再用wx.uploadFile传到后端。表单校验至少要做标题非空、价格是数字、分类已选这三项。// pages/publish/publish.js Page({ data: { title: , price: , categoryId: 0, coverImg: }, chooseImage() { wx.chooseImage({ count: 1, success: (res) { const tempPath res.tempFilePaths[0]; wx.uploadFile({ url: https://your-domain.com/api/upload, filePath: tempPath, name: file, header: { token: wx.getStorageSync(token) }, success: (uploadRes) { const data JSON.parse(uploadRes.data); this.setData({ coverImg: data.url }); } }); } }); }, submit() { const { title, price, categoryId, coverImg } this.data; if (!title.trim()) return wx.showToast({ title: 请填标题, icon: none }); if (!price || isNaN(price)) return wx.showToast({ title: 价格不对, icon: none }); if (!categoryId) return wx.showToast({ title: 请选分类, icon: none }); wx.request({ url: https://your-domain.com/api/goods/create, method: POST, header: { token: wx.getStorageSync(token) }, data: { title, price: Number(price), categoryId, coverImg }, success: () { wx.showToast({ title: 发布成功 }); wx.navigateBack(); } }); } });上传接口返回的 url 要存到 goods 表的 cover_img 字段。这里有个坑wx.uploadFile返回的 data 是字符串必须JSON.parse一次直接当对象用会报 undefined。另外价格字段前端传字符串后端记得转成 DECIMAL 再入库不然 MySQL 会按字符串处理排序时「10」会排在「9」前面。4. 后端接口与数据库增删改查把六张表串起来4.1 商品列表和详情的查询逻辑后端我一般用 Node.js Express mysql2轻量且和前端同语言调试方便。商品列表接口要支持按分类筛选和关键词搜索SQL 里用动态拼接。// routes/goods.js const express require(express); const router express.Router(); const db require(../db); // 商品列表支持分类和关键词 router.get(/list, async (req, res) { const page parseInt(req.query.page) || 1; const pageSize parseInt(req.query.pageSize) || 10; const categoryId req.query.categoryId; const keyword req.query.keyword; let sql SELECT g.*, u.nickname FROM goods g LEFT JOIN user u ON g.user_id u.id WHERE g.status 1; const params []; if (categoryId) { sql AND g.category_id ?; params.push(categoryId); } if (keyword) { sql AND g.title LIKE ?; params.push(%${keyword}%); } sql ORDER BY g.create_time DESC LIMIT ? OFFSET ?; params.push(pageSize, (page - 1) * pageSize); const [rows] await db.query(sql, params); res.json({ list: rows }); }); // 商品详情同时返回留言 router.get(/detail/:id, async (req, res) { const [goods] await db.query( SELECT g.*, u.nickname, u.avatar_url FROM goods g LEFT JOIN user u ON g.user_id u.id WHERE g.id ?, [req.params.id] ); if (!goods.length) return res.status(404).json({ msg: 商品不存在 }); const [messages] await db.query( SELECT m.*, u.nickname FROM message m LEFT JOIN user u ON m.user_id u.id WHERE m.goods_id ? ORDER BY m.create_time DESC, [req.params.id] ); res.json({ goods: goods[0], messages }); }); module.exports router;列表查询用 LEFT JOIN 把发布者昵称带出来前端就不用再发一次请求。分页用 LIMIT OFFSET注意 OFFSET 在数据量大时会变慢但毕业设计的数据量完全够用。详情接口把留言一起返回减少一次请求这是常见的接口聚合做法。参数上categoryId 和 keyword 都是可选的不传就不拼对应条件这样一套接口能覆盖首页、分类页、搜索页三个场景。4.2 下单和收藏的写入操作下单和收藏都是写操作重点在于防重复和状态校验。下单前要检查商品是否还在售收藏前要检查是否已收藏。// 下单检查商品状态写入订单更新商品为已售 router.post(/order/create, async (req, res) { const { goodsId } req.body; const buyerId req.userId; // 从 token 解析 const [goods] await db.query(SELECT * FROM goods WHERE id ? AND status 1, [goodsId]); if (!goods.length) return res.status(400).json({ msg: 商品已售或不存在 }); const g goods[0]; if (g.user_id buyerId) return res.status(400).json({ msg: 不能买自己的商品 }); await db.query( INSERT INTO order (goods_id, buyer_id, seller_id, amount, status) VALUES (?, ?, ?, ?, 1), [goodsId, buyerId, g.user_id, g.price] ); await db.query(UPDATE goods SET status 2 WHERE id ?, [goodsId]); res.json({ msg: 下单成功 }); }); // 收藏用 INSERT IGNORE 防重复 router.post(/favorite/add, async (req, res) { const { goodsId } req.body; await db.query(INSERT IGNORE INTO favorite (user_id, goods_id) VALUES (?, ?), [req.userId, goodsId]); res.json({ msg: ok }); });下单接口里先查商品状态再判断是不是自己发布的最后写订单并更新商品状态。这三步缺一不可少了状态检查会出现「同一件商品被两个人同时下单」的超卖问题。收藏用INSERT IGNORE配合联合唯一索引重复收藏不会报错也不会插重复数据。注意 order 是 MySQL 关键字建表和查询时都要加反引号不然会报语法错误这个坑我踩过不止一次。4.3 管理员审核与商品下架管理员接口和普通用户接口要分开用 role 字段做权限判断。中间件里解析 token 后查 user 表role 不等于 1 就返回 403。// 管理员下架商品 router.post(/admin/goods/off, async (req, res) { if (req.role ! 1) return res.status(403).json({ msg: 无权限 }); const { goodsId } req.body; await db.query(UPDATE goods SET status 3 WHERE id ?, [goodsId]); res.json({ msg: 已下架 }); });权限中间件建议单独写一个auth.js在需要鉴权的路由前挂上。管理员账号不用单独建表直接在 user 表里把某条记录的 role 改成 1 就行答辩演示时用这个账号登录能看到下架按钮。5. 避坑与排查校园二手交易小程序最容易翻车的五个地方5.1 真机预览时接口全部 404现象开发者工具里一切正常用手机扫码预览时所有请求都失败控制台报「不在以下 request 合法域名列表中」。原因小程序的网络请求域名必须在微信公众平台后台配置且必须是 https。解决登录小程序后台在「开发管理 → 开发设置 → 服务器域名」里把后端域名加进去同时确保域名有 SSL 证书。开发阶段可以临时勾选「不校验合法域名」但答辩前一定要配好。5.2 图片上传后显示裂图现象发布商品时图片上传成功但列表页显示的是裂图。原因后端返回的 url 是相对路径或者上传目录没有做静态资源映射。解决上传接口返回完整 url后端用express.static把上传目录暴露出来路径类似https://your-domain.com/uploads/xxx.jpg。另外小程序 image 组件的 src 不支持本地路径必须是网络地址。5.3 分页加载出现重复数据现象上拉加载第二页时列表里出现了第一页已经展示过的商品。原因下拉刷新时没有重置 page 和 goodsList或者后端排序字段不唯一导致分页错乱。解决下拉刷新时把 page 重置为 1、goodsList 清空后端 ORDER BY 加上 id 作为第二排序字段保证顺序稳定。5.4 下单后商品状态没更新现象用户下单成功但商品列表里该商品还在售别人还能继续下单。原因下单接口只写了 order 表忘了更新 goods 表的 status。解决把「写订单」和「改商品状态」放在同一个事务里用db.beginTransaction()和commit()保证要么都成功要么都回滚。5.5 微信登录换不到 openid现象后端调微信 jscode2session 接口返回 errcode 40029 或 40163。原因code 只能用一次或者 appid/secret 配错了。解决确保每次登录都用新的 code不要把 code 缓存起来复用检查后端配置的 appid 和 secret 是否和小程序后台一致。40029 通常是 code 无效40163 是 code 已被使用。6. 让这套源码在答辩时加分三个进阶技巧第一个技巧是给商品列表加缓存。校园二手交易平台的首页访问频率最高但商品数据变化不频繁可以在后端用内存缓存把列表结果存 30 秒减少数据库压力。实现很简单用一个 Map 存 key 和过期时间请求进来先查缓存命中就直接返回。答辩时你可以说「我做了接口层的缓存优化首页 QPS 从 200 提升到 800」这个数字不用精确但思路要讲清楚。第二个技巧是给数据库加慢查询日志。在 MySQL 配置文件里把slow_query_log打开long_query_time设为 1 秒跑一遍所有接口看看哪些 SQL 超过 1 秒。我一般会重点看商品列表的 LIKE 查询和订单表的 join如果慢了就加索引。比如goods表的title字段如果要做模糊搜索可以考虑加全文索引但数据量小的时候普通索引就够。第三个技巧是准备一份接口文档。答辩老师不一定懂技术细节但看到你有清晰的接口文档会觉得你工程素养不错。用 Markdown 写一个表格列出每个接口的路径、方法、参数、返回示例十来个接口一页纸就够。下面是我常用的格式接口方法参数返回/api/loginPOSTcodetoken, userInfo/api/goods/listGETpage, pageSize, categoryIdlist/api/goods/detail/:idGETidgoods, messages/api/goods/createPOSTtitle, price, categoryId, coverImgmsg/api/order/createPOSTgoodsIdmsg/api/favorite/addPOSTgoodsIdmsg最后说个我自己的习惯每次改完代码先在小程序开发者工具里把「清除缓存 → 重新编译」走一遍再真机预览一次。很多玄学问题都是缓存导致的清一下就好。这套方案我前后搭过三次每次都能在两周内跑通完整流程剩下的时间用来打磨页面和准备答辩话术。希望帮到你。本文还有配套的精品资源点击获取
返回列表