拿到一个标题叫做“weixin152未知小程序的设计与实现+ssm(文档+源码)_kaic”的工程包,我估计不少人都是一脸懵。weixin152大概率是资源站自己排的序号,真正系统叫什么,藏在doc文档里;但后半段已经把技术路线写得很清楚了:前端是微信小程序,后端是SSM(Spring + SpringMVC + MyBatis),交付物包括完整文档和源码。这就是一个典型的Java Web + 小程序前后端分离项目,也是近几年毕业设计和课程设计里出现频率最高的组合之一。
这篇文章我想站在实际做项目的角度,把这类“小程序 + SSM”从需求拆解、数据库设计、后端接口、小程序联调、文档编写到源码整理,完整走一遍。如果你手上正好有一个类似标题的工程包,或者正在做一个需要交付文档和源码的小程序系统,这篇文章可以帮你少踩很多坑。哪怕你连SSM是什么都还没搞明白,跟着后面的思路走,也能大概知道该从哪里下手。
1. 项目整体设计与技术选型
1.1 这类项目到底在做什么
先别被“未知小程序”四个字吓到。在我接触过的资源包命名习惯里,“weixin”前缀代表微信小程序,“152”是站点编号,真正业务名往往在文档的封面或者数据库初始化脚本里。可能是校园二手交易,可能是健身房预约,可能是党建答题,也可能是超市订单管理。但不管业务名称怎么变,骨架高度相似:用户通过小程序端操作,后端使用SSM框架提供接口,数据落在MySQL里,整个系统围绕“前台小程序 + 后台管理 + 数据库”三块展开。
所以拿到这类项目后,第一件事不是写代码,而是先打开文档目录和数据库脚本,确认业务实体有哪些。比如用户表、商品表、订单表、分类表、评论表,基本能反推出来首页展示什么、用户能做什么、管理员管什么。如果你手里的包名比较模糊,文档又缺失,那就按“用户登录、信息展示、业务操作、个人中心”这条主线去补全,评阅老师看重的也是这条流程是否完整。
1.2 为什么用SSM而不是Spring Boot
现在很多人一上来会问:都什么年代了,为什么不用Spring Boot?这个问题在实际项目中确实存在,但结论很现实:如果题目要求或者课程大纲指定了SSM,那就老老实实用SSM。我见过不少学生私自改成Spring Boot,最后答辩时被老师追问“为什么和开题报告不一致”,反而扣分。
从技术角度看,SSM的价值在于分层清晰。Spring管对象,SpringMVC管请求路由,MyBatis管数据库访问,每一层职责单一。对于校园项目来说,这种“看得见配置”的框架比Spring Boot更容易讲清楚原理。它确实有缺点,比如XML配置繁琐、依赖版本要自己配,但换个角度想:你把这些配置全部跑通,对Java Web的理解会扎实很多。以后再去用Spring Boot,你会知道它帮你省掉了哪些东西。
如果文档和源码已经是SSM,我建议不要移植框架,而是把精力放在功能完整性和稳定性上。技术选型不是越新越好,而是越匹配题目越好。
1.3 技术栈与运行环境清单
这类项目的标准运行环境,我列一个表,照着自己核对就行:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 稳定性最好,兼容绝大多数SSM工程 |
| Maven | 3.6+ | 统一依赖管理,建议配置阿里云镜像 |
| Tomcat | 8.5 或 9.0 | 部署SSM war包 |
| MySQL | 5.7 或 8.0 | 记得设置utf8mb4编码 |
| 微信开发者工具 | 最新稳定版 | 调试小程序端 |
| 接口测试工具 | Postman 或 Apifox | 联调后端接口时必备 |
版本不要盲目追新。JDK 17、Jakarta EE这些新玩意儿,和学校给的SSM模板经常不兼容。我第一次用JDK 11跑一个老项目时,Tomcat直接报ClassNotFound,后来换回JDK 8一次通过。另外,Maven仓库镜像一定要配好,否则首次构建下载依赖可能让你怀疑人生。
2. 核心功能拆解与数据库设计
2.1 小程序端功能模块划分
虽然不同业务有差异,但一个完整的小程序系统通常包含三类角色:普通用户、管理员、后端服务。以常见的“预约/商城/报修”类项目为例,小程序端的核心页面离不开这几个:
- 首页:展示列表、轮播图、分类入口,承担信息分发的角色
- 列表页:支持分页加载、关键词搜索、筛选条件
- 详情页:展示主体信息,提供“立即预约”“加入购物车”“报名”等操作入口
- 我的/个人中心:展示用户信息、订单记录、收藏等内容
后端管理功能可以是Web页面,也可以是小程序内的隐藏页面。很多毕设为了省事,会做一个独立的Admin端,但我更建议把管理操作接口单独设计,然后通过权限控制,让小程序端只暴露用户功能。这样文档里的“角色权限”章节会非常好写。
功能拆解的时候,一条核心原则是:先画用例图,再列功能清单,最后写代码。不要想到哪写到哪。很多项目最后乱成一团,不是因为代码能力差,而是因为没有先把“谁能干什么”搞清楚。
2.2 后端接口设计原则
后端接口建议遵循RESTful风格,路径用名词,操作用HTTP方法。举个例子:
| 功能 | 方法 | 路径 |
|---|---|---|
| 用户登录 | POST | /api/user/login |
| 获取商品列表 | GET | /api/goods/list |
| 查看商品详情 | GET | /api/goods/{id} |
| 创建订单 | POST | /api/order/save |
接口返回格式要统一,我给一个非常通用的结构:
{ "code": 200, "msg": "success", "data": { "list": [], "total": 100 } }code为200表示成功,其他为业务错误码。data可以泛型化,这样小程序端解析逻辑只需要写一遍。很多新手喜欢直接把ResultSet或实体对象返回给前端,字段混乱、状态码不统一,联调的时候痛不欲生。
登录态这块要注意:小程序端通过wx.login拿到临时code,传给后端,后端再拿着code、appid、secret去微信服务器换openid。出于安全考虑,secret绝不能放在小程序代码里。如果你只是本地调试,可以先用一个写死的假token顶住,等后端联通了再接入微信登录。
2.3 数据库表结构核心设计
数据库设计直接决定开发效率。以通用管理类系统为例,下面几张表是标配:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| user | id, openid, nickname, avatar, phone | 用户表,openid做唯一标识 |
| category | id, name, sort | 分类表 |
| goods | id, category_id, title, cover, price, stock | 商品/内容表 |
| orders | id, user_id, goods_id, num, status, create_time | 订单表 |
| banner | id, image, link | 首页轮播配置 |
设计时有一个容易犯的错:用昵称或者手机号做主键逻辑。正确做法是单独维护一个自增id,用户唯一性交给openid。如果你在小程序里看到用户登录后又改了头像,但是历史订单里还保留着改之前的头像,那说明头像是冗余存到订单表里的,这也是常见的业务取舍。
MyBatis层面,一张表对应一个实体类、一个Mapper接口、一个XML文件,包名分层为entity、mapper、service、controller。表结构不要设计过度复杂,外键能不加就不加,逻辑关联通过service层控制。对学生项目来说,清爽的表结构比完美的范式更重要。
3. 实操实现:从后端到小程序端
3.1 SSM后端骨架搭建
创建一个Maven工程,packaging选择war,pom.xml里加上最核心的依赖。我贴一个精简版本:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.5</version> </dependency> </dependencies>接下来是SSM最让人头疼的配置。web.xml里配置DispatcherServlet和ContextLoaderListener;springmvc.xml开启注解驱动和包扫描;applicationContext.xml配置数据源、SqlSessionFactory、Mapper扫描。我建议在applicationContext.xml里用property-placeholder引入jdbc.properties,数据库账号密码集中在外部文件,避免每次改数据库都要改代码。
一个容易忽略的点是:springmvc.xml扫描controller,applicationContext.xml扫描service和mapper,职责分开。如果两项混在一起扫描,事务注解偶尔会失效,排查起来非常隐蔽。配置完成后启动Tomcat,访问一个最简单的controller地址,能返回JSON,说明骨架已经通了。
3.2 小程序端请求封装与登录态
小程序端不能像Web开发那样直接在浏览器里敲地址,所有请求都通过wx.request发出。为了避免每个页面都写一坨请求代码,我会在utils目录下建一个request.js:
const BASE_URL = 'http://localhost:8080/ssm-demo'; function request(url, method, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); }这里有两个非常关键的坑。
第一,开发者工具里填localhost没问题,但真机预览时手机访问的是你电脑的局域网IP,不是localhost。你需要把BASE_URL改成类似http://192.168.31.101:8080/ssm-demo,并且勾选开发者工具里的“不校验合法域名”。如果后端部署在阿里云服务器上,就要用你备案好的域名,同时在小程序公众平台配置request合法域名,否则正式版会直接请求失败。
第二,登录态。小程序端拿到后端返回的token后,存到wx.setStorageSync里,每次请求通过header带过去。后端用拦截器校验token,校验失败返回401,前端收到401就跳回登录页。这套机制虽然简单,但足够覆盖毕设项目。
3.3 列表加载更多与分页实现
微信小程序列表页加载更多,是绝大多数系统都绕不开的需求。后端接口接收page和size两个参数,前端在onReachBottom时请求下一页。后端如果用MyBatis分页插件PageHelper,写法很简洁:
@GetMapping("/goods/list") public Result list(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { PageHelper.startPage(page, size); List<Goods> list = goodsService.selectList(); PageInfo<Goods> pageInfo = new PageInfo<>(list); return Result.success(pageInfo); }PageHelper会通过拦截器自动拼接limit语句,total、pages这些信息也一并查出来,省掉手写count的麻烦。如果不想引插件,手写就是:
<select id="selectList" resultType="goods"> select * from goods order by create_time desc limit #{offset}, #{size} </select>前端逻辑要注意防止重复请求。我见过很多新人在快速下滑时发出多个请求,导致列表数据错乱。加一个标志位:
Page({ data: { list: [], pageNum: 1, pageSize: 10, hasMore: true, isLoading: false }, onReachBottom() { if (this.data.isLoading || !this.data.hasMore) return; this.loadGoods(); }, loadGoods() { this.setData({ isLoading: true }); request('/api/goods/list?page=' + this.data.pageNum + '&size=' + this.data.pageSize) .then(res => { this.setData({ list: this.data.list.concat(res.list), pageNum: this.data.pageNum + 1, hasMore: this.data.pageNum < res.totalPage, isLoading: false }); }); } })每次请求前判断isLoading,请求完成后判断hasMore,逻辑清晰且不容易出bug。
3.4 文件上传与图片处理
小程序里图片上传也很常见。前端使用wx.chooseMedia选择图片,然后wx.uploadFile把文件流提交到后端:
wx.uploadFile({ url: BASE_URL + '/api/upload', filePath: tempFilePath, name: 'file', success(res) { const data = JSON.parse(res.data); console.log(data.url); } });后端接收MultipartFile后,保存到服务器指定目录,并在SpringMVC配置里映射静态资源路径。比如图片保存到“/upload”目录,访问地址就是“http://localhost:8080/upload/xxx.jpg”。需要注意Tomcat重启可能清空临时目录,所以绝不要把上传文件放在Tomcat的webapps临时目录下,最好放在一个独立目录,并在配置里写好绝对路径。
4. 配套文档撰写与源码管理的经验
4.1 毕设文档怎么写才容易过
文档是这类项目的另一半价值。很多开发能力不错的同学最后挂在文档上,原因很简单:文档写得像操作手册,缺少分析和设计过程。一份合格的课程设计或毕业设计文档,通常包含这些章节:
- 摘要:两百字左右,说明系统做什么、用了什么技术、达到什么效果
- 需求分析:包括业务背景、用户角色、功能需求、非功能需求
- 总体设计:系统架构图、功能结构图、技术选型说明
- 详细设计:核心类设计、关键业务流程图、接口设计
- 数据库设计:ER图、表结构说明、字段解释
- 系统实现:每个模块的截图和关键代码讲解
- 测试:测试用例表、测试结论
我自己的习惯是:写代码之前先搭好文档框架,每完成一个模块就回填一段,最后只要补测试和总结即可。千万不要等代码全部写完再从头写文档,那样很容易漏掉设计决策,也容易跟实际代码对不上。
数据库设计部分特别重要。评阅老师大概率会先翻数据库表,再看接口定义。每张表最好都有一张字段说明表,主键、外键、索引、默认值都写清楚。哪怕只是复制数据库注释,也比空白强。
4.2 源码目录组织与注释规范
拿到源码包后,我会先看目录结构。好的SSM项目一般长这样:
demo/ ├── pom.xml ├── src/main/java │ └── com/example │ ├── controller │ ├── service │ ├── mapper │ ├── entity │ └── config ├── src/main/resources │ ├── mapper │ ├── applicationContext.xml │ ├── springmvc.xml │ └── jdbc.properties ├── src/main/webapp ├── miniprogram │ ├── pages │ ├── utils │ ├── app.js │ └── app.json └── doc ├── 需求文档.docx ├── 数据库设计.docx └── 接口文档.docx后端按controller、service、mapper、entity分包,前端按功能页面分包,文档单独放一个doc目录。注释不用写太多,但关键方法上一定写清楚“入参出参”和“业务逻辑”,比如“这个方法处理下单并扣减库存”,比满屏的“// new一个对象”有价值得多。
很多二手资源包里的源码,包名乱七八糟,类名拼写错误,甚至还有无用的测试类。遇到这种包,我建议先全局搜索清理掉无关文件,再保留原始结构。交付源码时,保证“下载后能直接导入运行”是第一位的。
4.3 部署与答辩演示注意事项
部署环节最容易在答辩前翻车。有几个实操点我必须单独拎出来说:
第一,数据库导入要完整。source命令执行sql脚本时,如果脚本里带了外键,导入顺序错了会报错。我会把建库、建表、插入数据分开,并用Navicat或MySQL Workbench重跑一遍。
第二,Tomcat部署路径要统一。小程序端baseUrl里写的是/ssm-demo,那后端war包名必须是ssm-demo.war,否则路径404。很多同学改了工程名却忘了改前端地址。
第三,答辩演示前一定要把后端先启动完,小程序从开发者工具中打开,并保证所有页面都能走通。不要在答辩现场第一次跑完整流程,我见过有人在老师面前演示登录接口超时,紧张到话都说不利索。
5. 常见问题与排查实录
5.1 小程序真机预览连不上本地后端
这可能是被问得最多的问题。开发者工具里页面能打开,手机一扫码就白屏或者请求失败。排查步骤按顺序走:
- 手机和电脑必须连接同一个WiFi
- 开发者工具里把BASE_URL从localhost改成电脑的局域网IP
- 关掉电脑防火墙,或者在防火墙规则里放行8080端口
- 在手机浏览器直接访问这个IP+端口,如果能看返回,说明网络通
- 如果后端跑在云服务器,则检查安全组和域名备案情况
一个小技巧:在电脑上执行ipconfig(Windows)或ifconfig(Mac),找无线网卡的IPv4地址,然后把前端baseUrl改成它,再在开发者工具右上角的“详情-本地设置”里勾选“不校验合法域名”。
5.2 数据库连接失败和中文乱码
数据库连接失败,第一反应看异常信息。Access denied for user说明账号或密码不对;Unknown database说明库名打错了;Communications link failure几乎都是MySQL服务没启动,或者端口被占用。排查这些错误时,我习惯先用命令行登录MySQL,确认服务没问题,再回到后端看配置。
中文乱码通常有三个原因:数据库连接URL没有加characterEncoding=utf8,表结构不是utf8mb4,后端响应头没有设置编码。URL写法参考:
jdbc.url=jdbc:mysql://localhost:3306/ssm_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai另外,在web.xml里配置一个CharacterEncodingFilter,强制所有请求和响应都走UTF-8,能解决大部分乱码问题。
5.3 Mapper接口报Invalid bound statement错误
这个错误非常经典,项目能启动,但一调用到某个Mapper方法就报Invalid bound statement (not found)。原因基本只有一个:MyBatis没有扫描到对应的XML文件。检查顺序是:
- Mapper接口和XML文件是否同名,并且namespace写的接口全限定名
- XML里每个语句的id是否和接口方法名一致
- applicationContext.xml里mapper-locations是否配置为
classpath:mapper/*.xml - pom.xml里是否把resources目录下的XML排除了(Maven默认只打包java/resources,如果XML放错目录会被漏掉)
我第一次遇到这个问题时,定位了快两个小时,最后发现是把XML放到了java目录下的Mapper包中,Maven没有把它同步到classes目录。解决办法是在pom.xml的build节点加resources配置,或者直接把XML挪到resources/mapper目录。
5.4 页面样式和顶部导航栏适配
微信小程序的顶部导航栏在不同机型上高度不一样,很多项目在iPhone上正常,换到安卓就出现按钮顶到状态栏的问题。如果使用自定义导航栏,需要调用wx.getWindowInfo()获取状态栏高度和胶囊按钮位置,然后动态计算导航栏高度。
列表页还有一个常见的交互问题:页面数据量多,切换Tab时状态丢失。这时建议在小程序全局data或缓存里保存滚动位置,onShow时恢复。这类细节虽然不直接影响功能评分,但会让演示体验好很多,老师看着也舒服。
做这类“小程序 + SSM”项目,我个人最大的体会是:不要被资源包里的乱码文件名和陌生目录吓住。先跑通一条最简单的登录链路,再往里面填功能,整套系统很快就能站起来。文档和源码不是割裂的,写文档时把接口定义、表结构、页面流程记清楚,后面改代码和答辩都会省力很多。最后分享一个小习惯:每写完一个后端接口,我就在接口文档里追加一条记录,包含请求参数和返回示例,这样等所有功能做完,文档也基本成形了,不用熬夜补材料。