简介:基于Java、SSM、MySQL与微信小程序的中国剪纸小程序毕业设计包,定位为计算机专业毕业设计、课程设计与期末大作业的完整参考项目。压缩包共含815个文件,整体约22兆字节,涵盖后端Java源码、SSM框架配置、小程序前端页面、后台管理界面、图标图片素材、SQL数据库脚本、Markdown说明、论文与答辩演示文档,从数据库到前后端形成完整可运行链路。系统围绕中国剪纸艺术展示与学习场景,提供丰富内容展示、交互操作与便捷管理能力;项目已经导师指导并通过高分评价,严格调试后可下载即用,支持在集成开发环境、微信开发者工具及数据库管理工具中直接部署运行。包内还提供安装、构建、启动脚本以及备份样式、多媒体示例和说明文档,便于对照学习系统架构、数据表设计、接口调用以及前后端交互逻辑。已有88人学习/下载,适合作为毕业设计参考、课程设计选题或二次开发底座。
1. 中国剪纸微信小程序:一套能跑通的毕设全栈资源
每年毕设季都会有人拿着一套 java + ssm + mysql 的微信小程序项目问我:这玩意到底能不能跑通、答辩会不会翻车。这套中国剪纸微信小程序,就是典型的「后端 SSM 守擂、小程序端打前站」的全栈毕设结构——后端用 Spring + SpringMVC + MyBatis 暴露接口,MySQL 存作品、分类、用户和收藏数据,小程序端负责展示剪纸作品、分类检索和收藏。压缩包里除了源码和数据库初始化脚本,还有一篇完整的毕业设计论文,从选题背景写到系统测试,可以直接当论文框架的参照物。适合两类人:一是课程设计想选传统文化主题、又不想从零搭框架的同学;二是想搞明白小程序端 wx.request 到底怎么跟 SSM 后端对接的 Java 初学者。下面我把后端结构、小程序联调、数据库设计和最常见的坑一次讲完。
2. SSM 后端工程结构:从 IDEA 导入到接口跑通的完整路径
拿到压缩包先别急着点运行。先把 src 下面的包结构对着论文第 3 章的「系统设计」看一遍,搞清楚每一层是干什么的,后面查问题才有方向。SSM 项目最大的特点是分层清晰,但你如果不知道哪一层负责什么,出了问题会到处乱翻,浪费一晚上。
2.1 工程目录拆解:三层架构分别管什么
通常导入后看到的目录是这样的,包名可能略有差异,但结构基本一致:
cut-paper-server ├── pom.xml ├── src/main/java/com/cutpaper/ │ ├── controller/ │ │ ├── admin/ # 后台管理控制器,返回 JSP 页面 │ │ └── api/ # 小程序接口控制器,返回 JSON │ ├── service/ │ │ ├── PaperArtService.java │ │ └── impl/PaperArtServiceImpl.java │ ├── mapper/ │ │ ├── PaperArtMapper.java │ │ └── xml/PaperArtMapper.xml │ ├── entity/ # 实体类,对应数据库表 │ └── common/ # Result、PageResult 等公共类 └── src/main/resources/ ├── application.properties └── mapper/ # 部分项目把 MyBatis XML 放这里service 层是事务边界,所有涉及多表操作的逻辑都在这层加@Transactional。mapper 层只做单表查询,复杂查询靠 XML 里的 SQL 完成。这里有个细节:admin 和 api 两个 controller 分开是很规范的做法。admin 返回 JSP 页面走 ModelAndView,api 返回 JSON 走@ResponseBody,混在一起的话权限区分和代码阅读都会很费劲。毕设答辩时老师问「你的接口怎么设计的」,顺着这个分层讲就够清晰了。
2.2 关键配置:数据源、MyBatis 映射与端口
核心配置集中在 application.properties 或者 jdbc.properties 里,重点看这几个参数:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/cut_paper?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.cutpaper.entity server.port=8080 server.servlet.context-path=/cutpaperdriver-class-name 是第一个坑点:如果你的 MySQL 是 5.7,用com.mysql.jdbc.Driver没问题;如果是 MySQL 8,得换成com.mysql.cj.jdbc.Driver,否则启动时直接报 ClassNotFoundException。url 里的serverTimezone=Asia/Shanghai是 MySQL 8 的强制要求,5.7 无所谓。context-path=/cutpaper决定了小程序端接口地址是http://localhost:8080/cutpaper/api/...而不是http://localhost:8080/api/...,这个前后端必须对齐,后面联调 404 多半就是这里不一致。
2.3 从 IDEA 导入到启动:三步把它跑起来
第一,IDEA 里 File → Open,直接选压缩包解压后的pom.xml,以 Maven 工程导入,等右下角依赖下载完。第二,在 MySQL 里执行 sql 目录下的初始化脚本,建库建表灌数据,具体步骤下一章说。第三,改掉 application.properties 里的数据库账号密码,配置 Tomcat 8.5,把项目以 war exploded 方式部署,启动后浏览器访问http://localhost:8080/cutpaper,能看到后台登录页就算后端活了。
我一般会先启动一次后端,不急着连小程序。后端能起来,说明依赖、配置、数据库三件事都对了,后面小程序联调出错时,问题范围就缩小到接口层。Tomcat 建议用 8.5,SSM 这种 Java 8 时代的老项目配 Tomcat 8.5 最稳,Tomcat 9 偶尔会出现 JSP 编译兼容问题。
2.4 接口自测:先确认返回结构再碰小程序
后端起来后,先用 Postman 或者 curl 打一个接口,确认返回格式符合预期。以小程序登录接口为例:
curl -X POST http://localhost:8080/cutpaper/api/user/login \ -H "Content-Type: application/json" \ -d '{"code": "test_code"}'{ "code": 0, "message": "success", "data": { "token": "a1b2c3", "userInfo": { "nickname": "测试用户" } } }这个code / message / data三段式结构是这套项目里最常见的约定,小程序端的 request 封装就是按这个结构写的。code 为 0 表示成功,非 0 表示业务异常,message 是给前端 toast 用的。自测这一步最大的价值在于:确定后端返回结构没变,再去看小程序端,问题就不会两头猜。
3. 小程序端联调:wx.request 封装与登录态处理的完整链路
后端接口跑通后,剩下的事都在小程序端。小程序端目录结构不复杂,但要搞明白每个页面的职责,以及页面之间怎么跳转传参。这套剪纸项目的功能闭环是:首页看推荐 → 分类页筛选 → 详情页看大图和文字介绍 → 收藏和留言。所有数据都来自后端,前端本身不存业务数据。
3.1 小程序目录与页面职责
cut-paper-miniapp ├── app.json # 页面注册、tabBar 配置 ├── app.js # 全局登录逻辑 ├── utils/ │ └── request.js # wx.request 的 Promise 封装 ├── pages/ │ ├── index/ # 首页:轮播图 + 分类入口 + 推荐列表 │ ├── category/ # 分类页:左侧分类、右侧作品列表 │ ├── detail/ # 详情页:作品大图、介绍、收藏按钮 │ ├── favorite/ # 收藏页:收藏的作品列表 │ └── user/ # 个人中心:用户信息和操作入口app.json 里 pages 数组的第一个元素就是首页,所以 index 要放在最前面。tabBar 如果有,index、category、user 是常见的三个 tab。这里有个容易被忽略的点:收藏页如果不是 tab,跳过去之后要保证左上角有返回按钮,否则用户会卡死在收藏页——app.json 里页面注册顺序和页面间 navigateBack 的行为有关,建议把收藏页放在 detail 之后注册,这样返回层级更自然。
3.2 封装 wx.request:统一 baseUrl 和错误处理
小程序里不能直接用 axios,原生的 wx.request 写法啰嗦且回调嵌套深。我一般会先在 utils/request.js 里封装一层,让所有页面调接口时只关心业务数据:
const BASE_URL = 'http://localhost:8080/cutpaper' function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json' }, success(res) { if (res.data.code === 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.message, icon: 'none' }) reject(res.data) } }, fail(err) { wx.showToast({ title: '网络请求失败', icon: 'none' }) reject(err) } }) }) } module.exports = { request, BASE_URL }用 Promise 封装的好处是页面里可以配合 async/await 写同步感代码。BASE_URL 里的/cutpaper就是后端 context-path,两边必须严格一致。header 里的 Content-Type 用 application/json,后端接口如果用的是@RequestBody接收,这个必须对上;如果后端是接收普通表单参数,这里就得改成表单格式。判断依据很简单:看一眼 api Controller 里对应方法的参数声明。
3.3 首页数据对接:轮播图和推荐列表
首页的逻辑一般是 onLoad 里并发请求两个接口:分类列表和推荐作品。封装好 request 后,页面代码干净很多:
const { request } = require('../../utils/request') Page({ data: { categories: [], arts: [], loading: true }, onLoad() { this.loadHomeData() }, async loadHomeData() { try { const categories = await request('/api/category/list') const arts = await request('/api/art/recommend') this.setData({ categories, arts, loading: false }) } catch (e) { this.setData({ loading: false }) } } })这段代码里有两个容易翻车的地方。一是 loading 状态必须在 try 和 catch 里都置为 false,否则接口异常时页面会一直转圈。二是 setData 一次传全部数据,数据量小没问题;如果作品列表有几十张图,建议分页加载,onReachBottom 里再调一次带页码的接口。推荐接口如果后端没做,可以直接用列表接口加orderBy create_time desc limit 6代替,这个在 mapper XML 里改一下就行。
3.4 登录态:wx.login 换 openid 并维护会话
小程序端没有传统密码登录,而是通过 wx.login 拿临时 code,后端拿这个 code 去微信服务器换 openid,再生成一个业务 token 返回:
wx.login({ success: async (res) => { if (res.code) { try { const data = await request('/api/user/login', 'POST', { code: res.code }) wx.setStorageSync('token', data.token) wx.setStorageSync('userInfo', data.userInfo) } catch (e) { console.error('登录失败', e) } } } })这段逻辑通常放在 app.js 的 onLaunch 里,保证小程序打开时就完成登录。这里有个关键认知:openid 是用户在小程序里的唯一标识,但后端不能把 openid 当 token 用,因为小程序端每次 request 都带 openid 相当于裸奔。常见做法是后端生成一个随机 token 存到数据库或者 Redis,小程序端存 storage,每次请求带上。毕设项目用数据库表存 token 就够了,把 token 和 userId 做一张表,Redis 那套可以在答辩时作为优化方向提一句。收藏功能判断「当前用户是否已收藏」,就是拿 token 换 userId,再去 favorite 表查记录。appid 和 secret 配置在后端的 application.properties 或单独配置类里。
4. MySQL 表结构设计:五张核心表撑起整个业务闭环
数据库这块是毕设论文里最好写的部分,也是答辩老师最爱问的部分。这套剪纸项目的表数量不算多,但每一张表都有明确职责,并且能支撑从浏览到收藏到留言的完整路径。我拆过不少毕设项目,表之间的外键关系、字段注释、索引设计,基本决定了代码写起来顺不顺手。
4.1 核心表设计:字段、类型与用途
| 表名 | 关键字段 | 用途 |
|---|---|---|
| admin | id, username, password | 后台管理端登录 |
| user | id, openid, nickname, avatar_url, create_time | 小程序用户,openid 唯一 |
| category | id, name, sort | 剪纸分类,如人物、花鸟、民俗 |
| paper_art | id, category_id, title, image_url, description, detail, create_time | 剪纸作品核心表 |
| favorite | id, user_id, art_id, create_time | 收藏关系表 |
| comment | id, user_id, art_id, content, create_time | 用户留言评论 |
这六张表里,favorite 是典型的多对多中间表。一个用户能收藏多个作品,一个作品能被多个用户收藏,所以不把 user_id 和 art_id 直接塞进任何一张业务表,而是单独拆出来。comment 表同理,是 user 和 paper_art 之间的中间表。答辩时老师问「多对多关系怎么设计」,favorite 这张表就是现成的答案。
4.2 初始化脚本:先建库再建表最后灌数据
压缩包里的 sql 脚本一般包含建库、建表、插入示例数据三个部分。如果按顺序执行还是报错,多半是重复执行导致表已存在,脚本里缺了 DROP TABLE IF EXISTS。典型的建表和初始数据长这样:
CREATE DATABASE IF NOT EXISTS cut_paper DEFAULT CHARACTER SET utf8mb4; USE cut_paper; CREATE TABLE category ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT '分类名,如人物、花鸟', sort INT DEFAULT 0 COMMENT '排序值,越小越靠前' ); CREATE TABLE paper_art ( id INT AUTO_INCREMENT PRIMARY KEY, category_id INT NOT NULL, title VARCHAR(100) NOT NULL, image_url VARCHAR(255), description TEXT, detail TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category (category_id) ); INSERT INTO category (name, sort) VALUES ('人物', 1), ('花鸟', 2), ('民俗', 3); INSERT INTO paper_art (category_id, title, image_url) VALUES (1, '仕女图', '/upload/girl.jpg'), (2, '喜鹊登梅', '/upload/magpie.jpg');字符集用 utf8mb4 而不是 utf8,原因很简单:用户昵称和留言里可能出现 emoji,utf8 存不下会报 Incorrect string value 的错误,utf8mb4 是 utf8 的超集,专门为四字节字符设计的。category_id 我建议加索引,因为列表页的联表查询是按分类查作品,没有索引的话数据量一上去就会慢。脚本执行用 Navicat 或者命令行source /path/script.sql都行,执行完跑一下SELECT * FROM paper_art;确认有数据再往后端走。
4.3 表关系与论文 ER 图的对应
表设计完,论文里的 ER 图就按下面这个关系画:category 与 paper_art 是一对多,一个分类下有多个作品;user 与 favorite 是一对多,一个用户有多条收藏记录;paper_art 与 favorite 也是一对多。简单说,两张业务表加一张中间表,就是一套标准的关系模型。
写论文时要注意一个细节:实体类里的字段和表字段不能对不上。比如 paper_art 表的 detail 字段如果用来存长文本,实体类里就要有对应的 detail 属性;如果代码里写的是 content,那 SQL 查询和属性映射就会报错。拿到源码后先对照实体类过一遍字段,这是花十分钟能避免的瞎折腾。
5. 避坑指南:SSM 加小程序联调里最常翻车的五个现场
这套组合的坑相当固定,而且几乎每个都有人踩。我把这几年在毕设项目里见过最多的问题按「现象 → 原因 → 解决」列出来,你照着排查,基本十分钟内能定位问题。
5.1 后端启动了但数据库连不上,报 SSL 或时区错误
现象:Tomcat 启动报错,日志里出现Communications link failure或者The server time zone value is unrecognized,有时候还带着 SSL 相关的 warning。
原因:绝大多数情况是 MySQL 版本与驱动不匹配。MySQL 5.6 以前用com.mysql.jdbc.Driver,MySQL 8 必须换驱动类名并加时区参数,而且 MySQL 8 默认开启了 SSL 校验,连接 url 里不关 SSL 就可能握手失败。
解决:按你的 MySQL 版本改 application.properties。MySQL 8 的驱动类名换成com.mysql.cj.jdbc.Driver,url 最后加上serverTimezone=Asia/Shanghai&useSSL=false。顺便说一句,如果你是按 mysql 安装配置教程装的 MySQL 8,pom.xml 里的 mysql-connector-java 版本也要升到 8.x,只改驱动字符串不改依赖版本,照样报 ClassNotFoundException。
5.2 小程序请求一直 404,后端后台页面却正常
现象:小程序开发者工具里 console 显示 request fail,或者后端日志根本没收到请求,返回 404。
原因:八成是 baseUrl 里的 context-path 对不上。后端配置是/cutpaper,小程序端 BASE_URL 却写成了http://localhost:8080;或者反过来,后端 context-path 是/,小程序端多写了一个/cutpaper。另一种情况是 Tomcat 部署名称带了版本号,比如cut-paper-server.war部署后自动生成的路径是/cut-paper-server。
解决:先在后端浏览器访问http://localhost:8080/cutpaper/api/category/list,能出 JSON 说明后端没问题。然后检查小程序端 BASE_URL,让它和后端 context-path 完全相等。注意开发者工具里如果不勾选「不校验合法域名」,http 的本地接口会被拦截,这个能用但上线时必须改成 https 并配合法域名。
5.3 接口返回中文乱码,从后端一路乱到小程序
现象:后台页面正常,小程序详情页里的剪纸介绍文字全部变成乱码,或者后端控制台打印的 SQL 查询结果中文是问号。
原因:三层编码不一致。Tomcat 默认 URI 编码是 ISO-8859-1,SpringMVC 没配 CharacterEncodingFilter,数据库连接 url 没带 characterEncoding=UTF-8,这三处只要有一处不对,中文就乱。
解决:三处统一。application.properties 的 url 加characterEncoding=utf8;SpringMVC 配置类里加 CharacterEncodingFilter,forceEncoding 设为 true;Tomcat 的 server.xml 里 Connector 加URIEncoding="UTF-8"。改完一定要重启 Tomcat,Filter 配置是启动时加载的,热部署不会生效。
5.4 登录接口报错,openid 拿到 null 或者直接返 401
现象:wx.login 成功拿到了 code,但后端登录接口返回失败,日志显示 code 换取 openid 时接口返回 errcode 40013 或者 invalid code。
原因:appid 或 secret 配错是最常见的,其次是因为侧把 code 传给了后端,这个 code 只能用一次,调试时前端刷新一次页面旧 code 就会被丢掉。还有一个隐蔽问题:同一个小程序账号如果绑定了多个环境,appid 一致但 secret 不同,后端配成了别的环境的密钥。
解决:去小程序后台确认 appid 和 secret 是否与后端配置一致。前端排查时,在 wx.login 的 success 回调里先 console.log 出来看看 res.code 是否存在,确认后再传给后端。后端排查时,把请求微信接口返回的完整错误信息打出来,40013 一般是 appid 问题,40029 是 code 无效,对症下手。建议后端接口做个超时重试,微信接口偶尔会抖动。
5.5 分页数据不对,PageHelper 分页失效或 count 查询报错
现象:列表页一直只显示第一页的数据,或者翻页时总数统计错误,SQL 日志里 count 语句执行报错。
原因:PageHelper 版本与 MyBatis 版本冲突,或者 PageHelper 拦截器没有在 MyBatis 配置中正确注册。这个坑特别隐蔽,因为项目不报错,只是分页逻辑不对,看起来像前端参数传错了。
解决:pom.xml 里确认 pagehelper 版本,5.x 版本通常需要在 Spring 配置里手动注册 PageInterceptor,并且放在 SqlSessionFactoryBean 的 plugins 属性里;6.x 以上版本如果配合 mybatis-spring-boot-starter,配置方式又不一样。检查参数传递时注意 PageHelper.startPage 必须紧跟要分页的查询语句,中间不能夹其他查询,否则分页条件会落在错误的 SQL 上。
6. 上线前核验清单:让毕设从「能跑」变成「答辩稳」
能本地跑通和答辩现场顺利演示是两回事,差的往往不是技术,而是几个你在自己电脑上永远不会遇到的问题。我交付这类毕设项目前,会强制自己走一遍核验清单,每条都是真实翻车换来的。
| 核验项 | 操作 | 判定标准 |
|---|---|---|
| 数据可重建 | 删掉数据库,重新执行初始化脚本 | 全流程无报错,示例数据完整 |
| 账号可登录 | 用测试微信号走一遍登录、收藏、留言 | 接口全部返回 code=0 |
| 演示路径完整 | 从首页点进详情再到个人中心,录屏走三遍 | 无白屏、无接口超时 |
| 后端可重启 | 关闭 Tomcat 重新启动,再走一遍核心流程 | 数据不丢,登录态正常 |
| 论文代码一致 | 论文里的表结构、接口列表与源码逐一对照 | 无缺表、无接口名不一致 |
数据库重建这一条最值得花时间。答辩教室的电脑大概率没有你本地的 MySQL 配置,评委可能让你现场重新部署,脚本跑不通就是灾难。论文一致性也容易忽略:论文里写了收藏功能,源码里却没有 favorite 表,老师翻到就直接扣分。把那三个示例作品换成剪纸主题的图片,演示时视觉效果会好很多。
我当年交毕设前没检查脚本可重建性,现场部署时初始化脚本执行到一半报错,那条数据恰好是首页推荐位依赖的,页面白屏了五分钟。从那以后我每次交付毕设都强制走一遍这套核验清单,前后花不到半小时,但能让整个答辩过程稳很多。希望帮到你。
本文还有配套的精品资源,点击获取