最近整理了一个有意思的毕业设计项目,基于微信小程序的跳蚤市场设计与实现,后端走的是SSM框架,也就是Spring + SpringMVC + MyBatis这套经典组合的校园二手交易平台。这个项目如果只是做出来一个“能发布商品、能浏览列表”的Demo,其实并不难,难的是把交易闭环里那些容易被忽略的环节,比如登录态、商品状态流转、图片处理、搜索排序这些东西串起来,才能真正达到毕业设计答辩时能讲清楚、能演示完整流程的水平。
这篇文章想把我在做这个项目过程中的完整思路写出来,包括需求层面的推演、技术选型为什么锁定了SSM而非其他框架、数据库和接口怎么设计、小程序端和后端是怎么一步步联调的,以及我在实际编码里踩过的一些比较典型的坑。如果你正在做类似的校园二手交易小程序,或者本身就在纠结这类毕设该从哪里下手,这篇文章应该能省你不少摸索的时间。
1. 这类需求背后的真实痛点:二手交易不是只做一个列表页
很多人拿到“基于微信小程序的跳蚤市场设计与实现”这个题目,第一反应就是:这不就是一个商品列表加一个发布页吗?等真正动手做完你会发现,拼凑出一个能看的主界面并不难,真正的麻烦全在那些看不见、却在用户实际使用中随时会暴露问题的地方。
从用户角度讲清楚这个平台到底要解决什么问题。跳蚤市场面向的是校园场景,用户有毕业生卖书、买书,有同学互相转让闲置电器、衣物,也有一部分人是想找便宜好用的二手物品。核心诉求是三个:
第一,用户要能快速发布一件闲置物品,上传图片、写价格和描述,最好从拍照到上架不超过两分钟。这个是整个平台的使用动机,如果发布流程繁琐,用户试一次就不想再打开。
第二,买家要能按分类浏览、关键词搜索、看商品详情,并且能联系到卖家。这里要分清楚“联系”这个动作怎么做。很多毕设项目做成了站内IM聊天,功能是好看,但轮子造得太大,从技术实现和项目管理角度都未必合适。我在这类项目里通常建议做“显示微信号或手机号”的方案,或者做“我要留言”的简单表单。原因后面会详细说。
第三,卖家要能管理自己发布的商品,进行下架、编辑、标记为已售出等操作。商品不能一直挂在平台上,必须有一个状态流转的生命周期。
从管理员的视角出发,隐藏的需求则是审核和统计数据。虽然大多数毕设项目对后台管理模块的演示深度要求不高,但设计里得有,至少要展现“平台内容可控”的思路。管理员可以查看用户列表、商品列表,对违规内容做下架处理,查看基本的发布数量、分类占比统计。
所以我在真正敲代码之前,先进行了一次需求拆解,把项目目标分成了几个层面:
- 用户端核心功能:登录授权、发布闲置、浏览分类、关键词搜索、商品详情、留言联系、个人中心(我发布的、我卖出的、我买到的)
- 管理端核心功能:登录、用户管理、商品管理、分类管理、上架/下架操作、基础数据统计
- 业务规则:商品状态包含在售、已售出、已下架三种;同一件商品只能被买家“我想要”联系,不会出现多个人下重复订单的问题;商品数量达到预警状态时要提示
- 场景延伸:这个是跳蚤市场的特点,商品信息粒度比较粗,不需要像电商系统那样处理SKU、库存、优惠券这类复杂逻辑,所以他的业务建模可以被控制在一个合理的复杂度区间。
明白了这些,才能解释为什么后端架构要用SSM而不是随便写一个Servlet,也才能合理地去设计数据库和接口。没有这一步,后面的开发就只是在“照着教程敲CRUD”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型为什么盯着SSM不放——毕设项目的取舍逻辑
选技术栈,首先是想清楚这个项目给谁看、用来干什么。基于微信小程序的毕设项目,代码一般要过两关:第一关是导师和评审老师看整体设计是否合理,第二关是答辩时老师可能会直接翻开代码或跑起来看效果。
SSM,全称是Spring + SpringMVC + MyBatis,在当前的毕业设计环境里是一个非常稳的选择。从教学评价角度来说,SSM一定程度上已经代表了Java Web课程体系里足够经典、足够讲究“分层”的框架组合。Controller—Service—Mapper三层结构清晰,MyBatis的SQL写在XML里,反而更好向老师解释每一步在做什么。相比之下,Spring Boot虽然开发效率更高、配置更少,但很多东西是被“约定”掉的,答辨的时候一旦被问到“Spring Boot的自动配置原理”,不少同学反而不容易讲得清楚。SSM的优势在于结构透明:所有配置都在web.xml和Spring配置里显式地写着,每个Bean怎么创建、事务怎么切,都能看得见摸得着。当然这个体验只是针对毕设项目,不是否定Spring Boot在生产环境里的碾压级效率。
小程序的端上技术选用原生微信小程序。为什么不选uni-app?uni-app确实有一个代码编译到多端的优势,但对于这种只跑微信端的项目,引入第三方编译层反而增加了不确定性。原生小程序组件生命周期直接、API调用清楚、调试的时候报错能找到非常直接的原因。而且很多在微信网页开发里的概念,比如rpx动态尺寸、Page和Component的区分,在原生开发者工具里都能获得最原汁原味的支持。如果你是第一次做小程序,直接用原生,不要绕路。
整体的技术栈与运行环境如下表:
| 技术层 | 选型 | 说明 |
|---|---|---|
| 用户端 | 微信小程序原生 | 端上只做展示与交互,业务逻辑放后端 |
| 后端框架 | Spring + SpringMVC + MyBatis | 经典Java Web分层架构 |
| 容器 | Tomcat 8/9 | 负责Servlet请求与JSP解析,后端以JSON数据交互为主 |
| 数据库 | MySQL 5.7+ | 存储用户、商品、分类、留言等业务数据 |
| 缓存 | 无 | 毕设阶段本地数据量小,不需要引入Redis增加复杂度 |
| 构建工具 | Maven | 管理依赖;建议不要本地手拷jar包,地址要记牢 |
| 版本控制 | Git | 本地多分支开发配合码云或者GitHub做备份 |
这个选型最核心的逻辑:在“能顺利跑完演示”和“能让老师问得住”之间找到了一个平衡点。SSM虽然是老牌框架,但其分层思想是后面理解Spring Boot、Spring Cloud这些新生态的地基。我在很多文章和代码里见过有人把后端写成“一个Controller里面又是JDBC又是JSON拼装”的样子,这种代码跑起来虽然没问题,但那个代码实质上是没有架构可言的。SSM逼着你把参数接收、业务处理、数据访问分开,这个过程本身就是设计能力的锻炼,也是毕设里最重要的隐性得分点。
3. 小程序端页面设计的核心思路:从用户心智倒推功能
页面设计这件事,我建议不要从“别人家的二手App有什么界面”开始抄,也不要从“小程序有哪些组件”开始套,而是应该从“一个想卖东西的同学心路历程”去倒推。
一个新生想卖掉自己大一用过的教材,他打开小程序后的动线几乎一定是:登录授权 -> 点击底部Tab的“发布” -> 拍照片/选照片 -> 填标题、描述、价格、分类 -> 确认上架。想买东西的同学动线则是:首页看推荐-> 点分类筛选 -> 搜索框输入关键词 -> 点进商品详情 -> 下拉看到卖家信息/联系方式 -> 联系交易。
正式动工前先做了一张“页面功能地图”(对应二维码或页面跳转关系),规划出了这些页面:
- 首页:轮播图(运营位)、分类快捷入口、最新发布列表、搜索框
- 分类页:左侧一级分类导航,右侧展示对应商品列表
- 搜索列表页:展示关键词搜索结果,支持按价格排序、按时间排序
- 商品详情页:多图展示、价格、描述、卖家信息、发布时间、浏览数、留言入口
- 发布页:表单页,核心字段含标题、描述、分类、价格、图片上传、联系方式展示开关
- 我的页面:头像昵称、我发布的、我卖出的、我买到的、留言记录
- 登录页(或弹窗):通过微信授权获取用户信息
这里想重点花点篇幅讲讲首页信息流和发布页的设计细节,因为这两个页面直接决定了用户愿不愿意用下去。
首页信息流向下拉加载,列表数据来自后端分页接口,每页10条或者20条都可以,字段包含封面图、标题、价格、发布时间的相对时间(比如“3分钟前”“2小时前”这种人类友好的格式)。下拉刷新时要重新请求首页接口,不能直接拼接上一页的数据。微信小程序的onReachBottom是页面滚到底部自动触发,这个事件里要避免并发请求,最好加一个loading状态锁,否则用户快速滚动时会触发多次重复请求,列表会大量重复。
发布页里最容易让用户流失的是图片上传。小程序端官方API是wx.chooseMedia,可以选择本地图片或拍照,一张一张选择容易让用户崩溃,所以从发布入口就限定好最多传9张,支持从相册里多选,配合压缩处理。图片作为小程序临时文件路径先预览,实际上传走后端的时机是在用户点击“发布”按钮的那瞬间,然后把这些图片的URL作为字符串拼接好提交给后端,这样可以省掉“先传图、后提交”这种复杂交互带来的中间状态。
这里有个很关键的细节:微信小程序的临时文件路径在真机上是有时效性的,退出小程序或者下载缓存刷新后临时文件会自动失效,所以不能把本地临时路径直接存数据库。上传拿到网络图片路径后再做同步执行,顺序是先传图、后提交表单。
发布页的分类选择建议做成 picker 组件而不是二级滚动列表,因为跳蚤市场分类粒度一般是衣服、书籍、电子产品、生活用品、运动器材、其他,总共可能就6~10个分类,一个单列选择器就足够。这里选择灵活的控制方式是有意的:在交互上,连让用户一个个手动填写“成色”“原价”这种字段都不要,只保留必要字段,降低使用门槛。
再来说说为什么要用“显示联系方式”而不是“聊天功能”。这背后是成本和体验的双重考虑。做一个IM聊天界面在小程序端并不是不能实现,但这意味着要引入WebSocket长连接或者使用第三方即时通讯SDK,这部分从开发量、稳定性、答辩难度来说都会在毕设项目里成为不可控的黑洞。校园二手交易的特点是低频率、大金额、强信任缺失,交易双方往往需要当面验货,用微信联系方式直接沟通已经是最顺着用户习惯的方案。所以我的做法是:卖家在发布时可以选择是否公开微信号,买家在详情页可以直接看到微信号展示,也可以给卖家留言,留言内容在卖家的“我的”页面里聚合展示。这个设计保留了交易闭环的入口,又规避了做IM系统的复杂度,自认为在毕设项目里是一个很合理、很成熟的功能取舍策略。
4. 后端接口与数据库设计:如何把交易流程画成一张表
接口设计要“面向页面”,不要做成一个简单的JSON数据导出器,否则小程序端开发会非常别扭。我在设计REST接口时遵循了几个原则:
第一,接口尽量一次性返回页面需要的数据,不能让小程序端一次请求后再发起第二次请求去拼装数据。例如商品详情页除了商品基本信息,还需要展示卖家头像和昵称,接口就直接返回一个嵌套对象{ goodsInfo, userInfo },前端不用为了拿一个昵称再调一次用户查询接口。
第二,统一返回值结构。当前后端分离的项目接口格式化是一个老生常谈的话题,但毕设项目里很多人不重视,每个接口返回的JSON字段风格都不一致,导致前端解析时做了大量判断。我这边定义了一个统一的Result类(code + msg + data),code=200表示成功,其他为失败,小程序端封装了一个wx.request的Promise函数,统一处理响应码。这样在“我的列表”和首页加载这类请求里写起来很简洁,只需要关注成功分支的数据解构。
第三,分页参数必须显式传。接口统一接收pageNum和pageSize两个参数,后端用MyBatis的PageHelper插件做分页查询,数据返回时除了list之外还要返回total总条数,前端可以用于判断还有没有更多数据。
现在重点说数据库表设计。我见过太多人把“跳蚤市场”设计得有订单表、支付流水表,过度设计了。二手平台本质上是撮合交易平台,不处理资金流。用户之间线下交易,平台不需要订单这个概念,只需要维持商品状态,以及记录买卖双方的留言互动。所以核心数据表四张就够了:
用户表(user)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 自增 | 用户ID |
| openid | varchar(64) | 微信用户唯一标识 |
| nickname | varchar(64) | 昵称 |
| avatar | varchar(255) | 头像URL |
| phone | varchar(20) | 联系方式(可选) |
| create_time | datetime | 注册时间 |
商品分类表(category)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 分类ID |
| name | varchar(20) | 分类名称 |
| sort | int | 排序权重,数值越小越靠前 |
商品表(goods)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 自增 | 商品ID |
| user_id | int | 发布用户ID |
| category_id | int | 归属分类ID |
| title | varchar(50) | 商品标题 |
| description | text | 商品详细描述 |
| price | decimal(10,2) | 价格 |
| image_list | varchar(1000) | 商品图片URL列表,逗号分隔 |
| status | tinyint | 状态:0在售,1已售出,2已下架,3违规下架 |
| browse_count | int | 浏览量 |
| create_time | datetime | 发布时间 |
| update_time | datetime | 最近更新时间 |
留言表(message)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 自增 | 留言ID |
| goods_id | int | 关联商品 |
| from_user_id | int | 留言人用户ID |
| to_user_id | int | 被留言对象用户ID |
| content | varchar(200) | 留言内容 |
| create_time | datetime | 留言时间 |
这四张表已经支撑起了完整闭环,浏览的人查商品表,发布的人在商品表新增一条记录,想联系的在留言表加一条,卖家在我的页面读留言记录。
但是这里有一个容易踩细节坑的点:商品表状态。
status的三个值一定别只存int不写注释,否则过好几周自己可能都会忘了。更重要的是业务上要注意:同一个商品不允许重复发布,用户把商品状态标记为“已售出”后,商品应从前台搜索结果里消失,不能再被普通用户检索到。所以列表查询统一带一个条件status = 0,查看详情时如果status不是0,前端可以提示“该商品已不在架”。
对于搜索功能,我在商品标题和描述字段都加了普通索引,但只有标题支持LIKE匹配。很多访问量大的系统不会在描述字段上直接用模糊查询,会拖慢全表扫描速度。但毕设项目的数据量没有这个瓶颈,直接简单查询是可以的。查询时要注意的一点是排序:新建的商品排前面,同时浏览量可以作为一个辅助权重,我做的是ORDER BY create_time DESC, id DESC,搜索页面增加价格排序时,只需要拼SQL时把orderBy字段切换一下。
5. 前后端联调最容易卡壳的三个环节
这一部分是我觉得整个项目中最有工程量、最值得分享的地方。模型可以画得很美好,前端页面代码也写完了,但是一联调,各种各样奇怪的问题就冒出来了,而且往往都集中在下面几个点上。
5.1 授权登录与openid的坑
微信小程序登录流程不要混淆开发早期和上线之后的处理方式。开发阶段可以走“测试号登录”,但到了项目快要提交文档和演示的时候,如果还是用测试号,老师用另一台手机扫体验版二维码时会发现“永远登录不上”,因为独立开发者的体验版成员需要添加体验者白名单。
正常的登录流程是:小程序端调用wx.login()拿到临时code -> 把code发给后端自己的接口 -> 后端调用微信的auth.code2Session接口,拿appid+secret+code换回openid和session_key -> 后端根据openid查数据库,如果没有这条用户则自动注册,然后生成一个自定义登录态token返回小程序端。小程序端把token存在storage里,之后每次请求在请求头带这个token。
这里说一个很多人搞不清楚的点:早期版本的微信小程序,前端可以直接通过wx.getUserProfile拿到完整用户信息,然后只要把整个头像一个POST发给后端保存,后端甚至不需要微信官方接口。但后来微信隐私政策调整后,wx.getUserProfile拿到的头像昵称越来越多地变成了“微信用户”和灰色头像,真机上要去后台设置头像昵称填写能力。所以在设计用户体系时一定要把主键定位在openid,而不是头像昵称,前端的用户资料展示可以允许用户手动修改自己的头像昵称,不要直接依赖微信用户信息接口返回。
代码层面上,后端需要先到微信后台拿登录凭证校验接口,这部分属于服务端API,appsecret一定不能放在小程序代码里——这是底线安全问题。appsecret一旦泄露,任何人都可以拿着你的身份信息去后台调用接口钱要谨慎。
token的生成不能逞一时之快。我见过有项目直接用openid当token的,这意味着只要拿到对方openid,就可以冒充他的身份操作接口,非常危险。实际方案应该用UUID或IdWorker生成token,同时在后端内存里维护一份token到userId的映射关系。为了简化毕设,不必上Redis,用一个全局Map存token并发量也不大,但没有影响可接受程度。
5.2 图片上传:本地临时路径导致的各种灵异问题
发布商品时图片上传是前后端联调最容易出现诡异问题的区域。早期的代码我犯过直接用本地临时路径作为图片字段存数据库,然后就傻眼了。后来细想觉得是这么回事:
小程序里选择图片后得到的是一个wxfile://开头的临时文件路径,只在当前小程序会话内有效,冷启动后就失效。图片存储的正确姿势是把临时文件通过wx.uploadFile上传到自己的后端接口,后端用MultipartFile接收,把文件写到一个对外可访问的目录,然后把访问URL返回给前端。
所以我在后端预留了/api/common/upload这个不需要登录即可访问的上传接口,接收任意图片类型时会校验文件大小和格式。这里又涉及另外一个大坑:上传文件落盘的目录不能是项目编译后的classes目录,否则重新打包部署时文件容易丢失。安全的做法是配置一个外部绝对路径,比如D:/upload/或者Linux服务器上的/home/ubuntu/upload/,再通过nginx映射或写一个静态资源映射配置,把/upload/**映射到那个物理目录。Tomcat下可以直接在server.xml里配Host的Context,也可以在SpringMVC配置里加<mvc:resources mapping="/upload/**" location="file:D:/upload/" />。
图片名称用UUID.randomUUID()生成,加原始后缀名,这样不会出现中文乱码或重名覆盖问题。图片大小限制建议2MB以内,上传前在前端先做了一次压缩,小程序可以调用wx.compressImage接口,也可以利用canvas配合等比缩放的方案。细节处理好之后,哪怕用户网速慢,详情页的图片加载体验也不会太差。
5.3 用户token与内容安全
到了临近提交的时间,要着重检查所有涉及用户隐私数据操作的接口是否有登录态校验。有同学程序一通就开心,觉得“登录”页面写得不错,已经回调到首页了,就万事大吉,但实际上别人完全可以用POSTMAN直接调用发布接口,绕过小程序前端,在后端生成一堆垃圾商品数据。所以后端必须在一个拦截器里统一校验请求头是否有合法token。
SSM里写拦截器的方法是HandlerInterceptor实现preHandle方法,在SpringMVC配置里排除登录接口、轮播图接口这些无需登录的URI就能做到。小程序端在请求函数里封装统一的header:Authorization: xxxxx。后端通过ThreadLocal保存当前userId,之后的Service层拿到当前用户就可以向下传递了。我有一个血泪教训:在没有ThreadLocal存储userId时,把userId作为方法入口参数一层层往下传,会出现维护上的大量麻烦。
在内容安全方面,商品标题和描述应做一下简单的XSS过滤,前端和后端都要有长度限制。发布端昵称和留言等输入项要过滤脏话或者关键词。虽然毕业设计的评委大概率不会真正去攻击接口,但这些处理能展示你的安全设计意识,在项目文档里能写上“在登录态校验、参数合法性校验和内容安全等方面做了相关防护”,是加分项。
5.4 联调阶段的接口对接请求体积
很多人写完接口,拿微信开发者工具一调就报错或者格式不匹配。想分享一个实用方法论:不要在接口联调时“一步到位”。小程序端的请求统一封装好之后,先拿Postman或Apifox把后端接口文档里的URL全部过一遍,再开始连页面。这样做能确保问题只出在某一侧,不会出现“后端以为前端传错了、前端以为后端没返回”这种互相踢皮球的局面。
我在前后端数据交互格式上的统一方案是:请求参数用application/json传POST body,后端Controller对象用@RequestBody接收;上传和普通请求分两个域,避免互相影响。返回给前端的data字段不要用Map乱拼,定义一个VO类,让MyBatis查询结果存的是VO,JSON序列化后字段名保持一致。这里最烦人的坑是Java用驼峰命名,MySQL表字段用下划线命名,MyBatis如果没开mapUnderscoreToCamelCase会返回null。我的习惯是把MyBatis配置项中设置成map-underscore-to-camel-case: true,但更稳妥的做法是实体类的字段名和数据库字段保持一致,不必追求把数据库的created_at映射成Java的createdAt这种过度封装。
6. 从“能跑”到“能答辩”:文档、测试与坑位复盘
做完这些功能之后,毕设项目其实还差最后一批收尾工作,这也是很多代码本身的隐性重量所在。能跑起来不等于能提交文档,这两者的差距有时候比代码差距还大。
第一件要紧的事是整理一份清晰的表结构和接口清单。文档里放一张数据库ER图(用PowerDesigner或者draw.io画都行),再把每个接口的Method、URL、请求参数、返回结果列成一张表格。我的项目里接口清单差不多有20个左右,整理完后你会发现自己对这些接口记忆会特别清晰,答辩被问到的时候不用翻代码也能对答如流。
第二件事是写一份业务流程图或时序图,最好把用户发布商品、买家留言、卖家下架这几个核心场景走查一遍。虽然不能用mermaid在你的文档里做流程图,但你完全可以用Visio或processon画好截图放文档里。
第三件事是测试用例。把每一条核心流程在“开发者工具 + 真机预览”下各走一遍,记录一下结果。真机预览和模拟器的差异非常大,我遇到过模拟器正常、真机上图片上传就失败的情况,原因是模拟器没有走http安全检查但真机强制校验域名合法性和HTTPS证书。开发者在开发时必须要在微信公众平台后台把request合法域名配置成自己服务器的域名,否则真机预览永远请求不到后端。如果只是本地开发,可以在开发者工具里勾选“不校验合法域名”来临时调试,但到了写测试报告阶段,域名配置这块不能靠跳过绕过。有条件的话买个最低配的云服务器加上备案域名是有必要的,如果实在没有,把后端部署在本地并用内网穿透的临时域名对接演示,也只能算是演示级的方案,不建议这么干。
第四件事,把测试中发现的几个常见问题理成一张表,在答辩的时候如果被老师问“项目有什么不足”,直接就说“发现在弱网条件下图片加载较慢,后续可以考虑引入CDN加速”这种答案,比尬吹自己项目没有缺陷要可信得多。
对我来说整个过程里最有价值的体会,是我发现一个毕设题目,要做到“有业务价值”比他看上去要复杂得多。SSM、微信小程序、源码这些关键词都是表象,真正的功夫在多维实践:想清楚交易闭环能不能走通,想清楚一个买家和一个卖家的核心诉求会不会被满足,想清楚代码写出来有没有资格撑起一套真实可用的演示。
我给后来者一个很中肯的建议:做这类小程序 + SSM的项目,多把一个功能打磨到闭环,胜过同时铺开十条需求线。比如把“发布商品 -> 图片上传到服务器 -> 首页展示 -> 买家留言 -> 卖家下架商品”这一条链路完整跑通,没有bug,演示效果好,这其实就已经超过了大部分半成品项目。在此基础上如果还能抽空加一个浏览量统计和按热度推荐的小功能,整个项目的完成度就会立刻上一个台阶。
最后走一遍流程,你会发现在小程序云开发大量普及的今天,重新选择SSM自建后端似乎是一条弯路,但对一个正在学习Web开发的学生来说,这条路恰恰能让你在“Java Web基础 + 数据库SQL + 前后端分离思想 + 微信生态API”这套组合里获得最扎实的训练,也最符合这类毕设想要考核的核心素养。
