ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSM+微信小程序剪纸展示系统毕设实战:从数据库到接口全流程

SSM+微信小程序剪纸展示系统毕设实战:从数据库到接口全流程 简介面向Java毕业设计与课程设计的中国剪纸主题项目基于SSM框架与微信小程序开发聚焦非遗文化展示、在线浏览、后台管理等典型业务场景可用于毕业设计选题参考、课程设计实践或SSM与小程序开发学习。资源共722个文件包含102个java后端源码、101个vue前端页面、小程序wxml/wxss界面文件、sql数据库脚本、pptx答辩PPT、mp4演示视频以及开题报告、任务书、使用文档等多种结课材料压缩包仅28.1MB文件结构清晰便于下载与本地部署。项目已在Windows10/11环境严格调试附部署脚本和教程下载后可按步骤启动运行整体完成度高已获导师认可答辩评审得分97分。目前已有115人学习下载适合需要完整可运行参考源码的学生也可作为课程设计或SSM微信小程序开发的模板项目便于二次开发学习。1. 毕设季最常见的“剪纸”难题SSM 后端和小程序前端怎么捏到一起每年毕设季都会看到一批类似选题非遗文化展示、地方手工艺推广、线上博物馆。中国剪纸是其中出现频率很高的一个因为它既有文化厚度又有天然的图片展示需求。但真正动手做的时候很多人卡在了同一个地方后端用的是 SSM前端是微信小程序两套技术栈之间怎么把数据、图片、登录态串起来网上能找到的完整案例又少。这个项目标题里给到的是一套已经打包好的完整资源——源码、数据库、PPT、开题报告、任务书、使用文档、演示视频都在属于那种“拿来就能跑、跑完能讲清楚”的高分毕设模板。它适合三类人想快速搭起一个可演示系统的本科生、想把非遗展示类项目改成自己课题的人、以及需要一套前后端分离练手案例的 Java 学习者。这一篇不聊 PPT 怎么写只讲代码怎么跑通、数据怎么设计、坑在哪。2. 先想清楚再动手SSM 项目凭什么能撑起剪纸展示系统2.1 SSM 三件套的分工谁管请求、谁管业务、谁管数据库SSM 是 Spring SpringMVC MyBatis 的组合项目里每一层都有明确职责。Spring 管对象创建和依赖注入SpringMVC 管前端请求怎么路由到 Java 方法MyBatis 管 Java 对象和 MySQL 表之间的映射。实际写代码的时候一个请求从浏览器或者小程序发出来先进 DispatcherServlet再被 Controller 接收然后调 Service 层处理业务最后通过 Mapper 接口操作数据库。这套分工对毕设来说刚刚好不会像 Spring Boot JPA 那样自动得让人答不上来也不会像纯 Servlet 那样写 200 行还只是一张列表页。答辩被问“三层架构是什么”的时候你直接把 Controller、Service、Dao 拿出来讲就够了。2.2 为什么这个项目结构适合剪纸题材剪纸展示系统的核心数据只有几类作品、分类、用户、收藏、评论。这类业务用 SSM 写非常顺手因为它没有复杂的分布式事务、没有高并发、没有消息队列。你要做的就是设计几张表写出增删改查接口再在小程序端把列表和详情页渲染出来。相比电商系统、抢票系统那种高并发场景剪纸项目的难点不在“量”而在“完整度”——是不是每个模块都有管理员能不能管理作品用户能不能收藏和留言。这也是毕设评分的重点功能闭环比技术炫技重要。我以前见过太多类似的翻车现场有的把精力花在搞 Redis 缓存结果评论功能没做完有的用了一个特别复杂的权限框架结果登录都调不通。做剪纸这种选题先把基础 CRUD 做扎实再考虑加东西。表设计的核心思路其实就是一个作品主表然后带上分类、用户、收藏、留言这些关联表。下面是建表 SQL 的常用写法CREATE TABLE t_papercut ( id int NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 作品名称, category_id int DEFAULT NULL COMMENT 分类ID关联t_category, image_url varchar(255) DEFAULT NULL COMMENT 作品图片路径, description text COMMENT 作品描述, dynasty varchar(50) DEFAULT NULL COMMENT 年代/时期, author varchar(50) DEFAULT NULL COMMENT 作者/传承人, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里有几个细节值得注意。image_url存的是相对路径比如/upload/papercut/10001.jpg不存完整域名这样后端换服务器的时候不用改库里的数据。category_id要关联分类表等一下写关联查询备用。create_time直接用DEFAULT CURRENT_TIMESTAMP新增数据的时候省一行 Java 代码。用utf8mb4是因为作品描述里可能带 emoji 或者生僻字。2.3 数据库设计的另一个关键用户与收藏表怎么写用户表是和小程序登录绑定的。微信小程序登录后你会拿到一个openid这是用户在微信体系里的唯一标识。用户表里至少要有id、openid、nickname、avatar_url这几个字段。收藏表则是典型的关联表记录哪个用户收藏了哪个作品加上收藏时间就够用了。写一个简单的收藏表CREATE TABLE t_favorite ( id int NOT NULL AUTO_INCREMENT, user_id int DEFAULT NULL, papercut_id int DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_papercut (user_id, papercut_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;UNIQUE KEY在这里不是摆设。没有唯一约束的话同一个用户反复点收藏表里就会插进多条相同记录前端显示收藏数就会翻倍。加了联合唯一索引之后后端插入前先查一遍或者直接用INSERT IGNORE都能兜住。毕设答辩时老师常会追问“数据库怎么防重复数据”这一行索引就是最好的答案。2.4 SSM 项目目录怎么摆包结构决定了你能不能快速找代码拿到源码之后第一件事不是跑起来而是看清包结构。一个健康的 SSM 毕设项目src下应该能看到这样的分包controller、service、service.impl、mapper或者dao、entity或者pojo、common放统一返回结果和工具类。一个常见的包结构大概是这个样子src/main/java └── com.example.papercut ├── controller # 接收前端请求 ├── service # 业务接口 ├── service/impl # 业务实现 ├── mapper # MyBatis Mapper接口 ├── entity # 数据库对应实体类 ├── common # 统一返回结果、异常处理 └── utils # 工具类我自己看项目第一步永远是打开mapper下的 XML 文件看 SQL再回过来对照entity里的字段确认两者对得上。很多毕设源码“跑不起来”的根本原因就是 Mapper XML 里写了数据库没有的字段或者字段大小写对不上启动时就报错。这里的逻辑不要想当然最好一个接口一个接口去试。3. SSM 后端从建表到接口一次跑通的 MyBatis 配置与核心接口3.1 从配置文件开始数据库连接池和 MyBatis 映射怎么配后端第一步是把 Spring 和 MyBatis 串起来。最常见的方法是用spring-dao.xml配置数据源和SqlSessionFactory再用spring-mvc.xml配置包扫描和视图解析器。数据库连接这里我一般会用db.properties统一管理链接参数避免硬编码。一个典型的配置文件是这样写的jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/papercut_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这里有一个几乎每个人都会翻车的点driver必须要和 MySQL 版本匹配。MySQL 8 以上要用com.mysql.cj.jdbc.DriverMySQL 5.x 用com.mysql.jdbc.Driver。用错的话启动直接报ClassNotFoundException。另外serverTimezone参数不是玄学MySQL 8 默认时区是 UTC不设置的话时间字段会差 8 个小时你的create_time全都会变慢 8 小时。这些细节答辩被问到的时候都算加分项。MyBatis 的配置里我最在意的是 3 个参数mapUnderscoreToCamelCase、typeAliasesPackage和mapperLocations。前两个没配好实体类字段和数据库字段就对不上。configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings typeAliases package namecom.example.papercut.entity/ /typeAliases mappers mapper resourcemapper/PapercutMapper.xml/ mapper resourcemapper/CategoryMapper.xml/ /mappers /configurationmapUnderscoreToCamelCase设为true数据库里create_time就能自动映射到实体类的createTime字段不用在 SQL 里写一长串AS别名。typeAliasesPackage配好之后Mapper XML 里的resultType可以直接写类名Papercut不用写全限定名。3.2 列表查询接口从 SQL 到 Controller 的完整链路先实现最核心的作品列表接口。这个接口要支持分页还要能按分类筛选。通过这个接口你可以完整看到 MyBatis 从 XML 到接口再到 Controller 的整个链路。Controller 代码是这样Controller RequestMapping(/api/papercut) public class PapercutController { Autowired private PapercutService papercutService; ResponseBody GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer categoryId) { ListPapercut list papercutService.queryPage(page, size, categoryId); return Result.success(list); } ResponseBody GetMapping(/detail) public Result detail(RequestParam Integer id) { Papercut papercut papercutService.getById(id); if (papercut null) { return Result.error(作品不存在); } return Result.success(papercut); } }这里用的是 SpringMVC 注解风格不是 XML 映射 URL。RequestParam上的defaultValue很重要前端不传 page 和 size 的时候默认拉第 1 页每页 10 条不至于一页返回 200 条数据把小程序拖垮。ResponseBody加上后这个方法的返回值会直接转成 JSON 返回给前端这是前后端分离最常见的做法也是小程序端能直接拿到数据的保证。Mapper 接口只有一个空壳方法真正的 SQL 写在 XML 里public interface PapercutMapper { ListPapercut selectPage(Param(offset) int offset, Param(size) int size, Param(categoryId) Integer categoryId); }XML 里对应的是select idselectPage resultTypePapercut SELECT id, title, category_id, image_url, description, dynasty, author, create_time FROM t_papercut where if testcategoryId ! null AND category_id #{categoryId} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{size} /selectwhere加if是 MyBatis 动态 SQL 最常见的组合。categoryId 是空的时候SQL 不拼分类条件不是空的时候才拼。这套写法在毕设里够用而且好讲。分页用的是LIMIT offset, sizeoffset 在 Service 层算好后传进来。3.3 Service 层必须做的两件事分页计算和异常兜底很多人写毕设时喜欢 Controller 直接调 Mapper把 Service 层当摆设。这个习惯在答辩时非常吃亏老师问“Service 层存在的意义是什么”你总不能说“因为老师要求我用 SSM 框架”。Service 层至少要做两件事一是把前端传来的 page 和 size 换算成数据库需要的 offset二是把异常吞掉并转成友好提示。Service public class PapercutServiceImpl implements PapercutService { Autowired private PapercutMapper papercutMapper; Override public ListPapercut queryPage(int page, int size, Integer categoryId) { int offset (page - 1) * size; return papercutMapper.selectPage(offset, size, categoryId); } Override public Papercut getById(Integer id) { Papercut papercut papercutMapper.selectById(id); if (papercut null) { throw new ServiceException(作品不存在或已下架); } return papercut; } }offset 的计算是一个极其常见的 bug 来源。page1 的时候 offset0取第 1 条到第 10 条这没问题但如果直接在 SQL 里用 page 作为 offset第一页的数据就会是第 1 条到第 10 条第二页直接从第 2 条开始每次都会和上一页重复一条数据。在 Service 层统一算 offset至少逻辑集中出错时一眼就能定位。3.4 图片上传和静态资源映射剪纸上传功能的落地写法剪纸项目绕不开图片。管理端要上传作品图片小程序端才能展示。SSM 项目里图片上传的常见做法是后端保存文件到本地磁盘再把文件路径存进数据库。Controller 写一个上传接口ResponseBody PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file, HttpServletRequest request) { if (file.isEmpty()) { return Result.error(上传文件不能为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName System.currentTimeMillis() ext; String uploadDir request.getServletContext().getRealPath(/upload/); File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(uploadDir fileName)); return Result.success(/upload/ fileName); } catch (IOException e) { return Result.error(图片保存失败); } }这里有几个容易踩的坑。文件名我习惯用时间戳重命名不是保留原始文件名因为老师同学上传的图片很可能是微信图片_20240601103024.jpg这种带特殊字符的名字存到 Linux 服务器上会乱码或者找不到文件。文件路径默认存在当前 Tomcat 的webapps目录下毕设可以这样用但如果你的项目要部署上线这个路径要改成外部独立目录不然服务器一重启图片就可能丢了。3.5 管理员端与用户端的权限控制不用重框架的两行拦截器剪纸系统通常有管理员功能上传作品、删除评论、管理用户。如果引入 Shiro 或者 Spring Security短时间内又看不懂又容易配错。对于毕设来说用 SpringMVC 拦截器做个简单的角色判断就够了。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); Integer userId RedisUtil.getUserId(token); // 也可以从 Session 取 if (userId null) { response.setStatus(401); return false; } // 检查用户角色 if (!userService.isAdmin(userId)) { response.setStatus(403); return false; } return true; } }这个拦截器的思路是带权限的接口才走拦截公开接口不拦截。注册方式也很简单在 spring-mvc.xml 里加mvc:interceptors配置把/api/admin/**这样的路径拦下来放行/api/papercut/**和/api/user/login。这样既实现了权限控制又没引入额外的学习成本。还有一种更简化的做法管理端和小程序端完全分开成两个项目小程序只读公开接口管理端单独部署。毕设周期短的时候这个方案最省事但答辩时容易露出“没有用户权限概念”的短板。我建议还是加一层简单的拦截器哪怕只有校验登录状态这一件事学术上也算你做了“用户认证与授权”。4. 微信小程序前端这样接后端登录态、列表页与图片加载的落地写法4.1 小程序目录结构page 分包和 utils 别混在一起拿到小程序端源码时先看项目根目录下pages文件夹是怎么组织的。优秀的结构基本是每个页面一个文件夹里面放 4 个同名文件.wxml、.wxss、.js、.json公共请求封装放在utils/request.js全局变量放app.js。最怕遇到的是所有页面堆在一个目录里文件名叫index1.wxml、index2.wxml这种代码后期改到想哭。我见过不少剪纸毕设的小程序端布局是先做两个 tab首页作品列表和我的个人中心。首页推荐用swiper放轮播图展示代表性作品下面用网格布局放分类入口。详情页需要支持放大查看图片这个用小程序原生wx.previewImage就能实现不用引入任何组件库。4.2 封装 request 请求统一的 baseURL 和错误提示小程序里不能直接发 ajax只能通过wx.request。最省事的做法是封装一个公共请求函数把 baseURL、header、错误码处理统一在一起以后接口地址变了只改一个文件。// utils/request.js const BASE_URL http://localhost:8080/papercut function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, Authorization: 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) } }) }) } module.exports { request, BASE_URL }这段代码把小程序的网络请求封装成了 Promise 风格success和fail回调里干了三件事判断后端统一返回码、弹出错误提示、把数据解析出来交给页面。最关键的是Authorization这一行小程序没有 Cookie 概念服务端 session 也很难维持所以约定成俗用 token 方式登录后把 token 存到本地存储所有请求自动带上。BASE_URL 用的是本地开发地址等后端部署到服务器之后只需要改这一行。4.3 首页列表页开发下拉刷新、上拉加载和收藏操作剪纸列表页是小程序端的门面。首页要展示作品缩略图、标题、分类下拉可以刷新触底加载更多。用小程序原生 Page 构造器写Page({ data: { list: [], page: 1, size: 10, hasMore: true, loading: false }, onLoad: function () { this.loadList(true) }, onPullDownRefresh: function () { this.loadList(true) }, onReachBottom: function () { if (!this.data.hasMore || this.data.loading) return this.loadList(false) }, loadList: function (reset) { if (this.data.loading) return this.setData({ loading: true }) const page reset ? 1 : this.data.page 1 request(/api/papercut/list, GET, { page: page, size: this.data.size }) .then((data) { const list reset ? data : this.data.list.concat(data) this.setData({ list: list, page: page, hasMore: data.length this.data.size, loading: false }) }) .catch(() { this.setData({ loading: false }) }) } })这里有个判断hasMore的思路很重要如果返回的数据条数小于请求的 size说明已经没有更多了。这样设计比后端传 total 再自己算页码更省事还能避免后端返回的总数和列表长度不一致的问题。onPullDownRefresh在刷新后需要手动调wx.stopPullDownRefresh()不然加载动画会一直转。WXML 里展示列表的时候图片要加上modeaspectFill防止剪纸作品图被压缩变形view classcard wx:for{{list}} wx:keyid image src{{item.image_url}} modeaspectFill classthumb / view classinfo text classtitle{{item.title}}/text text classcategory{{item.categoryName}}/text /view /view这里image_url如果直接是相对路径小程序端会加载失败。小程序image组件的src只认绝对地址所以要拼接完整地址。常见的做法是在后端接口返回数据时就把相对路径拼成完整 URL比如http://localhost:8080/papercut/upload/xxx.jpg。如果后端只存了相对路径前端拼接时要注意端口号和上下文路径别漏掉。4.4 收藏功能与登录态的联动逻辑收藏功能的实现逻辑不复杂但有一个硬性的先后顺序先登录才能收藏。小程序端需要判断本地有没有 token没有的话先弹出登录引导。用wx.login换取 code 后把 code 发到后端后端拿着 code 换 openid登录逻辑就完成了。前端代码wx.login({ success: (res) { if (res.code) { request(/api/user/login, POST, { code: res.code }) .then((data) { wx.setStorageSync(token, data.token) wx.setStorageSync(userId, data.userId) }) } } })这里面藏着一个容易被忽略的问题request函数里已经带了Authorization但是这里登录接口本身是不需要 token 的。如果你把所有请求都统一走带 token 的 request后端拦截器会把登录接口也拦截导致死循环。解决办法是request函数加一个needAuth参数登录接口这个参数传false其他接口默认传true。这个小细节能救你一命。4.5 小程序详情页与评论展示两种典型数据渲染方式详情页的数据结构跟列表页不一样它不是数组是单个对象。页面加载时拿到作品 id请求详情接口然后把数据填充到 data 里。评论列表则是详情页里内嵌的另一个数组接口。评论输入框需要做内容校验防止空内容直接提交submitComment: function () { const content this.data.commentContent.trim() if (!content) { wx.showToast({ title: 评论内容不能为空, icon: none }) return } request(/api/comment/add, POST, { papercutId: this.data.id, content: content }).then(() { this.setData({ commentContent: }) this.loadComments() }) }trim()这行代码几乎是必须的不 trim 的话用户输入一堆空格也能提交数据库里就会出现看起来是空白但实际不是空字符串的数据。剪纸这种文化展示项目用户列表和评论区的数据质量直接影响答辩时现场演示的效果。数据清清爽爽老师第一印象就好。5. 避坑五个高频问题排查清单照着查能省一整天5.1 站端口通但接口全 404SpringMVC 拦截了所有请求现象启动 Tomcat 不报错浏览器访问http://localhost:8080/papercut/api/user/login显示 404。原因spring-mvc.xml 里的mvc:default-servlet-handler/没配或者mvc:annotation-driven/被注释了。SpringMVC 默认把每个请求都当作要匹配的 Controller匹配不到就 404不会转发给 Tomcat 的默认 Servlet。解决在 spring-mvc.xml 中显式加上这两行mvc:annotation-driven / mvc:default-servlet-handler /前者启用注解驱动的 SpringMVC 功能后者允许未匹配到 Controller 的静态资源由默认 Servlet 处理。缺了default-servlet-handler前端引用的 CSS 和图片也会一起挂掉不只是接口 404。5.2 小程序请求后端就报 “url not in domain list”现象小程序开发者工具里点预览所有请求都报错提示域名不在合法列表中。原因微信小程序要求wx.request的域名必须是配置过的 HTTPS 合法域名而本地调试的http://localhost:8080显然不在列表中。这个报错困扰了几乎每一个入门的人。解决开发者工具里勾选“不校验合法域名”即可位置在右上角“详情” → “本地设置” → 勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。注意这个选项只在开发者工具有效真机预览如果不勾选同样会报错。真机调试时你可以把 BASE_URL 改成局域网 IP 试试但要保证手机和电脑在同一 WiFi 下。5.3 数据库连不上MySQL 8 驱动的时区坑现象项目启动时报The server time zone value й׼ʱ is unrecognized之类的错误。原因MySQL 8 的驱动com.mysql.cj.jdbc.Driver会校验数据库的时区设置默认时区CST在驱动里没有被正确解析就会抛异常。这个报错里的乱码就是时区名称没匹配上。解决在 JDBC URL 参数里拼接serverTimezoneAsia/Shanghai并且确保driver是com.mysql.cj.jdbc.Driver不是 5.x 版本用的com.mysql.jdbc.Driver。另外检查你 MySQL 的时区设置直接执行SHOW VARIABLES LIKE %time_zone%如果显示 SYSTEM执行SET GLOBAL time_zone 8:00也能解决。5.4 小程序图片裂开相对路径加载不出完整地址现象首页数据返回了但所有作品的图片都是碎图标点击图片查看原图也是黑屏。原因数据库中存的是/upload/xxx.jpg这种相对路径小程序的image组件不会自动补全域名和上下文路径。浏览器可以自动补全 IP 和端口小程序不行它必须拿到完整的http://开头的 URL。解决两种处理办法我推荐后端改。后端在返回image_url字段时拼接拦截器批量处理或者直接用 MyBatis 的resultMap无法做这个拼接逻辑更直接的方案是在 Controller 返回值后统一处理把uploadDir补成一个常量。改前端也可以在 WXML 里用{{BASE_URL}}{{item.image_url}}但这样每个要用图片的页面都要拼一次容易漏。后端一次性解决好前端只管展示。5.5 数据都正常但页面白屏控制台报 “Cannot read property length of undefined”现象小程序控制台报错页面空白数据明明已经返回了。原因接口返回的数据结构和页面预期不一致比如data.list实际叫data.rows或者返回的是{records: [...]}这种结构。页面代码里写了this.data.list.length初始化时list是undefined就崩了。解决先给 data 里的 list 赋默认值比如list: []这样就算接口还没返回也不会直接挂掉。接着在success回调里用console.log(res)打印完整返回值看一下真实的 key 是什么再改拼接逻辑。永远不要指望后端返回的数据结构和你心里想的一模一样。联调时多打印、多看实际数据这个习惯能省不止半天。5.6 管理端上传图片成功后列表页刷新就看不到现象管理端上传作品时图片显示正常返回列表页就裂了刷新也没用。原因上传时返回的图片路径是完整 URL比如http://localhost:8080/papercut/upload/xxx.jpg而查询列表接口拼接时又把localhost:8080/papercut拼了一遍变成http://localhost:8080/papercut/http://localhost:8080/papercut/upload/xxx.jpg。解决图片路径存储时统一约定只存相对路径前端展示时统一拼接一次不要两头都拼。这个问题的本质是“存储格式”和“展示格式”没有解耦。我在做这个项目时专门写了个ImageUrlUtil.getFullUrl()方法所有返回给前端的数据都过一遍这个工具格式就永远是一致的了。6. 把“能跑”变成“答辩高分”三个不用改代码的提升方向代码能跑只是及格想拿高分需要在系统之外做一些关键的验证和准备工作。第一个建议是把每一个核心接口的执行流程画一遍画法不用复杂一张 A4 纸就够了。比如登录流程画成“小程序 wx.login → 拿 code → 后端请求微信接口换 openid → 生成 token → 存 Redis → 返回前端”。这张图既是答辩时的讲解稿也是你梳理思路的过程。很多答辩被问住的人根本不是代码有问题是自己没理清楚流程。第二个建议是准备三组以上的演示数据。剪纸项目的数据库里只放两条作品数据演示的时候三分钟就翻完了老师会觉得这个系统是空壳。添加数据时不要只加剪纸可以按“传统剪纸”“现代创意剪纸”“窗花剪纸”三个分类各放 5 条描述写具体一点图片选清晰度高的。“内容详实”在毕设评分中的权重远比很多人想的高。第三个建议是给系统做一个简单的性能观察。不用引入压测工具就在管理后台的页面加载时用浏览器开发者工具看 Network 面板记录下接口响应耗时。如果列表接口超过 500ms检查是不是 SQL 没有索引、或者一次性查了全表数据。给t_papercut.category_id加一个普通索引给t_favorite.user_id加普通索引这两个动作耗时不到一分钟但能在答辩时说出“我做了索引优化”这句话比空谈“系统稳定”有力得多。最后说说演示视频的录制习惯。录视频不是把项目从头到尾点一遍而是按功能模块分段录制登录演示、首页加载、详情查看、收藏操作、管理端上传、数据展示。每个模块之间停顿两三秒录像时不要带出调试工具里的报错日志。剪辑不需要高级技巧把每个片段按顺序拼起来配上不到一分钟的口头讲解这段视频能直接当答辩现场的备用材料。经历了几届学生的答辩会发现老师对功能细节的兴趣远大于技术名词。你在答辩时能脱口说出某个表的字段设计理由比背出“用了 Spring AOP 实现了日志切面”要可信得多。希望这篇笔记能帮你把这个经典毕设少走几个月的弯路。本文还有配套的精品资源点击获取
返回列表