1. 项目概述:一套能直接拿去交作业的二手交易系统
“基于微信小程序的跳蚤市场设计与实现”,配上“weixin250”这个编号,基本可以断定这是某所高校的毕业设计或者课程设计题目。这类题目的套路很固定:前端用微信小程序,后端用SSM框架,数据库用MySQL,凑成一套完整的“前后端分离”作品。但你别小看这套组合,它在教学场景里恰好覆盖了Web开发的核心知识链——移动端界面、HTTP接口、业务逻辑、数据库建模,一整套走下来,该练的技术点全都练到了。
这个项目解决的是校园或社区场景下的二手商品交易问题。同学们有闲置的书、数码产品、生活用品需要出手,买家想找便宜的二手货,传统的做法是发朋友圈、在QQ群里吼一嗓子,效率低且信息分散。通过小程序来做,用户不用下载App,扫码或搜索就能打开,微信登录免注册,这种体验对校园用户来说几乎零门槛。系统基本角色分两类:普通用户(买家和卖家同一人)和管理员,核心业务涵盖商品发布、商品浏览、收藏、下单、订单管理和后台审核。
我先说结论:如果你正在找毕设题目,或者在公司里需要快速搭一个轻量交易类小程序,这套“小程序 + SSM”的组合是性价比非常高的方案。原因很简单——资料多、坑少、演示效果好。即便是在生产环境,这种结构也能经得起一定规模的并发,并非只是学生作业的水平。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体设计与技术选型:为什么是微信小程序和SSM
2.1 前端为什么选微信小程序而不是H5或App
微信小程序最大的优势是“用完即走”,用户不需要经历下载、安装、注册的链条。做校园跳蚤市场这种低频但刚需的应用场景,小程序是天然适配的。用户卖一本书、买一个充电器,都是偶发需求,要是让人专门装一个App,转化率会低得可怜。
小程序开发还存在一个利好:微信官方提供了一套完善的组件体系和API,比如 wx.login、wx.uploadFile、wx.request,这些都能直接对应后端的接口设计。前端代码结构上也更接近Vue的单文件组件写法,对于学过Vue的人来说几乎是无缝切换。另一个很实际的原因是,微信开发者工具的调试能力很强,模拟器里能直接看到不同机型的适配效果,保存即编译,开发效率比原生iOS/Android双端开发高出太多。
2.2 后端为什么选SSM而不是Spring Boot
在这个时间点,如果从零新起一个项目,很多人会直接上Spring Boot。但SSM(Spring + SpringMVC + MyBatis)依然有它不可替代的位置。这里有个很现实的因素:教学和毕设场景里,学校教材、实验手册、往届学长留下的代码几乎都是SSM体系。Spring Boot虽然简化了配置,但正因为“太自动了”,很多学生在答辩时说不清楚请求是怎么进到Controller的、事务是怎么生效的、SQL是怎么执行的——这恰恰是答辩老师最爱问的地方。
SSM的好处是,每一层都很显式。Spring管理Bean,SpringMVC处理路由,MyBatis写SQL,三层结构边界清晰。你拿着系统说“用户点了一下按钮,请求从Controller进到Service再到Mapper,最后落到MySQL”,整个过程有迹可循,这在答辩环节非常加分。另外,SSM部署到Tomcat的流程也很经典,对服务器要求低,一台1核2G的云主机跑起来毫无压力。
2.3 系统整体架构与角色权限
从架构上看,这套系统是典型的前后端分离结构,不过用的是传统的Session保持登录态,而不是JWT。前端小程序通过 wx.login 获取临时code,发给后端换取openid,后端再生成一个自定义的登录态字段(比如 token)返回给前端,前端存储后每次请求带上它。
系统角色分成三层:
| 角色 | 核心操作 | 边界 |
|---|---|---|
| 游客 | 浏览商品、搜索 | 不能下单、不能发布 |
| 普通用户 | 发布商品、编辑下架、收藏、下单、确认收货 | 能管理自己的商品和订单 |
| 管理员 | 商品审核、用户管理、订单监控 | 不能代替用户交易 |
这里有个容易被忽略的设计点:二手交易市场里,同一个用户既是买家也是卖家,所以用户表不需要分成 buyer 和 seller,而是用一条记录加角色状态位,商品表通过 user_id 关联到发布者,订单表同时记录 buyer_id 和 seller_id 两个外键。这样的模型更符合实际业务——一个学生可能今天卖掉一台旧手机,明天又在别人那里买一个键盘。
2.4 部署环境与工具链
开发和部署环境整理如下:
- JDK 1.8 + Maven 3.6
- Tomcat 8.5
- MySQL 5.7
- 微信开发者工具(稳定版)
- IDEA 2020+(或Eclipse)
这套组合的兼容性经过大量验证,网上可以搜到无数篇环境配置教程。唯一要注意的是别一上来就用JDK 17或Tomcat 10,API命名空间变了,SSM项目大概率会报 ClassNotFoundException,到时候排查起来会多花不少时间。
3. 核心功能拆解与数据库设计
3.1 功能模块划分
整个系统的功能可以画成以下这些模块:
- 用户模块:微信授权登录、个人资料维护、我发布的商品、我的收藏
- 商品模块:商品发布、商品列表(分页+分类筛选)、商品搜索(关键词)、商品详情、上下架
- 交易模块:发起购买/我想要、订单列表(我买到的/我卖出的)、订单状态流转
- 管理后台:登录、商品管理(审核/下架)、用户管理、数据统计
这里我特别想强调的是,“我卖出的”这个维度的设计往往被新手忽略。很多初学者做交易系统,只做了“用户下单”这个方向,但跳蚤市场里每个用户都有双重身份,卖家也要看谁买了自己的东西、要不要发货、买家是否确认收货。所以订单表的设计一定要有方向性,通常用 seller_id 和 buyer_id 两个字段来区分视角,而不是用一个 user_id 表示下单人。
3.2 数据库表结构设计
这一块是整篇项目里最能体现工程能力的地方。建议设计如下几张核心表:
用户表 tb_user
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int(11) PK | 自增主键 |
| openid | varchar(64) | 微信openid,唯一索引 |
| nickname | varchar(32) | 昵称 |
| avatar | varchar(255) | 头像URL |
| phone | varchar(11) | 手机号 |
| role | tinyint | 0普通用户 1管理员 |
| create_time | datetime | 创建时间 |
商品表 tb_goods
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int(11) PK | 自增主键 |
| user_id | int(11) | 发布者ID |
| category_id | int(11) | 分类ID |
| title | varchar(64) | 商品标题 |
| description | text | 商品描述 |
| price | decimal(10,2) | 价格 |
| original_price | decimal(10,2) | 原价(可选) |
| images | varchar(1024) | 商品图片URL,多张用逗号分隔 |
| status | tinyint | 0待审核 1在售 2已下架 3已售出 |
| view_count | int(11) | 浏览次数 |
| create_time | datetime | 发布时间 |
订单表 tb_order
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int(11) PK | 自增主键 |
| order_no | varchar(32) | 订单编号 |
| goods_id | int(11) | 商品ID |
| buyer_id | int(11) | 买家ID |
| seller_id | int(11) | 卖家ID |
| amount | decimal(10,2) | 成交金额 |
| status | tinyint | 0待付款 1已付款 2已发货 3已收货 4已取消 |
| create_time | datetime | 下单时间 |
收藏表 tb_favorite、分类表 tb_category 就不展开列字段了,结构都比较简单。有一点要提醒:图片字段不要只存一张图,建议存多个URL逗号拼接,前端用 wx.previewImage 预览时就靠这个字段把图片列表取出来。别看这个设计简单,但很多毕设项目都栽在“只能传一张图”这种体验上。
3.3 为什么订单要单独建表,而不是在商品表上加状态
这是很多初学者容易纠结的地方。表面上看,标记一个商品“已卖出”,只需要把商品表的 status 改成 3 就够了。但这样做的问题是,你无法记录“谁在什么时候、以什么价格、买了什么东西”。
举个例子:用户A发布了一本书,用户B下单购买,A把书标记为已售出。但如果B后来取消了订单,商品的 status 就要从“已售出”变回“在售”。如果直接在商品表上改,中间状态就无法追溯——是卖掉了又退货?还是B根本没下单?单独建订单表,每个动作都留下一行记录,商品的最终状态只是由订单状态计算出来的快照,这样业务才能闭环。
4. 核心流程实现:从小程序登录到订单闭环
4.1 微信登录的完整流程
登录是整个系统的入口,也是第一次接触微信生态的开发者最容易搞混的地方。标准的流程是这样:
- 小程序端调用
wx.login获取临时code(有效期5分钟,只能用一次) - 小程序把
code发送到后端接口/api/login - 后端拿着
code请求微信接口https://api.weixin.qq.com/sns/jscode2session,用appid和secret换取openid和session_key - 后端查数据库,如果
openid不存在则创建新用户;存在则直接返回登录态 - 后端生成一个自定义
token(可以用UUID),在Redis或数据库存一份,返回给前端 - 小程序存储
token,后续所有请求在header里带上
后端Controller的关键代码大致长这样:
java复制@RestController
@RequestMapping("/api")
public class LoginController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
// 1. 用code换openid
String openid = wxService.code2Session(dto.getCode());
// 2. 查库/建用户
User user = userService.findOrCreateUser(openid);
// 3. 生成token
String token = UUID.randomUUID().toString().replace("-", "");
// 4. 存储token对应的userId(Redis或内存Map)
TokenStore.put(token, user.getId());
return Result.success(new LoginVO(token, user));
}
}
这里有一个很重要的细节,我见过不少同学把 wx.login 拿到 code 后,直接在当前端用 appid + secret 去调微信接口。这在开发工具里可能能跑通,但一旦上线到正式环境,secret 会被暴露在小程序包里,任何人都能抓包看到,这是严重的安全漏洞。secret 必须只存在于后端服务器。
4.2 商品发布与图片上传
商品发布是跳蚤市场的高频操作,它的核心难点不在表单,而在图片上传。小程序的 wx.chooseMedia 可以一次选多张图,然后通过 wx.uploadFile 将图片传给后端,后端保存到本地目录或云存储,返回图片URL。
实现上要注意几点:
- 微信小程序对
wx.uploadFile有并发限制,同时上传超过10个文件可能出现失败,建议用Promise串行上传或者限制数量(比如最多9张) - 后端接收图片的接口要配置
multipart解析,SpringMVC里默认的CommonsMultipartResolver需要设置maxUploadSize,否则图片稍微大点就会报MaxUploadSizeExceededException - 图片存储路径建议按日期分目录,比如
/upload/2025/01/15/xxx.jpg,这样方便后期运维清理,而不是把所有图片堆在一个文件夹里
java复制@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件为空");
}
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
// 按日期创建目录
String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
String dirPath = uploadBasePath + "/" + datePath;
File dir = new File(dirPath);
if (!dir.exists()) {
dir.mkdirs();
}
file.transferTo(new File(dirPath + "/" + fileName));
return Result.success("/upload/" + datePath + "/" + fileName);
}
4.3 订单状态机与并发防超卖
订单状态流转是业务逻辑里最复杂的部分,倒不是因为代码难写,而是状态多了之后容易混乱。推荐在设计阶段就把状态机图画清楚,一共四个状态足够了:
- 待付款:买家发起订单,占用商品
- 已付款 / 待发货:买家付款,卖家看到订单
- 已发货:卖家在“我卖出的”里点发货
- 已收货:买家确认收货,交易完成
跳蚤市场里最怕的问题是“一件商品被两个人同时下单”。虽然二手商品没有严格的库存概念,但一个商品被两个人下了单,体验非常糟糕。解决办法是:下单时对商品行加锁,SQL里带上条件更新:
sql复制UPDATE tb_goods SET status = 3
WHERE id = #{goodsId} AND status = 1
如果返回的影响行数为0,说明商品已经被别人抢了,或者已经下架,直接提示用户“手慢了”。用条件更新来代替先查后改,可以避免并发下重复下单的问题。
4.4 后台管理端实现
管理后台不打算用小程序来实现——管理员在手机上一个一个点太难受了。通常做法是用一个简单的Web页面(Bootstrap + Thymeleaf)或者单独的管理端页面。管理员的账号可以直接在数据库里写死一条 role=1 的记录,登录后跳转到后台页面,能看到用户列表、商品列表,对违规商品进行下架操作。
后台页面不需要做得多华丽,但一定要有“数据统计”的概念:总用户数、总商品数、总订单数、交易金额合计。答辩的时候,老师在台下最常问的问题就是“你这系统上线之后,运营数据怎么看”,如果你能现场打开管理后台页面,指着图表说“这是近一周的商品发布趋势”,会是一个非常亮的加分项。
5. 环境配置与项目运行全流程
5.1 后端项目结构
标准的Maven项目结构,按controller、service、mapper分层。大致长这样:
code复制src/main/java
├── com.shop
│ ├── controller
│ │ ├── GoodsController.java
│ │ ├── OrderController.java
│ │ └── UserController.java
│ ├── service
│ │ ├── GoodsService.java
│ │ └── impl
│ │ └── GoodsServiceImpl.java
│ ├── mapper
│ │ ├── GoodsMapper.java
│ │ └── GoodsMapper.xml
│ ├── entity
│ │ ├── Goods.java
│ │ ├── Order.java
│ │ └── User.java
│ ├── config
│ │ └── WebConfig.java
│ └── common
│ ├── Result.java
│ └── TokenStore.java
src/main/resources
├── application.properties
├── spring-mvc.xml
├── mybatis-config.xml
└── mapper
└── GoodsMapper.xml
这里我用的是 application.properties 来管理数据库连接,但SSM传统项目更多是 jdbc.properties + spring-context.xml 的方式,本质上没有区别。关键的配置文件有三个:web.xml(配置SpringMVC入口)、spring-mvc.xml(扫描controller、配置视图解析器)、spring-dao.xml(数据源、SqlSessionFactory、Mapper扫描)。
5.2 从零开始跑通项目的七步
下面是我验证过很多次的最稳启动顺序,按这个来基本不会出错:
- 准备数据库:新建
shop数据库,执行项目附带的shop.sql脚本,导入所有表结构和测试数据 - 修改配置:打开
jdbc.properties,把数据库用户名密码改成你自己的;把basePath改成你本地图片要存放的绝对路径 - 启动后端:IDEA里直接运行Tomcat配置,或者在项目根目录执行
mvn tomcat7:run,看到 “Server startup in xxx ms” 说明启动成功 - 验证接口:浏览器访问
http://localhost:8080/api/goods/list,有JSON返回说明后端没问题 - 打开微信开发者工具:导入小程序前端源码目录,修改
app.js里的baseUrl为http://localhost:8080 - 关闭域名校验:在开发者工具右上角“详情” -> “本地设置”里勾选“不校验合法域名”,否则请求会被拦截
- 编译运行:编译后就能在模拟器里看到首页商品列表,可以跑通注册登录、发布商品全流程
5.3 真机预览时必须处理的两个点
模拟器跑通了,很多人急着点“真机调试”,结果页面白屏。这里有两个点需要提前处理:
第一,手机和电脑必须在同一局域网,你把 localhost 改成电脑的局域网IP,比如 http://192.168.1.6:8080。手机访问不了 localhost,这是新手最容易踩的坑。
第二,微信会对请求域名做合法性校验。在开发者工具里关掉校验只是帮你测试,真机上 http://192.168.x.x 这种内网IP也是不行的——除非你在真机调试模式下(右上角菜单勾选“不校验合法域名”)。真正要上线的话,必须申请一个备案域名,配好HTTPS证书,然后在微信公众平台把域名加入 request合法域名 和 uploadFile合法域名 白名单里。
5.4 部署到云服务器的关键步骤
如果只是本地跑通,那还停留在“交作业”阶段。要把系统部署到云服务器上,让别人也能访问,核心步骤如下:
- 服务器安装JDK 8、MySQL、Tomcat
- 数据库导入SQL,注意修改MySQL的
character-set-server=utf8mb4,避免中文乱码 - 用Maven打包:
mvn clean package -DskipTests,得到war包 - war包丢到Tomcat的
webapps目录,重启Tomcat - 小程序前端代码里把
baseUrl改成https://你的域名 - 微信公众平台配置域名白名单、HTTPS证书
这里我要单拎出来提醒一个细节:图片上传的目录路径和Tomcat的发布目录不是一回事。如果你在本地 basePath=/usr/local/upload,那么Nginx要把 /upload/** 这个路径映射到服务器上的实际目录,否则用户上传的商品图裂了。最省事的办法是部署一笔安装Nginx,把静态资源(图片)和动态请求(/api/**)分成两个location来处理。
6. 常见问题与排查技巧实录
6.1 微信登录失败:errCode: 40029
后台日志提示 invalid code,这种情况大概率是同一个 code 被用了两次。检查一下你的前端是不是用了 wx.login 之后,某些场景下自动又触发了一次 wx.login,比如页面 onShow 的时候每次都执行登录逻辑。修复方案:登录成功后将 token 存到 storage,后续请求都带 token,只有 token 失效时才重新走登录流程。
6.2 数据库中文乱码
部署到服务器后,商品标题和描述出现“???”,这是数据库连接URL缺少字符编码参数。在 jdbc.properties 里,连接串建议写成这样:
properties复制jdbc.url=jdbc:mysql://localhost:3306/shop?useSSL=false&useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai
注意 characterEncoding 要放在连接串上,只修改mybatis配置文件是不生效的。另外在初始化数据库时,最好指定库的字符集是 utf8mb4:
sql复制CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
6.3 模拟器正常,真机上图片不显示
这个问题的根源基本可以锁定为 http 明文请求被微信拦截。微信小程序正式环境要求所有资源必须是 https,只有开发者工具里可以关闭校验。解决办法:本地开发用“不校验合法域名”模式,线上发布前把图片和接口统一切到HTTPS域名下。如果是部署初期没有域名,有一个临时方案:把图片 base64 后传到云存储,但存储成本较高,不推荐长期使用。
6.4 商品列表返回速度慢
商品列表接口每次查询都要返回全部分类数据,如果分类不做缓存,前端每次切Tab都来一次请求,数据库压力大,体验也卡。最简单的优化是:分类数据量小,可以做成静态配置文件,前端打包时直接内置;商品列表SQL加上 LIMIT 分页,前端用 onReachBottom 触发加载下一页。
6.5 常见问题排查速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
请求失败,显示 fail url not in domain list |
域名没加白名单或没关闭校验 | 工具里勾选不校验域名,正式环境加白名单 |
| 登录接口返回500 | openid查库时字段类型不符 | 检查数据库 openid 字段长度,建议varchar(64) |
| 图片上传后打开是破损的 | 存储路径没有写权限 | chmod 755 上传目录,或检查路径拼接 |
发布商品报 json parse error |
后端实体类没有无参构造器 | 检查实体类是否有默认构造器和getter/setter |
| Tomcat启动卡在Spring初始化 | 数据库连不上 | 先单独测试数据库连接,确认用户名密码 |
7. 项目答辩时的加分点与常见提问
7.1 老师在答辩时最爱问的四个问题
这类毕业设计项目的答辩,老师问的问题其实高度集中:
“为什么商品状态不直接用布尔值?”
这个问题考察的是业务边界意识。布尔值只能表示在售/下架两种状态,但实际业务还有待审核、已售出等场景,用 tinyint 枚举状态是成本最低的扩展方案。如果加上时间维度,比如记录“下架时间”,将来就能做“30天未售出自动降权”的功能。
“如果两个用户同时买同一件商品,怎么处理?”
这是并发控制问题。我上面提到的条件更新 UPDATE tb_goods SET status=3 WHERE id=? AND status=1 就是答案,配合返回影响行数判断是否成功,代码量不大但能把并发隐患解决掉。
“用户不点确认收货怎么办?”
这个问题的核心是交易流程的超时机制。跳蚤市场场景下可以不做自动确认,因为交易场景偏线下“面交”,买家点“确认收货”即视为交易完成。但如果要扩展成完整电商,需要引入定时任务,比如发货后7天自动确认收货。
“你如何保证用户发布的内容合法合规?”
标准回答:管理后台对商品进行审核,发布时先进入“待审核”状态,管理员审核通过后才在前端展示。同时在发布页面加敏感词过滤,从源头拦截违规内容。
7.2 在原项目基础上做的低成本扩展
如果你想让这个项目显得“比别人多做了一步”,下面这些方向都是投入小、效果大的:
- 微信订阅消息:下单后给卖家发“你有一笔新订单”的通知,用
subscribeMessage.send接口实现,流程不复杂,但演示时非常惊艳 - 搜索历史记录:用户搜索的关键词存到
localStorage,下次打开搜索页时展示最近搜索,前端代码不超过20行 - 商品浏览记录:在商品详情页把浏览的商品ID存到后端,个人中心“浏览记录”里展示,需要一个表、一个接口、一个页面
- 管理员数据图表:用ECharts统计每日发布量、热门分类占比,数据从订单/商品表聚合查询,前端展示一个折线图
8. 写在最后的实操心得
这套“微信小程序 + SSM跳蚤市场”项目我前后接触过好几个版本,也帮不少人排查过问题。整体感受是,它的代码量并不大,真正的难点集中在“微信生态的特殊约束”和“交易流程的状态管理”这两块。前者需要你理解微信平台的各种规则,比如域名校验、登录态设计、HTTPS强制;后者需要你有一张清晰的状态流转图,知道每一步数据的状态会怎么变化。
如果你拿到的是一份带文档的完整源码,动手前我建议先别急着跑起来,而是花半小时把数据库的SQL脚本完整看一遍,搞清楚每张表之间的关系——这比看十遍代码更管用。从项目本身的完成度来看,这套系统的下限是“能跑通”,上限取决于你在细节上愿意投入多少精力。
有一点我一直觉得值得多说几句:毕设项目绝不等于“能运行就完事”。同样的功能,有人的代码写到2000行还是乱成一团,有人的代码精简到1200行并且注释清晰、表设计合理。差距不在天赋,而在动手之前有没有把整体的数据流和状态流转想清楚。写代码只是最后一步,前面那些思考才是能带走的真本事。
