
简介这是一套面向高校计算机相关专业学生的Java毕业设计完整项目以中国剪纸为主题采用SSM框架搭配微信小程序实现适合需要完成毕业设计或课程设计、希望直接获得可运行高分项目的同学。资源包共722个文件约28.1MB包含102个Java后端源码、101个Vue前端组件、21个WXML与22个WXSS小程序页面文件以及SQL数据库脚本、PPT演示文稿、开题报告、任务书、使用文档和演示视频另附bat启动脚本与properties配置文件覆盖从开发到答辩的完整链路。该项目已通过导师指导与答辩评审获得97分在Windows 10/11环境下严格调试下载即可运行部署教程齐全。已有115人学习下载读者可借此快速理解SSM与微信小程序的整合方式、掌握剪纸主题业务模块的设计思路并直接复用文档与视频完成答辩准备。1. 从一份能跑通的 SSM微信小程序剪纸项目说起很多同学做毕业设计时选题卡在“有文化味但技术不空”的交叉点上。中国剪纸这个题目恰好踩中了这个需求它既有非遗题材的叙事空间又能落地成一个完整的 SSM 后端加微信小程序前端的作品。我带过几届学生的毕设发现选剪纸、皮影、年画这类传统文化题材的项目答辩时老师天然有好感但前提是你的代码得真能跑起来而不是只交一份文档。这个方案的核心链路很清晰微信小程序负责展示剪纸作品、分类浏览、详情查看和收藏SSMSpring SpringMVC MyBatis负责提供 REST 接口、管理作品数据和用户信息MySQL 存数据。配套的开题报告、任务书、PPT 和演示视频是毕业设计流程里的硬性交付物缺一项都可能被退回。适合的人群很明确——计算机相关专业、需要在一个学期内完成一个“有前端有后端有数据库”的完整项目、并且希望答辩时能演示真实交互的本科生。我见过太多人把时间花在写文档上结果代码跑不起来答辩现场翻车。所以这篇笔记的重点放在怎么把这个项目的骨架搭起来、SSM 和微信小程序怎么对接、哪些参数必须提前定好、以及那些我踩过的坑。2. 环境与工程结构先把地基打对2.1 后端技术栈的版本选择与理由SSM 这套组合虽然老但毕业设计场景下它反而比 Spring Boot 更“安全”。原因有两个一是学校机房或答辩电脑的 JDK 版本往往停留在 JDK 8Spring Boot 2.x 以上对 JDK 版本有要求容易在答辩现场启动失败二是 SSM 的 XML 配置虽然繁琐但每一步都看得见老师问起来你能说清楚“这个 Bean 是在哪里注册的”。我一般会锁定这套版本组合组件版本说明JDK1.8答辩环境兼容性最好Spring5.3.x与 JDK 8 稳定搭配SpringMVC5.3.x与 Spring 同版本MyBatis3.5.x避免 3.4 以下的分页插件兼容问题MySQL5.7 或 8.08.0 需要改驱动类名和时区Druid1.2.x连接池比 C3P0 稳定Maven3.6依赖管理MySQL 8.0 的坑在于驱动类从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver连接 URL 还要加serverTimezoneAsia/Shanghai否则启动就报时区错误。如果你不确定答辩电脑装的是哪个版本直接在pom.xml里用 MySQL 5.7 的驱动兼容性更稳。2.2 微信小程序端的目录约定小程序端我建议用原生开发不要上 uni-app 或 Taro。毕业设计答辩时老师大概率会问你“这个页面是怎么渲染的”原生 WXML WXSS 你能直接指着代码说清楚跨端框架反而多一层编译解释成本高。目录结构按功能划分miniprogram/ ├── pages/ │ ├── index/ # 首页轮播 分类入口 │ ├── list/ # 作品列表按分类筛选 │ ├── detail/ # 作品详情图片 介绍 收藏 │ ├── favorite/ # 我的收藏 │ └── mine/ # 个人中心 ├── utils/ │ ├── request.js # 封装 wx.request │ └── auth.js # 登录态管理 ├── images/ # 静态图标 └── app.js # 全局配置request.js里要统一处理 baseURL 和 token 注入不然后面每个页面都写一遍wx.request改接口地址时会想哭。2.3 数据库表设计的最小集合剪纸项目不需要几十张表核心四张就够-- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE NOT NULL COMMENT 微信openid, nickname VARCHAR(64) DEFAULT , avatar VARCHAR(255) DEFAULT , create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 剪纸作品表 CREATE TABLE paper_cut ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL, category_id INT NOT NULL, cover_img VARCHAR(255) NOT NULL, detail_img TEXT, description TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分类表 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, sort_order INT DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 收藏表 CREATE TABLE favorite ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, paper_cut_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_paper (user_id, paper_cut_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字符集必须用utf8mb4剪纸作品的描述里可能有生僻字或特殊符号utf8存不进去。收藏表的唯一索引是为了防止重复收藏这个约束放在数据库层比放在代码层可靠。提示建表语句建议单独存一个init.sql文件答辩前在干净环境里跑一遍确认没有语法错误。3. SSM 后端接口开发从配置到第一个可用接口3.1 Spring MyBatis 整合的关键配置SSM 整合最容易翻车的地方是 XML 配置的路径和包扫描。我习惯把配置拆成三个文件applicationContext.xmlSpring 核心、spring-mvc.xmlMVC 相关、mybatis-config.xmlMyBatis 设置然后在web.xml里加载。applicationContext.xml里数据源和 MyBatis 的整合!-- 数据源 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/paper_cut?useUnicodetrueamp;characterEncodingutf8mb4/ property nameusername valueroot/ property namepassword value你的密码/ /bean !-- SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.papercut.entity/ /bean !-- Mapper 扫描 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.papercut.mapper/ /beanmapperLocations的路径必须和实际文件位置一致我见过有人把 XML 放在src/main/java下Maven 打包时不会复制过去运行时直接报Invalid bound statement。正确做法是放在src/main/resources/mapper/下。web.xml里要注册 Spring 的ContextLoaderListener和 SpringMVC 的DispatcherServlet并且注意contextConfigLocation参数要指向classpath:applicationContext.xml。如果两个配置文件都叫applicationContext.xmlSpringMVC 会覆盖 Spring 的上下文导致 Service 注入失败。3.2 剪纸作品列表接口的实现以“按分类查询作品列表”为例完整走一遍 Controller → Service → Mapper 的链路。Controller 层RestController RequestMapping(/api/papercut) public class PaperCutController { Autowired private PaperCutService paperCutService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer categoryId) { PageInfoPaperCut pageInfo paperCutService.listByCategory(page, size, categoryId); return Result.success(pageInfo); } }Result是统一返回体包含code、msg、data三个字段。小程序端根据code判断请求是否成功不要直接返回裸对象否则前端处理错误时很乱。Service 实现里用 PageHelper 做分页Service public class PaperCutServiceImpl implements PaperCutService { Autowired private PaperCutMapper paperCutMapper; Override public PageInfoPaperCut listByCategory(Integer page, Integer size, Integer categoryId) { PageHelper.startPage(page, size); ListPaperCut list paperCutMapper.selectByCategory(categoryId); return new PageInfo(list); } }PageHelper 的startPage必须紧跟在查询方法之前中间不能插入其他数据库操作否则分页会作用到错误的查询上。这个坑我踩过表现为返回的数据条数不对但 SQL 日志看起来又没问题。Mapper XML 里写动态 SQLselect idselectByCategory resultTypecom.papercut.entity.PaperCut SELECT id, title, category_id, cover_img, description, create_time FROM paper_cut where if testcategoryId ! null category_id #{categoryId} /if /where ORDER BY create_time DESC /selectwhere标签会自动处理第一个AND比手写WHERE 11干净。resultType里的字段名要和数据库列名对应如果开启了驼峰映射mapUnderscoreToCamelCasetruecategory_id会自动映射到categoryId。3.3 微信登录与 openid 获取小程序端调用wx.login()拿到 code传给后端后端用 code 换 openid。这个接口不需要用户授权静默完成。后端接口PostMapping(/login) public Result login(RequestBody LoginDTO loginDTO) { String url https://api.weixin.qq.com/sns/jscode2session; // 用 RestTemplate 或 HttpClient 发起请求 // 参数appid, secret, js_code, grant_typeauthorization_code // 返回 openid 和 session_key // 根据 openid 查用户不存在则插入 return Result.success(user); }appid和secret写在配置文件里不要硬编码在 Java 代码中。答辩时老师如果问“你的密钥怎么管理的”你能说出“放在 properties 文件里通过 PropertyPlaceholderConfigurer 加载”就是一个加分项。注意session_key不要返回给小程序端它只在服务端使用。小程序端只需要拿到一个自定义的 token比如用 JWT 生成后续请求带上这个 token 即可。4. 微信小程序端对接页面、请求与状态管理4.1 封装 wx.request 与统一错误处理原生wx.request是回调式的每个页面写一遍容易漏掉错误处理。我一般封装成 Promise// utils/request.js const BASE_URL http://localhost:8080/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, token: wx.getStorageSync(token) || }, success(res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { // token 过期重新登录 wx.removeStorageSync(token); reject(new Error(未登录)); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(new Error(res.data.msg)); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };BASE_URL在开发时指向本地答辩前要改成局域网 IP因为手机预览时localhost指向的是手机本身。这个细节很多人忽略导致真机调试时请求全部失败。4.2 作品列表页的下拉刷新与触底加载列表页用scroll-view或页面级的onReachBottom都可以。我倾向用页面级代码更简洁Page({ data: { list: [], page: 1, size: 10, hasMore: true, categoryId: null }, onLoad(options) { if (options.categoryId) { this.setData({ categoryId: options.categoryId }); } this.loadList(); }, loadList() { if (!this.data.hasMore) return; request({ url: /papercut/list, data: { page: this.data.page, size: this.data.size, categoryId: this.data.categoryId } }).then(data { this.setData({ list: this.data.list.concat(data.list), page: this.data.page 1, hasMore: data.hasNextPage }); }); }, onReachBottom() { this.loadList(); }, onPullDownRefresh() { this.setData({ list: [], page: 1, hasMore: true }); this.loadList(); wx.stopPullDownRefresh(); } });hasNextPage是 PageHelper 的PageInfo自带的字段直接拿来判断有没有下一页不用自己算。下拉刷新时要重置page和list否则数据会重复。4.3 收藏功能与登录态校验收藏按钮点击时先检查本地有没有 token没有就触发登录流程onFavorite() { const token wx.getStorageSync(token); if (!token) { this.doLogin(); return; } request({ url: /favorite/toggle, method: POST, data: { paperCutId: this.data.detail.id } }).then(() { wx.showToast({ title: 操作成功, icon: success }); this.setData({ detail.isFavorited: !this.data.detail.isFavorited }); }); }doLogin里调用wx.login拿 code再请求后端/login接口换 token存到wx.setStorageSync(token, token)。整个流程不需要用户点击授权按钮体验更顺。提示微信小程序对wx.login的调用频率没有严格限制但不要在每个页面onShow里都调一次放在app.js的onLaunch里做一次静默登录即可。5. 避坑与排查那些答辩前容易翻车的地方5.1 跨域问题在小程序里不存在但真机调试有别的坑小程序不走浏览器同源策略所以后端不需要配 CORS。但真机调试时如果后端跑在localhost手机访问不到。解决办法是把后端部署到局域网某台机器上BASE_URL改成那台机器的 IP比如http://192.168.1.100:8080/api。同时确认防火墙放行了 8080 端口。5.2 MyBatis 的Invalid bound statement报错现象启动不报错调用接口时抛org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。原因Mapper XML 文件没有被编译到target/classes下。常见于 XML 放在src/main/java目录里Maven 默认不复制该目录下的非 Java 文件。解决把 XML 移到src/main/resources/mapper/下或者在pom.xml的build里加resources配置把src/main/java下的 XML 也包含进去。5.3 微信开发者工具能跑真机白屏现象模拟器上一切正常扫码真机预览时页面空白。原因多半是BASE_URL还是localhost或者后端服务没启动。另外检查小程序后台有没有配置合法域名——开发阶段可以在开发者工具里勾选“不校验合法域名”但真机预览时这个选项默认不生效需要在手机端打开调试模式。解决真机调试时在微信里打开小程序点击右上角菜单 → 开发调试打开调试模式然后重新进入小程序。这样就能跳过域名校验。5.4 图片上传后显示 404现象后台上传的剪纸图片小程序端image组件加载不出来。原因图片存在服务器本地磁盘但后端没有配置静态资源映射。SpringMVC 默认不处理静态资源请求。解决在spring-mvc.xml里加mvc:resources mapping/upload/** locationfile:/data/upload//location后面的路径要以file:开头指向实际存储目录。小程序端拼接完整 URL 时用http://你的IP:8080/upload/xxx.jpg。5.5 答辩现场数据库连不上现象在自己电脑上跑得好好的到答辩教室启动 Tomcat 就报Communications link failure。原因答辩电脑的 MySQL 密码和你本地不一样或者 MySQL 服务根本没启动。解决提前准备一个init.sql到现场先建库建表然后改applicationContext.xml里的密码。更稳妥的做法是带一个免安装的 MySQL 绿色版数据文件一起拷过去启动就能用。6. 让项目从“能跑”到“能拿高分”的几个技巧答辩老师看一个毕业设计第一眼看的是完成度第二眼看的是你有没有自己的思考。剪纸这个题材如果你只做了增删改查分数不会低但也不会高。我一般会建议学生在最后阶段加两个小功能成本不高但效果明显。第一个是作品详情页的“相关推荐”。不用上协同过滤就按同分类下随机取 3 条SQL 里加ORDER BY RAND() LIMIT 3就行。答辩时你可以说“这里做了一个基于内容的简单推荐后续可以扩展成协同过滤”老师会觉得你有延伸思考。第二个是后台管理端的作品上传支持多图。剪纸作品往往有多个角度或细节图详情页用swiper展示多张图比单图看起来完整得多。实现上就是在paper_cut表加一个detail_img字段存 JSON 数组前端解析后渲染。验证项目是否真的可交付我习惯用这个清单过一遍检查项通过标准后端启动Tomcat 无报错日志出现Starting ProtocolHandler数据库连接能查到 category 表的数据小程序首页轮播图和分类入口正常显示列表页下拉刷新、触底加载、分类筛选都正常详情页图片加载、收藏按钮状态正确登录清除缓存后重新进入能自动静默登录真机手机预览无白屏图片能加载最后说一个我自己的习惯答辩前一天把整个项目从零部署一遍——新建数据库、导入 SQL、启动后端、编译小程序。这个过程会暴露 90% 的现场问题。我见过太多人觉得自己项目没问题结果答辩时发现数据库密码忘了改或者 Mapper XML 没打包进去。提前走一遍比任何后悔药都管用。希望帮到你。本文还有配套的精品资源点击获取