news 2026/10/8 5:13:21

SSM+微信小程序健身管理系统拆解:前后端分离实战与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM+微信小程序健身管理系统拆解:前后端分离实战与避坑指南

简介:一套基于SSM与微信小程序的健身主题Java毕业设计项目,面向计算机专业学生、毕业设计者及前后端分离学习者。项目已通过导师认可,答辩评审分九十七分,在Windows10/11环境下调试运行,自带完整部署说明,下载后即可启动,也适合课程设计。

包内共九百七十个文件,体积约五十八点七六MB,覆盖后端Java业务代码、小程序页面、Vue管理后台、SQL数据库脚本、答辩PPT、开题报告、使用文档、演示视频,另有多个bat脚本一键完成安装、运行与打包,结构清晰,便于对照学习。

目前已有153人学习/下载。这套资源整合了从源码到演示的完整链路,不仅提供可运行工程,还能帮助理解SSM框架与微信小程序的业务整合方式,可作为毕业设计蓝本或课设参考,节省从零搭建的时间。

1. 拿 SSM + 微信小程序做健身管理系统:为什么这包值得拆一遍

如果你打开过十几个号称“下载即用”的毕业设计源码,大概率遇到过这种场景:代码解压完,数据库脚本导入就报错,Tomcat 一启动就端口冲突,小程序端怎么都连不上接口。这套基于 SSM + 微信小程序的健身管理系统,后台是 Spring + SpringMVC + MyBatis 三件套,前台是微信小程序,围绕会员管理、课程预约、私教排期这三条主线把数据串起来。我把它当做一个能完整复现前后端分离毕业设计的样本,适合做 Java 毕业设计、SSM 课程设计,也适合想在小程序端把后端接口走通的初学者。答辩评审能到 97 分,说明它在业务完整性和文档配套上确实做得比较齐。下面按照我实际拆包的过程,把每一步和踩过的坑都写出来。

2. 后端分层与接口设计:表结构怎么落、JSON 怎么回

拿到包先别急着导入 IDEA,先花五分钟把后端工程结构认明白。SSM 项目的经典分层是 Controller 收参数、Service 管业务、Mapper 管数据库,中间再用实体类把数据传下去。这套健身系统的核心业务并不复杂,难点反而在“课程预约”和“会员信息”这两块怎么把表设计得合理。

2.1 核心表设计:会员、教练、课程、预约一条线

健身类系统的表一般绕不开四张主表加几张字典表。第一张是用户表,用来存管理员、教练、会员三种角色,靠一个role_type字段区分;第二张是课程表,记录课程名称、所属教练、上课时间、人数上限;第三张是预约表,把用户和课程关联起来,用于判断是否重复预约;第四张是会员卡表,记录开通时长和到期时间。实际建库时我会先把四张表的主外键关系画清楚再动手,避免后面写 JOIN 查出全是笛卡尔积。

资源里如果带.sql脚本,通常会有这样的建表语句,我挑出核心部分加注释方便你对照:

CREATE DATABASE IF NOT EXISTS fitness_ssm DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE fitness_ssm; CREATE TABLE sys_user ( user_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID', openid VARCHAR(64) DEFAULT NULL COMMENT '小程序openid', user_name VARCHAR(32) NOT NULL COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '登录密码', real_name VARCHAR(32) DEFAULT NULL COMMENT '真实姓名', role_type TINYINT NOT NULL DEFAULT 2 COMMENT '0管理员 1教练 2会员', phone VARCHAR(20) DEFAULT NULL COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_name (user_name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE course ( course_id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(64) NOT NULL, coach_id INT COMMENT '教练ID,关联sys_user', course_date DATETIME COMMENT '上课时间', duration_min INT DEFAULT 60 COMMENT '单节时长(分钟)', capacity INT DEFAULT 20 COMMENT '人数上限', enrolled_count INT DEFAULT 0 COMMENT '已报名人数', course_status TINYINT DEFAULT 0 COMMENT '0未开始 1进行中 2已结束' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健身课程表'; CREATE TABLE course_booking ( booking_id INT AUTO_INCREMENT PRIMARY KEY, course_id INT NOT NULL, user_id INT NOT NULL, book_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0 COMMENT '0已预约 1已取消', UNIQUE KEY uk_course_user (course_id, user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程预约表';

这里有一个容易被新手忽略的设计点:course_booking给(course_id, user_id)加了唯一索引,它的作用是让数据库层面拦截重复预约,而不是只靠代码里if判断。如果只在前端做校验,用户连点两次按钮、或者两台设备同时提交,就会出现重复记录。加这个唯一索引属于“防重复提交”的兜底手段。course.capacity和enrolled_count记录人数上限和已报名数,下单成功后再给enrolled_count加 1,这就构成了最朴素的库存扣减逻辑。

2.2 三层调用链:Controller 到 Service 再到 Mapper 传什么

看代码时会发现每个功能都是“Controller 收参 → Service 处理 → Mapper 查库”的一条链路。以课程列表为例,查询条件一般有课程名、课程状态、上课日期范围,这些条件不要用散乱的单个参数往方法里塞,并成一个查询对象传下去会清爽很多。下面是课程列表接口的 Controller 写法,用的是 RESTful 风格:

@RestController @RequestMapping("/api/course") public class CourseController { @Resource private CourseService courseService; @GetMapping("/list") public Result list(CourseQuery query) { // query 由前端以 Query String 传入,SpringMVC 自动绑定到对象属性 List<CourseVO> list = courseService.selectCourseList(query); return Result.success(list); } @PostMapping("/book") public Result book(@RequestBody BookForm form, HttpSession session) { // 用户ID优先从 session 取,不要轻信前端传的 userId Integer userId = (Integer) session.getAttribute("userId"); if (userId == null) { return Result.error(401, "请先登录"); } boolean ok = courseService.bookCourse(userId, form.getCourseId()); return ok ? Result.success(null) : Result.error("人数已满或课程已开始"); } }

@RestController是@Controller加@ResponseBody的合并注解,意味着每个方法直接返回 JSON,不再走 JSP 视图解析器;如果项目里配了InternalResourceViewResolver,记得确认它对/api路径没有拦截。CourseQuery这个对象我一般放在vo包里,字段名和前端传参名保持一致,SpringMVC 才能绑定进去。值得注意的一个细节是list(CourseQuery query)没有加@RequestBody,因为 GET 请求的参数走 url 拼参,加了反而绑不上;POST 提交的BookForm是 JSON 体,必须加@RequestBody。这个区别经常让人踩坑。

真正的动态 SQL 会落在 Mapper 的 XML 文件中,MyBatis 这一层决定了 SQL 能不能按条件灵活组合,我一般会在 XML 里写成这样:

<select id="selectCourseList" parameterType="com.fitness.vo.CourseQuery" resultType="com.fitness.entity.Course"> SELECT c.course_id, c.course_name, c.course_date, c.duration_min, c.capacity, c.enrolled_count, c.course_status FROM course c <where> <if test="courseName != null and courseName != ''"> AND c.course_name LIKE CONCAT('%', #{courseName}, '%') </if> <if test="courseStatus != null"> AND c.course_status = #{courseStatus} </if> <if test="beginDate != null"> AND c.course_date &gt;= #{beginDate} </if> </where> ORDER BY c.course_date ASC </select>

<where>标签会自动处理多余的前置AND/OR,第一个条件成立时不会在 WHERE 前面多出 AND,这是 MyBatis 自带的智能拼接。test里面写的是 OGNL 表达式,courseName != null and courseName != ''这个写法要求CourseQuery对象里有对应属性的 getter,否则会直接抛ReflectionException。#{courseName}是预编译占位符,最终生成?占位由 JDBC 的PreparedStatement处理,能挡住大部分 SQL 注入;如果图省事用${courseName}拼字符串,等于把参数直接拼进 SQL,这是我在代码审查里一定会拦下来的写法。日期比较用&gt;=是因为 XML 里裸写>会被解析成节点结束符,要么用转义,要么把比较条件放在<![CDATA[ ]]>里。

2.3 返回格式与登录态:前后端协商一个 Result 类

微信小程序端不是浏览器,没有 cookie 机制,SSM 后端如果继续用HttpSession维持登录态,小程序端每次请求都需要显式带上会话标识。这套系统用了最朴素的方式:小程序端wx.login拿到 code,后端拿着 code 向微信接口换 openid,再把 userId 存进 session;小程序端把后端返回的 token 放进 header 里的Authorization,后端的拦截器从 header 里解析用户身份。

所有接口的返回体建议统一成Result对象,而不是让每个方法自己拼 JSON,不然前端出 bug 时排查成本很大。常见的约定是:

public class Result { private Integer code; // 200 成功,401 未登录,500 业务异常 private String msg; // 给前端提示文案 private Object data; // 真正的业务数据 public static Result success(Object data) { Result r = new Result(); r.code = 200; r.msg = "OK"; r.data = data; return r; } // error 静态方法省略,业务里按场景调用 }

小程序端的wx.request在success回调里第一件事就是判断res.data.code是否为 200,而不是直接渲染res.data。把code、msg、data三层分清楚之后,无论是课程列表、登录接口还是下单接口,前端只用写一套通用处理逻辑就够了。记住这个约定,后面读小程序端代码会快很多。

3. 微信小程序端:从首页渲染到预约下单的完整链路

管理端跑通后,再来看小程序端。这一层如果你没写过小程序,最需要先建立的概念是:小程序没有 DOM 操作,页面由wxml + wxss + js三件套组成,所有数据绑定靠setData。这也意味着你没法像在浏览器里那样直接document.getElementById,一切渲染都走数据驱动。

3.1 请求封装与本地配置:把 wx.request 包成 Promise

原生wx.request是回调风格,页面多了之后会出现“回调地狱”。所以拆这个包时先看utils/request.js,它通常会把wx.request封装成 Promise,并且统一在 header 里带上 token。一个最常用的形式是这样:

const BASE_URL = 'http://localhost:8080/fitness'; const 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 if (res.data.code === 401) { wx.navigateTo({ url: '/pages/login/login' }); reject(new Error('未登录')); } else { reject(new Error(res.data.msg)); } }, fail: (err) => { reject(err); } }); }); }; module.exports = { request, BASE_URL };

这里的BASE_URL是全局配置,开发者工具里填http://localhost:8080没问题,但真机预览时必须改成电脑的局域网 IP,例如http://192.168.1.100:8080,否则手机连不上。同时在微信开发者工具的详情设置里勾上“不校验合法域名”,否则工具会拦截所有http请求。这两点几乎是新手遇到“请求 fail 或者一直转圈”的头号原因。

提示:真机调试改 BASE_URL 为局域网 IP 时,记得把后端服务的 8080 端口在防火墙中放行,否则手机能 ping 通电脑也照样访问不了。

wx.getStorageSync('token')是从本地缓存同步读取 token,每次请求自动带上。后端拦截器拿到Authorization后去 session 里查对应用户,查不到就返回 401,前端这边就统一跳登录页。这里就引出登录的问题了。

3.2 登录流程与首页数据渲染

小程序登录现在通用的流程是wx.login拿 code,而不是像网页端那样直接输入用户名密码。拿到 code 之后调用后端接口,后端的LoginController大致处理过程如下:

@PostMapping("/wx/login") public Result login(@RequestBody WxLoginForm form) { // 微信 code 只能使用一次,且有效期五分钟 String openid = wxService.code2Openid(form.getCode()); if (openid == null) { return Result.error("code 无效或已过期"); } User user = userService.findOrCreateByOpenid(openid); String token = UUID.randomUUID().toString().replace("-", ""); sessionCache.put(token, user.getUserId()); // 生产环境换成 Redis return Result.success(token); }

code2Openid需要后端配好appId和appSecret,这两个值在微信公众平台的“开发管理 → 开发设置”里拿,不能写死在小程序前端代码里,否则等于把密钥公开。用ConcurrentHashMap做 session 缓存是毕业设计阶段的常见做法,能跑通但服务重启后 token 全部失效;上生产环境时换成 Redis 并设置过期时间才是合理方案。这个接口返回的 token 就是 3.1 里 header 里带的东西。

首页渲染逻辑就简洁了,课程列表页在onLoad里调用request('/api/course/list'),拿到数组后setData给wxml的wx:for。课程卡片一般要展示课程名、教练名、时间、剩余名额,数据不够时后端多半会返回一个CourseVO而不是原始实体类,VO里已经 JOIN 出了教练姓名。你排查“页面上教练名空白”时可以优先检查接口返回里有没有coachName字段,而不是去看wxml的绑定写法。

对应的页面结构一般是这样的,注意wx:key必须绑定唯一字段:

<view class="course-card" wx:for="{{courseList}}" wx:key="courseId"> <view class="course-name">{{item.courseName}}</view> <view class="course-meta">教练:{{item.coachName}}</view> <view class="course-meta">{{item.courseDate}} · {{item.durationMin}}分钟</view> <view class="course-meta">已报名 {{item.enrolledCount}} / {{item.capacity}}</view> <button size="mini">async onBookTap(e) { const courseId = e.currentTarget.dataset.id; const course = this.data.courseList.find(item => item.courseId === courseId); if (course.enrolledCount >= course.capacity) { wx.showToast({ title: '课程已满', icon: 'none' }); return; } const data = await request('/api/course/book', 'POST', { courseId }); wx.showToast({ title: '预约成功', icon: 'success' }); this.reloadList(); // 重新拉课程列表,刷新已报名人数 }

前端判断名额只是体验上的提前拦截,真正的并发控制要放在后端。我在写这类接口时习惯用一条原子更新做兜底:UPDATE course SET enrolled_count = enrolled_count + 1 WHERE course_id = #{courseId} AND enrolled_count < capacity,同一时刻只有一个事务能成功更新;即便前端连点两次按钮,course_booking表的唯一索引也会拦下第二次插入,接口返回“重复预约”的业务提示。发布会现场有人连续点按钮,反而能展示出你对边界情况的考虑。

3.4 手机号获取的限制与自定义导航栏高度

微信小程序“获取手机号”这几年改过规则,getPhoneNumber已经不是无条件可用的了,涉及企业主体认证和平台收费策略,个人主体的毕业设计项目经常拿不到完整手机号。这套健身系统如果只是做会员信息的展示,建议不要强依赖手机号:注册时用wx.login的 openid 作为账号标识,手机号字段让用户手动填写即可。从这几年的规则变化看,门槛一直在收紧,答辩演示时如果这里报错,评委比你还清楚原因,所以预留“手动录入手机号”的入口才是稳妥方案。

另一个刚需是自定义顶部导航栏的高度。如果项目里用了navigationStyle: custom,顶部刘海屏会造成内容被遮挡,常见做法是读取wx.getMenuButtonBoundingClientRect()拿到胶囊按钮的位置,再结合wx.getSystemInfoSync()的statusBarHeight算出导航栏总高度,把padding-top动态绑到页面上。这段逻辑最好写成公共方法放进utils,因为每个页面都要用;不同机型的状态栏高度不一样,千万别写死成一个数字。

4. 从压缩包到能跑:目录识别、环境版本与三套批处理脚本

后端和小程序端的代码逻辑通了,接下来是最容易翻车的一步——部署。毕业设计源码最常见的通病不是代码写错,而是环境版本对不上、数据库导入顺序搞错、端口被占用。这一节我按“先认目录、再配环境、最后跑脚本”的顺序带你过一遍。

4.1 拆包后先看目录:哪些文件要读、哪些文件别乱删

解压后先别双击1-install.bat,先看一眼根目录。一般会看到几个 README 或 doc 文档、数据库脚本目录、后端工程目录、小程序前端目录。这套资源里还出现了main.css.bak、update-password.vue.bak、IndexAsideStatic.vue.bak这类带.bak后缀的文件,它们是开发过程中改版前留下的备份,不是运行必需文件。正常部署时应该以不带.bak的原始文件为准,不要因为“备份版本看起来更新”就把.bak改成原名覆盖现有文件,很多人就是在这里把能跑的项目改坏的。

如果根目录有pom.xml,说明后端是 Maven 工程;如果只有.classpath和.project,说明是 Eclipse/MyEclipse 直接导入的动态 Web 项目。.classpath里能看到依赖的 jar 包路径,用 IDEA 导入时选 “Import Project” 后让它识别 Maven 或 Eclipse 结构,而不是手动把源码文件夹拖进去,否则编译时 Tomcat 找不到Servlet类。

管理端的前端文件这里出现了.vue后缀的备份,说明它并不是单纯的 JSP 项目,而是内嵌了一个 Vue 管理端。Vue 资源最终要npm install && npm run build生成dist目录,再由后端承接静态页;如果只改了.vue没重新构建,浏览器看到的永远是旧页面,这一点是我拆这类包时常遇到的隐藏问题。

4.2 环境版本怎么配:JDK、Tomcat、MySQL 对照

这套资源在 Win10/11 下调试通过,我的建议是优先复现它的原始环境版本,而不是手边有什么用什么。SSM 项目最常见的组合是下面这套,兼容性也最稳:

组件推荐版本说明
JDK1.8SSM 老项目兼容性最好
Tomcat8.5对应 javax.servlet,不是 jakarta
MySQL5.7驱动路径com.mysql.jdbc.Driver
Maven3.6+拉依赖与打包

如果你本机已经装了 MySQL 8.0,不用卸载重装,按第 5.1 节调整驱动类和 URL 参数即可。配置 Tomcat 时先确认CATALINA_HOME指向的版本,再确认 IDEA/Eclipse 里的Server Runtime Environment选的是同一个 Tomcat。如果代码里用了javax.servlet而不是jakarta.servlet,说明是 Tomcat 8/9 而不是 Tomcat 11,这个版本错配会让项目启动时直接报NoClassDefFoundError。JDK 同理,项目编译级别如果是 1.8,就不要用 JDK 17 去跑,语法上勉强能过,但一些反射和代理相关的老框架会在运行时出问题。

4.3 数据库初始化与数据源参数

数据库是 SSM 项目最容易出错的一环。先把docs/sql或db目录下的.sql脚本用命令行导入,而不是在 Navicat 里双击打开再手动复制,因为脚本里如果有建库语句,命令行执行能保证字符集和排序规则一致。我的习惯是这样:

mysql -u root -p fitness_ssm < docs/sql/fitness_ssm.sql

注意:导入 SQL 之前先把 root 密码确认好,脚本里的密码和本机不一致会直接中断,最常见的报错是Access denied for user 'root'@'localhost'。

如果脚本里已经写了CREATE DATABASE,那直接导入即可;如果脚本里只有建表语句,先CREATE DATABASE fitness_ssm DEFAULT CHARACTER SET utf8mb4;再切库导入。导入完成后重点查三张表的记录数,比如sys_user里有没有默认管理员账号、course里有没有课程测试数据,如果一张表是空的,说明脚本可能没导全或者导错了库。

数据源配置一般写在jdbc.properties或db.properties里,修改后不需要重新编译 Java 代码,重启 Tomcat 即可生效。一个典型的配置长这样:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fitness_ssm?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=你的密码

characterEncoding=utf8负责让 JDBC 连接使用正确的字符集,serverTimezone=Asia/Shanghai解决 MySQL 8.0 默认时区慢 8 小时的问题。useSSL=false是在本地调试省去证书校验,上生产再按运维要求开。这三项是 SSM 连接 MySQL 时最常被改的参数,改完记得重启。

4.4 install/run/build 三个脚本到底做了什么

资源里带的1-install.bat、2-run.bat、3-build.bat,看名字像一套安装流水线,但实际执行前必须打开看一眼内容,因为不同类型的工程,这三个脚本干的事情完全不同:

@echo off rem 1-install.bat 示例:初始化数据库 set DB_USER=root set DB_PASS=123456 mysql -u%DB_USER% -p%DB_PASS% < docs\sql\fitness_ssm.sql echo install done pause

如果后端是 Maven 工程,install 脚本更常见的是mvn clean install -DskipTests,执行完会往本地仓库塞依赖并打出 war 包。注意一点:先确认脚本里写的数据库密码和你本机 root 密码是否一致,脚本跑失败九成是密码不对或者mysql命令不在 PATH 里。

2-run.bat一般负责启动 Tomcat,常见写法是这样的:

@echo off set CATALINA_HOME=C:\apache-tomcat-8.5.88 set JAVA_OPTS=-Xms256m -Xmx512m -Dfile.encoding=UTF-8 call "%CATALINA_HOME%\bin\startup.bat"

JAVA_OPTS里的-Dfile.encoding=UTF-8值得保留,它能让 Tomcat 在 Windows 上输出日志时不出现乱码;-Xms256m -Xmx512m控制堆内存上下限,几十人的毕设项目这个值足够。如果不想让 Tomcat 弹新黑窗口,也可以直接把后端打成 war 包丢进webapps目录。

3-build.bat通常是给前端 Vue 工程用的,执行npm install、npm run build。但你要注意,npm命令依赖 Node.js 环境,如果你的机器没装 Node 或者版本太旧,这个脚本会直接报'npm' 不是内部或外部命令。前端构建产物dist拿到之后,要么让后端直接承接静态资源,要么单独部署到 Nginx,这一步决定了小程序端和管理端能不能同时访问到同一个后端。先跑通1和2,再处理3,是我推荐的顺序。

5. 部署避坑与排查:五条踩坑记录的现象、原因、解法

这一章是我拆这类资源时的血泪经验。每一条我都按“现象 → 原因 → 解决”的格式写,你遇到时可以照顺序排查。

5.1 MySQL 8.0 驱动与时区报错

现象:Tomcat 启动后打开页面提示Cannot create JDBC driver of class 'com.mysql.jdbc.Driver',或控制台刷出大段Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone'。

原因:项目写死在jdbc.properties里的驱动类是com.mysql.jdbc.Driver,这是 MySQL 5.x 时代的驱动路径;MySQL 8.0 把驱动类改成了com.mysql.cj.jdbc.Driver,并且要求 JDBC URL 里显式声明时区。

解决:驱动类改成com.mysql.cj.jdbc.Driver,URL 末尾追加serverTimezone=Asia/Shanghai。如果数据库密码包含特殊字符,比如@或#,记得在.properties里用\转义或改用 URL 编码形式,否则密码会被截断,一直报Access denied for user。

5.2 数据库导入后所有中文变成问号

现象:管理端页面、小程序列表里凡是中文都显示成????,但英文和数字正常。

原因:大概率是建库时没指定字符集,或者导入脚本里没有SET NAMES utf8mb4,导致数据库实际用 latin1 存中文,写入时字符无法完整编码。

解决:最彻底的方案是重建数据库,建库语句写成CREATE DATABASE fitness_ssm DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;,然后命令行导入前先执行SET NAMES utf8mb4;。如果数据已经写进去了,先别急着删表,用ALTER TABLE sys_user CONVERT TO CHARACTER SET utf8mb4;转换,但已损坏的??数据通常是找不回来的,所以毕业设计阶段直接重建库最快。

5.3 前端页面白屏,查了半天发现是 .bak 文件替换错了

现象:管理端登录页打开白屏,F12 控制台报Failed to load module script或Unexpected token '<',也有人误删.vue源文件后构建失败。

原因:包里的main.css.bak、update-password.vue.bak、IndexAsideStatic.vue.bak是旧版本备份,有人为了“更新一下前端”把.bak后缀去掉后覆盖了正式文件,导致 Vue 工程里出现重复或残缺定义;或者直接把.vue文件误删,npm run build自然失败。

解决:部署前把.bak文件连同其对应关系列个清单,确认哪些是运行时必需文件,哪些只是备份。改动任何前端文件之前,先把当前可运行版本整体复制一份作为回退点,改坏了还能回到原状,“后悔药”手里先备好。白屏问题解决后,重新构建dist再部署,浏览器强制刷新一次,排除旧缓存干扰。

5.4 小程序真机预览请求全部失败,localhost 连不上

现象:开发者工具里调试正常,一扫码到真机上所有接口就request:fail,或者页面一直在转圈。

原因:真机上没有“localhost”这个概念,它指向的是手机本身,后端在电脑上,必须用电脑在局域网内的 IP 地址才能访问。另外微信开发者工具如果没勾选“不校验合法域名”,工具会拦截所有不带 HTTPS 的请求。

解决:把utils/config.js里的BASE_URL改成http://电脑局域网IP:8080/fitness,手机和电脑连同一个 Wi-Fi,关闭电脑防火墙的入站拦截(或放行 8080 端口)。如果后端加了@CrossOrigin仍然报跨域,检查小程序端请求头里的Content-Type是不是application/json触发了预检请求,SSM 端的跨域过滤器要同时支持OPTIONS方法。小程序正式发布时后端接口必须上 HTTPS 并配置合法域名,这个话题在微信公众平台有专门指引,答辩演示阶段用开发者工具即可。

5.5 Tomcat 端口被占用或启动两次失败

现象:2-run.bat双击后窗口一闪而过,看日志发现Port 8080 required by Tomcat v8.5 Server at localhost is already in use。

原因:前一次启动没有正常关闭,或者电脑上其他服务占用了 8080。毕设机器装过多个 Tomcat、装过 Nginx 的情况下尤其常见。

解决:先执行netstat -ano | findstr 8080看哪个 PID 占用端口,在任务管理器里结束对应进程,或者直接改server.xml里的三个端口号(<Connector port="8080" .../>以及 8005、8009 两个辅助端口)。改完端口后,jdbc.properties不用动,但小程序端的BASE_URL要同步改。这里补充一条判断标准:startup.bat启动后看到Server startup in xxx ms才算成功,只显示Starting不代表启动完成,这种模糊状态最容易误导人。

6. 验证与扩展:让演示更稳的接口自检清单

6.1 二次开发建议:三个好做又容易讲清楚的方向

在这套系统上做二次开发,性价比最高的有三个方向。第一个是预约提醒,小程序端接入订阅消息,用户预约成功时推送上课提醒,这对健身类系统非常合理,也绕开了手机号获取的坑。第二个是密码存储升级,把sys_user表里的明文密码改成 BCrypt 加密,登录时用BCryptPasswordEncoder校验,这是答辩里安全性的加分项。第三个是首页数据可视化,把每周课程预约量按天聚合出来,用图表库画成折线图,数据直接从course_booking.book_time分组查询即可,工作量不大但演示效果很直观。

6.2 验证顺序:发布演示前固定走一遍的检查清单

部署完成后下一步不是写论文,而是把接口过一遍。打开浏览器输入http://localhost:8080/fitness/api/course/list,如果能直接看到 JSON 数组,说明后端和数据库这条链路已经通;再用微信开发者工具重新编译小程序,登录、首页列表、预约这三个页面走一遍。预约功能建议当场演示“重复预约被拦截”——第二次点击同一门课时收到“请勿重复预约”的提示,这比顺滑的下单流程更能体现你在业务边界上的思考。

验证时我固定过一张清单:第一,数据库服务是否启动,用户名密码与jdbc.properties是否一致;第二,Tomcat 日志有没有Server startup;第三,管理端页面是否加载新构建而不是旧缓存;第四,小程序端BASE_URL在当前网络环境下能否被手机访问;第五,预约后重新查询课程列表,enrolled_count是否加 1。这五项全绿,演示翻车的概率就低得多了。

资源包里的 PPT、开题报告和使用文档是答辩直接可用的材料,但我的建议是把它当成参考提纲,把你自己动手部署的过程、改过的参数和踩过的坑补进去,这份材料会显得扎实很多。从那以后,我每次拿到一个毕业设计压缩包,第一件事永远是先跑部署脚本、确认环境,再动源码;顺序一旦反过来,一晚上就没了。希望你这次部署顺利,希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 5:11:56

Agent-Reach实战:构建能交付结果的Agent执行框架

做Agent开发也有一段时间了&#xff0c;从最早跑通LangChain的Demo&#xff0c;到后来自己动手拼一个真正能落在业务里的Agent&#xff0c;最大的感受是&#xff1a;圈子里太多项目停留在“能聊天”的阶段&#xff0c;真正能把活干完、把结果交付出来的Agent&#xff0c;少之又…

作者头像 李华
网站建设 2026/10/8 5:11:12

claude-mem 记忆层实战:让 Claude 跨会话记住项目上下文

1. 从“记忆”这个痛点说起&#xff1a;claude-mem 到底想解决什么如果你用过 Claude 做稍微长一点的任务&#xff0c;大概率遇到过这种尴尬&#xff1a;前面聊得好好的&#xff0c;上下文里塞了一堆项目背景、代码约定、命名规范&#xff0c;结果对话一长&#xff0c;或者你新…

作者头像 李华
网站建设 2026/10/8 5:10:03

Claude Code长期记忆方案:claude-mem自动沉淀与注入实战

用了大半年 Claude Code&#xff0c;最让我崩溃的从来不是代码写得不对&#xff0c;而是它“记性太差”——今天下午刚跟你敲定的技术选型、接口约定、命名规范&#xff0c;睡一觉回来新开个会话&#xff0c;它全忘了&#xff0c;同一个问题能来回解释三遍。后来我自己折腾了一…

作者头像 李华
网站建设 2026/10/8 5:09:27

Claude-Mem 实战:外挂长期记忆层,让 AI 助手跨对话记住项目细节

1. 别被名字骗了&#xff1a;Claude-Mem 到底在解决什么问题用过 Claude 的同学应该都有过这种体验&#xff1a;明明前两天刚跟它讨论过一个项目方案&#xff0c;今天打开新会话再问细节&#xff0c;它一脸茫然。上下文窗口再大&#xff0c;关了对话就是“失忆”。短会话还行&a…

作者头像 李华
网站建设 2026/10/8 5:09:22

PyTorch多模态情感分析系统实战:从数据对齐到模型融合与上线

简介&#xff1a;这份源码资源面向具备Python与PyTorch基础、希望上手多模态情感分析的开发者与学习者&#xff0c;围绕文本与图像配对数据的三分类任务&#xff08;积极、中性、消极&#xff09;给出完整实现方案。项目以BERT提取文本特征&#xff0c;配合轻量图像神经网络完成…

作者头像 李华