☰
SSM+微信小程序剪纸毕设项目实战:从环境搭建到答辩避坑
2026/10/26 7:55:33 网站建设 项目流程

简介:这是一套面向高校计算机相关专业学生的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 后端加微信小程序前端的作品。我带过几届学生的毕设,发现选剪纸、皮影、年画这类传统文化题材的项目,答辩时老师天然有好感,但前提是你的代码得真能跑起来,而不是只交一份文档。

这个方案的核心链路很清晰:微信小程序负责展示剪纸作品、分类浏览、详情查看和收藏;SSM(Spring + SpringMVC + MyBatis)负责提供 REST 接口、管理作品数据和用户信息;MySQL 存数据。配套的开题报告、任务书、PPT 和演示视频,是毕业设计流程里的硬性交付物,缺一项都可能被退回。适合的人群很明确——计算机相关专业、需要在一个学期内完成一个“有前端有后端有数据库”的完整项目、并且希望答辩时能演示真实交互的本科生。

我见过太多人把时间花在写文档上,结果代码跑不起来,答辩现场翻车。所以这篇笔记的重点放在:怎么把这个项目的骨架搭起来、SSM 和微信小程序怎么对接、哪些参数必须提前定好、以及那些我踩过的坑。

2. 环境与工程结构:先把地基打对

2.1 后端技术栈的版本选择与理由

SSM 这套组合虽然老,但毕业设计场景下它反而比 Spring Boot 更“安全”。原因有两个:一是学校机房或答辩电脑的 JDK 版本往往停留在 JDK 8,Spring 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 还要加serverTimezone=Asia/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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 剪纸作品表 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 分类表 CREATE TABLE `category` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL, `sort_order` INT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 收藏表 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字符集必须用utf8mb4,剪纸作品的描述里可能有生僻字或特殊符号,utf8存不进去。收藏表的唯一索引是为了防止重复收藏,这个约束放在数据库层比放在代码层可靠。

提示:建表语句建议单独存一个init.sql文件,答辩前在干净环境里跑一遍,确认没有语法错误。

3. SSM 后端接口开发:从配置到第一个可用接口

3.1 Spring + MyBatis 整合的关键配置

SSM 整合最容易翻车的地方是 XML 配置的路径和包扫描。我习惯把配置拆成三个文件:applicationContext.xml(Spring 核心)、spring-mvc.xml(MVC 相关)、mybatis-config.xml(MyBatis 设置),然后在web.xml里加载。

applicationContext.xml里数据源和 MyBatis 的整合:

<!-- 数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/paper_cut?useUnicode=true&amp;characterEncoding=utf8mb4"/> <property name="username" value="root"/> <property name="password" value="你的密码"/> </bean> <!-- SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.papercut.entity"/> </bean> <!-- Mapper 扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.papercut.mapper"/> </bean>

mapperLocations的路径必须和实际文件位置一致,我见过有人把 XML 放在src/main/java下,Maven 打包时不会复制过去,运行时直接报Invalid bound statement。正确做法是放在src/main/resources/mapper/下。

web.xml里要注册 Spring 的ContextLoaderListener和 SpringMVC 的DispatcherServlet,并且注意contextConfigLocation参数要指向classpath:applicationContext.xml。如果两个配置文件都叫applicationContext.xml,SpringMVC 会覆盖 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) { PageInfo<PaperCut> 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 PageInfo<PaperCut> listByCategory(Integer page, Integer size, Integer categoryId) { PageHelper.startPage(page, size); List<PaperCut> list = paperCutMapper.selectByCategory(categoryId); return new PageInfo<>(list); } }

PageHelper 的startPage必须紧跟在查询方法之前,中间不能插入其他数据库操作,否则分页会作用到错误的查询上。这个坑我踩过,表现为返回的数据条数不对,但 SQL 日志看起来又没问题。

Mapper XML 里写动态 SQL:

<select id="selectByCategory" resultType="com.papercut.entity.PaperCut"> SELECT id, title, category_id, cover_img, description, create_time FROM paper_cut <where> <if test="categoryId != null"> category_id = #{categoryId} </if> </where> ORDER BY create_time DESC </select>

<where>标签会自动处理第一个AND,比手写WHERE 1=1干净。resultType里的字段名要和数据库列名对应,如果开启了驼峰映射(mapUnderscoreToCamelCase=true),category_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_type=authorization_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/**" location="file:/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 没打包进去。提前走一遍,比任何后悔药都管用。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询