简介:这份资源是面向计算机相关专业学生与小程序开发初学者的毕业设计/课程设计完整项目包,主题为基于微信小程序的二手车交易平台。项目实现管理员对首页、个人中心、汽车分类与品牌、汽车信息、用户、留言板、系统及订单等模块的管理,并配套完整前后端源码、MySQL数据库脚本与说明文档,适合需要独立完成毕设或课程作业、希望参考真实业务场景的开发者。压缩包共1241个文件,约47.39MB,涵盖Java后端源码、Vue与原生小程序页面、js逻辑脚本、json配置、wxss与wxml样式模板、sql建库脚本及png、svg等界面素材,另含bat启动脚本与项目说明文档,目录结构清晰,便于按模块查阅与二次开发。目前已有91人学习下载,可作为从需求分析、数据库设计到前后端联调的完整参考方案,帮助读者快速理解二手车交易类小程序的实现思路与工程组织方式。
1. 二手车交易小程序:从一台闲置车到一笔线上订单,中间隔着多少坑
二手车这门生意,线下最怕两件事:车况不透明、信息不对称。买家怕买到事故车,卖家怕被压价,车商怕客户流失。把这条链路搬到微信小程序上,本质是用一套前后端系统把「发布车源—浏览筛选—预约看车—下单/意向—后台管理」串起来。这个标题里的「基于微信小程序的二手车交易平台」,核心就是一套完整的前后端工程:小程序端负责展示与交互,服务端负责业务逻辑与数据落库,MySQL 负责持久化,再配一份说明文档和论文(LW)交付。它适合三类人:正在做毕业设计、需要一套能跑通全流程的参考工程的学生;想快速搭一个垂直交易类小程序原型的开发者;以及需要理解「小程序 + 后端 + 数据库」如何协作的入门工程师。下面我按实际落地顺序,把选型、建表、接口、联调和踩坑一次讲透。
2. 技术选型与工程结构:为什么是这套组合而不是别的
2.1 小程序端 + 自建后端 + MySQL 的分工边界
先明确一件事:微信小程序本身不能直接连 MySQL。小程序运行在微信客户端里,只能通过wx.request调用 HTTPS 接口,所有数据库操作必须由后端完成。所以这套工程天然是「前后端分离」结构,常见做法是:
- 小程序端:页面渲染、表单校验、调用接口、本地缓存登录态。
- 后端服务:接收请求、校验参数、处理业务、读写数据库、返回 JSON。
- MySQL:存用户、车源、订单、收藏、留言等结构化数据。
为什么不用云开发直接省掉后端?云开发确实快,但毕业设计通常要求体现「前后端分离」和「数据库设计」能力,而且自建后端更容易讲清楚接口鉴权、事务、索引这些点。所以选「小程序 + 自建后端 + MySQL」是更稳妥的方案。
后端语言常见有 Node.js(Express/Koa)、Java(Spring Boot)、Python(Flask/Django)。如果团队里没人熟悉 Java,Node.js 上手最快,和小程序的 JSON 交互也最顺。下面以 Node.js + Express + MySQL 为例,Java 版本逻辑一致,只是写法不同。
2.2 目录结构怎么划分才不混乱
一个能交付的工程,目录必须清晰。我一般会这样分:
used-car-miniapp/ ├── miniprogram/ # 小程序端 │ ├── pages/ # 页面 │ │ ├── index/ # 首页车源列表 │ │ ├── detail/ # 车源详情 │ │ ├── publish/ # 发布车源 │ │ ├── order/ # 订单 │ │ └── mine/ # 个人中心 │ ├── utils/ # 请求封装、格式化 │ └── app.js ├── server/ # 后端 │ ├── routes/ # 路由 │ ├── controllers/ # 业务逻辑 │ ├── models/ # 数据库操作 │ ├── middleware/ # 鉴权、错误处理 │ └── app.js └── docs/ # 说明文档、论文这样分的好处是:小程序端和后端完全解耦,接口一改,两边各自调整,不会互相污染。毕业设计答辩时,老师一看目录就知道你懂工程化。
2.3 数据库表设计:五张核心表撑起整个平台
二手车平台的数据关系不复杂,但字段要想清楚。核心表如下:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| user | 用户 | id, openid, nickname, phone, role |
| car | 车源 | id, user_id, title, brand, price, mileage, year, status |
| car_image | 车源图片 | id, car_id, url, sort |
| order | 订单/意向 | id, car_id, buyer_id, seller_id, status, create_time |
| favorite | 收藏 | id, user_id, car_id, create_time |
建表 SQL 示例:
CREATE TABLE `car` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL COMMENT '发布者', `title` VARCHAR(100) NOT NULL COMMENT '车源标题', `brand` VARCHAR(50) DEFAULT NULL COMMENT '品牌', `price` DECIMAL(10,2) NOT NULL COMMENT '价格', `mileage` DECIMAL(8,2) DEFAULT NULL COMMENT '里程(万公里)', `year` INT DEFAULT NULL COMMENT '上牌年份', `status` TINYINT DEFAULT 1 COMMENT '1在售 2已售 3下架', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;逻辑说明:user_id建索引是因为「我的车源」查询频繁;status建索引是因为首页只查在售车源。price用DECIMAL而不是FLOAT,避免金额精度丢失,这是血泪经验。utf8mb4是为了支持 emoji 和生僻字,别用utf8。
参数说明:mileage用万公里为单位,前端展示时再拼单位;year存整数,方便按年份筛选。create_time默认当前时间,省去后端手动赋值。
3. 后端接口与小程序联调:从登录到下单的完整链路
3.1 微信登录换 openid 的正确姿势
小程序登录不能自己造账号密码,标准流程是:小程序调wx.login拿code,后端拿code+appid+secret去微信接口换openid和session_key,然后后端生成自己的 token 返回给小程序。
后端示例(Node.js):
// routes/auth.js const express = require('express'); const axios = require('axios'); const router = express.Router(); const jwt = require('jsonwebtoken'); router.post('/login', async (req, res) => { const { code } = req.body; if (!code) return res.status(400).json({ msg: '缺少 code' }); // 用 code 换 openid const url = 'https://api.weixin.qq.com/sns/jscode2session'; const { data } = await axios.get(url, { params: { appid: process.env.APPID, secret: process.env.SECRET, js_code: code, grant_type: 'authorization_code' } }); if (data.errcode) return res.status(400).json({ msg: data.errmsg }); // 查库或新建用户 let user = await User.findByOpenid(data.openid); if (!user) user = await User.create({ openid: data.openid }); // 签发 token const token = jwt.sign({ uid: user.id }, process.env.JWT_SECRET, { expiresIn: '7d' }); res.json({ token, userInfo: { id: user.id, nickname: user.nickname } }); }); module.exports = router;逻辑说明:code只能用一次,用完即废,所以不要缓存。openid是用户唯一标识,存库时加唯一索引。session_key不要返回给前端,它只用于后端解密敏感数据。token 用 JWT,有效期 7 天,前端存wx.setStorageSync。
参数说明:APPID和SECRET放环境变量,别硬编码在代码里,这是安全底线。expiresIn按需调整,毕业设计 7 天够用。
3.2 车源列表分页与筛选接口
首页车源列表要支持分页、品牌筛选、价格区间。后端接口:
// routes/car.js router.get('/list', async (req, res) => { const { page = 1, size = 10, brand, minPrice, maxPrice } = req.query; const offset = (page - 1) * size; let where = 'WHERE status = 1'; const params = []; if (brand) { where += ' AND brand = ?'; params.push(brand); } if (minPrice) { where += ' AND price >= ?'; params.push(minPrice); } if (maxPrice) { where += ' AND price <= ?'; params.push(maxPrice); } const sql = `SELECT * FROM car ${where} ORDER BY create_time DESC LIMIT ? OFFSET ?`; params.push(Number(size), Number(offset)); const [rows] = await db.query(sql, params); const [[{ total }]] = await db.query(`SELECT COUNT(*) AS total FROM car ${where}`, params.slice(0, -2)); res.json({ list: rows, total, page: Number(page) }); });逻辑说明:LIMIT ? OFFSET ?是分页标准写法,params.slice(0, -2)去掉分页参数再查总数。ORDER BY create_time DESC保证最新车源在前。前端拿到total后判断是否还有下一页。
参数说明:page从 1 开始,size默认 10,别设太大,小程序一屏放不下。brand精确匹配,如果要模糊搜索改成LIKE。
小程序端请求封装:
// utils/request.js const BASE_URL = 'https://your-domain.com/api'; function request(options) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success: (res) => { if (res.statusCode === 200) resolve(res.data); else reject(res.data); }, fail: reject }); }); } module.exports = { request };逻辑说明:统一封装wx.request,自动带 token,统一处理成功失败。这样页面里只写request({ url: '/car/list', data: { page: 1 } }),干净利落。
3.3 发布车源与图片上传
发布车源涉及图片上传,小程序用wx.chooseImage选图,再wx.uploadFile传到后端,后端存到本地或对象存储,返回 URL 存库。
// pages/publish/publish.js Page({ data: { images: [] }, chooseImage() { wx.chooseImage({ count: 9 - this.data.images.length, success: (res) => { this.setData({ images: this.data.images.concat(res.tempFilePaths) }); } }); }, async submit() { const { images } = this.data; const urls = []; for (const path of images) { const res = await wx.uploadFile({ url: 'https://your-domain.com/api/upload', filePath: path, name: 'file', header: { 'Authorization': wx.getStorageSync('token') } }); urls.push(JSON.parse(res.data).url); } await request({ url: '/car/create', method: 'POST', data: { ...this.data, images: urls } }); wx.showToast({ title: '发布成功' }); } });逻辑说明:先传图再提交表单,避免表单提交时图片还没传完。count限制 9 张,和微信朋友圈一致。后端/upload接口用multer接收文件,存到uploads/目录,返回可访问 URL。
参数说明:name: 'file'要和后端multer的字段名一致,否则收不到文件。图片 URL 存库时只存相对路径,前端拼接域名,方便换域名。
4. 避坑与排查:那些让我加班到凌晨的问题
4.1 小程序请求域名必须 HTTPS 且备案
现象:本地调试正常,真机预览请求失败,报「不在以下 request 合法域名列表中」。
原因:微信小程序正式环境要求所有请求域名必须 HTTPS,且在小程序后台配置合法域名。HTTP 和未备案域名一律拒绝。
解决:开发阶段在开发者工具「详情—本地设置」勾选「不校验合法域名」,真机调试和上线必须换 HTTPS 域名并备案。别想着绕过,这是硬性规定。
4.2 MySQL 连接数耗尽导致接口超时
现象:并发一高,接口报ER_CON_COUNT_ERROR: Too many connections。
原因:每次请求都新建数据库连接,没释放。或者连接池配置太小。
解决:用连接池,mysql2的createPool,connectionLimit设 10~20。每次查询用pool.query,它会自动归还连接。别用createConnection手动开关,容易忘。
const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASS, database: 'used_car', connectionLimit: 15, waitForConnections: true });4.3 金额用浮点数导致对不上账
现象:车价 12.99 万,前端显示 12.989999999。
原因:FLOAT和DOUBLE有精度问题,金额计算会累积误差。
解决:数据库用DECIMAL(10,2),后端计算用整数分或decimal.js,前端展示再除 100。别用toFixed糊弄,那只是显示层掩盖问题。
4.4 图片上传后路径 404
现象:上传成功,但小程序里图片显示不出来。
原因:后端存了文件,但没配静态资源目录,或者路径拼错。
解决:Express 里加app.use('/uploads', express.static('uploads')),返回 URL 用/uploads/xxx.jpg,前端拼域名。注意uploads目录要有写权限,Linux 下别用 root 跑服务。
4.5 订单状态并发更新覆盖
现象:两个用户同时点「下单」,同一辆车生成两条订单。
原因:没加锁,两个请求都读到「在售」,都写入订单。
解决:用事务 + 行锁。START TRANSACTION; SELECT * FROM car WHERE id = ? FOR UPDATE;判断状态,再插入订单,COMMIT。或者用UPDATE car SET status = 2 WHERE id = ? AND status = 1,看affectedRows是否为 1。
5. 交付前必做的三件事:让工程从「能跑」到「能讲」
5.1 用 Postman 把接口跑一遍再联调
很多人一上来就调小程序,结果分不清是前端问题还是后端问题。我的习惯是:后端写完先用 Postman 把每个接口跑通,确认返回结构、状态码、错误信息都对,再让小程序调。这样出问题一眼就能定位。Postman 里建一个 Collection,把登录、列表、详情、发布、下单都存好,改参数就能测。
5.2 数据库加索引前后对比查询耗时
毕业设计答辩常被问「你做过优化吗」。最简单的优化就是加索引。用EXPLAIN看执行计划:
EXPLAIN SELECT * FROM car WHERE status = 1 AND brand = '大众' ORDER BY create_time DESC LIMIT 10;如果type是ALL,说明全表扫描,加idx_status_brand联合索引。加完再跑,type变ref,rows大幅下降。把这个对比截图放进论文,比空谈「优化了性能」有说服力。
5.3 说明文档和论文的写作顺序
别最后才写文档。我的做法是:建表时写数据库设计章节,写接口时写接口文档,联调时记录踩坑。这样文档是「做出来的」,不是「编出来的」。论文里的「系统实现」章节,直接贴关键代码和截图,配上「为什么这么设计」的说明。老师要看的不是代码多长,而是你懂不懂为什么这么做。
最后说个习惯:每次改完代码,先git commit再联调。翻车了能回滚,比事后找后悔药强。这套二手车小程序工程,难点不在技术多深,而在细节多、链路长。把登录、列表、发布、下单四条线跑通,剩下的就是堆页面和调样式。希望帮到你。
本文还有配套的精品资源,点击获取