简介:这是一套面向计算机专业本科生的高质量Java毕业设计实战项目,基于SSM框架与微信小程序双端协同,构建健身服务类应用,解决用户日常健身计划管理、课程预约、数据统计等实际需求,也适合作为课程设计或Java全栈开发入门实践。资源包共1066个文件,涵盖142个Java后端核心代码、147个JavaScript与118个Vue前端组件、39个WXML/WXSS小程序页面样式与逻辑文件、241个PNG/SVG图标资源,以及SQL数据库脚本、PPT答辩材料、开题报告、任务书、使用文档和MP4演示视频,整体压缩包仅32.55MB,结构清晰、模块解耦。已有160人学习下载,所有代码在Windows 10/11环境实测可运行,配套3个BAT一键部署脚本(安装/运行/构建)及详尽部署教程,含.bak备份文件便于对比学习,支持快速上手、调试验证与二次开发。
1. 这不是又一个“健身APP仿写”,而是一套能过答辩、能跑通、能改出新功能的SSM+微信小程序双端闭环毕业设计实战包
你手头正卡在毕业设计选题上?导师说“别抄网上烂大街的图书管理系统”,同学交的项目被批“前后端耦合严重、小程序只是静态页面、数据库没索引、连登录态都维持不住”——而这个“健身小助手”源码包,是真实拿97分答辩通过的成品:后端用SSM(Spring + SpringMVC + MyBatis)搭得扎实,Controller层有统一异常处理、Service层事务控制明确、Mapper XML里写了动态SQL和分页;前端不止是Vue单页,而是完整微信小程序原生工程(非uni-app),带扫码签到、运动打卡计时、课程预约、教练评价、消息推送(模拟)、用户健康数据图表(ECharts for Weixin);数据库含12张表,主键全用BIGINT自增,关键字段加了索引(user_id、course_id、record_date),还配了MySQL 5.7兼容的建表SQL和初始化数据(含3位模拟教练、8门课程、20+用户行为记录)。它不是教学Demo,是经过Windows 10/11双环境实测、bat脚本一键部署、PPT里每页都有技术决策依据(比如为什么不用Spring Boot而坚持SSM、为什么小程序不走云开发而对接自建Java后端)、开题报告里明确写了“解决中小型健身房私域运营中用户留存率低、课程转化难、服务反馈滞后三大痛点”的落地项目。适合Java初学者照着跑通,也足够让进阶者拆解学习——比如MyBatis二级缓存怎么配合Redis做课程预约库存扣减、小程序wx.login + Java JWT如何实现无感续期、SSM拦截器如何统一校验微信OpenID合法性。
2. 后端SSM工程结构解析与核心模块运行逻辑
2.1 工程目录结构与关键配置文件定位
拿到压缩包解压后,你会看到一个标准Eclipse风格的Java Web项目结构。重点不是“有多少个文件”,而是哪些文件决定了整个系统能否启动、数据能否落库、接口能否被小程序调用:
fitness-web/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/fitness/ # 根包名,所有业务类在此 │ │ │ ├── controller/ # RESTful接口入口,如UserController.java │ │ │ ├── service/ # 业务逻辑,含IUserService.java和UserServiceImpl.java │ │ │ ├── dao/ # 数据访问层,UserDao.java + UserMapper.xml │ │ │ └── entity/ # POJO实体,User.java字段与数据库user表严格对应 │ │ ├── resources/ │ │ │ ├── mybatis-config.xml # MyBatis全局配置:别名、插件(PageHelper分页)、类型处理器 │ │ │ ├── spring-context.xml # Spring核心配置:组件扫描、事务管理器、数据源bean定义 │ │ │ ├── spring-mvc.xml # SpringMVC配置:视图解析器、静态资源映射、拦截器注册 │ │ │ └── jdbc.properties # 数据库连接参数:url=jdbc:mysql://localhost:3306/fitness?useSSL=false&serverTimezone=GMT%2B8 │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ └── web.xml # Servlet容器入口:DispatcherServlet注册、字符编码过滤器 │ │ └── static/ # 前端静态资源(供管理后台用,非小程序) ├── target/ # Maven编译输出目录(若用Maven则存在) └── 1-install.bat # 关键:初始化数据库并导入初始数据提示:
jdbc.properties中的serverTimezone=GMT%2B8是血泪经验——MySQL 5.7+默认时区为UTC,不显式声明会导致Java读取datetime字段时比实际晚8小时,造成打卡时间错乱。这个配置必须保留,不能删。
2.2 用户登录与微信OpenID绑定的核心流程
小程序端调用wx.login()获取临时code,传给Java后端/api/user/login接口。后端不做账号密码校验,而是走微信官方接口换取openid和session_key,再根据openid查库或新建用户。这是整套系统“去账号体系”的关键设计:
// UserController.java @PostMapping("/login") @ResponseBody public Result login(@RequestParam String code) { // 1. 调用微信接口换取openid String url = "https://api.weixin.qq.com/sns/jscode2session?" + "appid=" + wechatAppId + "&secret=" + wechatSecret + "&js_code=" + code + "&grant_type=authorization_code"; String response = HttpUtil.get(url); // 使用工具类发起HTTP GET JSONObject json = JSON.parseObject(response); String openid = json.getString("openid"); String sessionKey = json.getString("session_key"); // 2. 根据openid查用户,不存在则插入新记录 User user = userService.findByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); user.setCreateTime(new Date()); userService.insert(user); } // 3. 生成JWT token返回(非session,适配小程序无cookie特性) String token = JwtUtil.createToken(user.getId(), user.getOpenid()); return Result.success().setData(token); }这段代码背后藏着三个硬性要求:
wechatAppId和wechatSecret必须在application.properties或jdbc.properties中配置真实值(测试时可用微信开发者工具提供的测试号信息);JwtUtil.createToken()生成的token需带exp(过期时间,建议24小时),且小程序端必须在每次请求Header中携带Authorization: Bearer <token>;userService.findByOpenid()查询必须走索引——检查user表的openid字段是否已建INDEX,否则高并发时会拖垮登录接口。
2.3 健身课程预约的事务控制与并发安全
用户点击“预约课程”按钮,小程序调用/api/course/reserve,后端需完成三件事:检查余量、扣减库存、生成预约记录。这三步必须原子执行,否则会出现超卖:
// CourseService.java @Transactional(rollbackFor = Exception.class) public boolean reserveCourse(Long userId, Long courseId) { // 1. 检查课程是否可预约(状态=上架、未满员) Course course = courseDao.selectById(courseId); if (!"ONLINE".equals(course.getStatus()) || course.getReserveCount() >= course.getMaxPeople()) { throw new BusinessException("课程不可预约"); } // 2. 使用MyBatis的select for update锁住该课程行(防止并发超卖) courseDao.lockCourseById(courseId); // 3. 再次校验余量(防止锁住后其他事务已扣减) Course freshCourse = courseDao.selectById(courseId); if (freshCourse.getReserveCount() >= freshCourse.getMaxPeople()) { throw new BusinessException("预约失败:名额已满"); } // 4. 扣减库存并插入预约记录 courseDao.updateReserveCount(courseId); ReserveRecord record = new ReserveRecord(); record.setUserId(userId); record.setCourseId(courseId); record.setReserveTime(new Date()); reserveRecordDao.insert(record); return true; }关键点在于courseDao.lockCourseById()对应的XML:
<!-- CourseMapper.xml --> <select id="lockCourseById" parameterType="long" resultType="com.fitness.entity.Course"> SELECT * FROM course WHERE id = #{id} FOR UPDATE </select>FOR UPDATE是InnoDB行锁,确保同一课程ID的多次预约请求串行执行。若漏掉这步,100人同时抢1个名额,最终数据库里reserve_count可能只+1而非+100——这是毕业设计答辩时老师最爱问的并发问题。
3. 微信小程序端工程结构与关键API对接细节
3.1 小程序目录结构与页面路由配置
小程序工程是原生开发(非uni-app),app.json定义了完整的页面栈和tabBar:
{ "pages": [ "pages/index/index", "pages/course/list", "pages/course/detail", "pages/reserve/confirm", "pages/user/profile", "pages/user/record" ], "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "assets/icons/home.png", "selectedIconPath": "assets/icons/home-active.png" }, { "pagePath": "pages/course/list", "text": "课程", "iconPath": "assets/icons/course.png", "selectedIconPath": "assets/icons/course-active.png" }, { "pagePath": "pages/user/profile", "text": "我的", "iconPath": "assets/icons/user.png", "selectedIconPath": "assets/icons/user-active.png" } ] } }注意两点:
- 所有页面路径以
pages/开头,且必须在app.json的pages数组中声明,否则wx.navigateTo会报错page not found; assets/icons/下的图标尺寸必须为81×81px(未选中)和96×96px(选中),否则tabBar图标显示异常——这是新手最容易翻车的UI细节。
3.2 小程序登录态维护与Token自动刷新
小程序没有cookie,所以Java后端返回的JWT token必须由前端自己存取。但token会过期,不能等用户点击按钮才提示重新登录。项目采用“请求拦截+自动续期”策略:
// utils/request.js const request = (options) => { // 1. 从storage读token const token = wx.getStorageSync('token'); // 2. 自动添加Authorization头 options.header = { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' }; // 3. 发起请求 return new Promise((resolve, reject) => { wx.request({ ...options, success: (res) => { // 4. 若返回401,说明token过期,触发静默刷新 if (res.statusCode === 401) { refreshAccessToken().then(newToken => { // 5. 用新token重发原请求 options.header.Authorization = `Bearer ${newToken}`; resolve(wx.request(options)); }).catch(() => reject({ errMsg: '登录已过期,请重新登录' })); } else { resolve(res); } }, fail: reject }); }); }; // 刷新token逻辑(调用后端/api/user/refresh接口) const refreshAccessToken = () => { return new Promise((resolve, reject) => { wx.login({ success: (loginRes) => { wx.request({ url: 'https://localhost:8080/api/user/refresh', method: 'POST', data: { code: loginRes.code }, success: (res) => { if (res.data.code === 200) { const newToken = res.data.data.token; wx.setStorageSync('token', newToken); resolve(newToken); } else { reject(); } }, fail: reject }); } }); }); };这个设计解决了“用户长时间不操作导致token过期,再点按钮直接白屏”的玄学问题。但要注意:/api/user/refresh接口必须在Java后端实现,且同样走wx.login()换取新openid,再生成新token——不能简单把旧token延长时间。
3.3 运动打卡计时功能的前端实现与数据上报
小程序端使用wx.startAccelerometer监听手机加速度变化,结合Date.now()计算运动时长,并在停止时将数据上报Java后端:
// pages/record/start.js Page({ data: { isRecording: false, duration: 0, // 秒数 timer: null }, startRecord() { if (this.data.isRecording) return; this.setData({ isRecording: true }); // 启动加速度监听(用于判断是否在运动) wx.startAccelerometer({ interval: 'game', success: () => { this.accelerometerListener = wx.onAccelerometerChange((res) => { // 简单阈值判断:加速度绝对值 > 12 即视为有效运动 const acc = Math.sqrt(res.x**2 + res.y**2 + res.z**2); if (acc > 12) { this.updateDuration(); // 每100ms更新一次时长 } }); } }); // 启动计时器 this.timer = setInterval(() => { this.setData({ duration: this.data.duration + 0.1 }); }, 100); }, stopRecord() { if (!this.data.isRecording) return; clearInterval(this.timer); wx.stopAccelerometer(); // 上报数据到Java后端 const durationSec = Math.round(this.data.duration); wx.request({ url: 'https://localhost:8080/api/record/save', method: 'POST', data: { userId: getApp().globalData.userId, duration: durationSec, calories: Math.round(durationSec * 0.05) // 简化计算:每秒消耗0.05千卡 }, success: (res) => { wx.showToast({ title: '打卡成功', icon: 'success' }); wx.navigateBack(); } }); } });这里的关键是wx.onAccelerometerChange的回调频率——设为'game'模式(约20ms一次)才能捕捉到细微动作,但会增加耗电;若设为'ui'(60ms)或'normal'(200ms),跑步时可能漏判。项目选择'game'是权衡结果,实际部署时建议在onHide生命周期里主动stopAccelerometer,避免后台持续耗电。
4. 数据库设计与MyBatis映射关键细节
4.1 核心表结构与索引策略
数据库共12张表,其中5张是业务主表。以下是user、course、reserve_record三张高频查询表的建表语句关键片段(来自fitness.sql):
-- 用户表:openid为唯一索引,避免重复注册 CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar_url` varchar(255) DEFAULT NULL COMMENT '头像', `gender` tinyint(1) DEFAULT '0' COMMENT '性别:0未知,1男,2女', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 课程表:status+start_time组合索引,加速“今日上架课程”查询 CREATE TABLE `course` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '课程标题', `coach_id` bigint(20) NOT NULL COMMENT '教练ID', `status` varchar(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT草稿,ONLINE上架,OFFLINE下架', `start_time` datetime NOT NULL COMMENT '开始时间', `max_people` int(11) NOT NULL DEFAULT '20' COMMENT '最大人数', `reserve_count` int(11) NOT NULL DEFAULT '0' COMMENT '已预约人数', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status_start` (`status`,`start_time`) USING BTREE, KEY `fk_coach_id` (`coach_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表'; -- 预约记录表:复合索引覆盖常用查询条件 CREATE TABLE `reserve_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '用户ID', `course_id` bigint(20) NOT NULL COMMENT '课程ID', `reserve_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '预约时间', `status` varchar(20) NOT NULL DEFAULT 'CONFIRMED' COMMENT '状态:CONFIRMED已确认,CANCELLED已取消', PRIMARY KEY (`id`), KEY `idx_user_course` (`user_id`,`course_id`) USING BTREE, KEY `idx_course_status` (`course_id`,`status`) USING BTREE, CONSTRAINT `fk_reserve_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_reserve_course_id` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表';注意:
idx_status_start索引能让SELECT * FROM course WHERE status='ONLINE' AND start_time > NOW()查询走索引,而不用全表扫描。很多毕业设计项目漏建这个索引,导致首页课程列表加载慢,被答辩老师当场指出。
4.2 MyBatis动态SQL实现课程多条件搜索
小程序“课程列表页”支持按关键词、教练、日期范围筛选。后端用MyBatis动态SQL避免拼接SQL字符串:
<!-- CourseMapper.xml --> <select id="selectByCondition" resultType="com.fitness.entity.Course"> SELECT c.*, u.nickname AS coach_name FROM course c LEFT JOIN user u ON c.coach_id = u.id WHERE c.status = 'ONLINE' <if test="keyword != null and keyword != ''"> AND (c.title LIKE CONCAT('%', #{keyword}, '%') OR u.nickname LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="coachId != null"> AND c.coach_id = #{coachId} </if> <if test="startDate != null"> AND c.start_time >= #{startDate} </if> <if test="endDate != null"> AND c.start_time <= #{endDate} </if> ORDER BY c.start_time ASC </select><if>标签确保只有参数非空时才追加WHERE条件,彻底规避SQL注入风险。对比手写StringBuilder.append()拼SQL,这种写法更安全、更易维护。但要注意:#{}是预编译占位符,${} 是字符串替换——此处全部用#{},绝不能用${} 处理用户输入的keyword。
4.3 PageHelper分页插件的正确集成方式
项目用PageHelper 5.2.0实现课程列表分页。关键不是“怎么引入jar包”,而是如何保证分页SQL在MyBatis执行前被拦截重写:
// CourseController.java @GetMapping("/list") @ResponseBody public Result list( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, String keyword, Long coachId, String startDate, String endDate) { // 1. PageHelper.startPage必须在service方法调用前执行 PageHelper.startPage(pageNum, pageSize, "start_time ASC"); // 2. 调用service,返回List<Course>(PageHelper会自动包装成Page<Course>) List<Course> courses = courseService.selectByCondition(keyword, coachId, startDate, endDate); // 3. Page对象自带分页信息,直接返回 PageInfo<Course> pageInfo = new PageInfo<>(courses); return Result.success().setData(pageInfo); }PageHelper.startPage()必须紧挨着service调用,且不能跨线程——如果service内部开了新线程(如异步发消息),分页会失效。这是PageHelper最经典的坑,90%的初学者第一次用都会踩。
5. 部署全流程与常见问题避坑指南
5.1 Windows环境下一键部署三步法
项目附带1-install.bat、2-run.bat、3-build.bat,但直接双击可能失败。正确流程是:
先执行
1-install.bat(以管理员身份运行CMD):- 它会自动创建数据库
fitness,执行fitness.sql导入表结构和初始数据; - 若报错“Access denied for user 'root'@'localhost'”,需先用MySQL Workbench或命令行登录,执行
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;; - 注意:
fitness.sql中的INSERT INTO user语句插入的是测试用户,其openid字段值是微信测试号生成的固定字符串,小程序端需同步修改utils/config.js中的appid和appsecret。
- 它会自动创建数据库
再执行
2-run.bat:- 它调用
mvn tomcat7:run启动内嵌Tomcat(端口8080); - 若提示“找不到mvn命令”,需先配置Maven环境变量,或改用IDEA右键项目 →
Run As→Maven build...→ Goals填tomcat7:run; - 启动成功后,浏览器访问
http://localhost:8080/fitness应看到欢迎页。
- 它调用
最后用微信开发者工具打开小程序工程:
- 在
project.config.json中确认"miniprogramRoot": "miniprogram/"; - 修改
utils/request.js中的baseUrl为https://localhost:8080(开发时允许不校验HTTPS); - 点击“编译”后,若控制台报
net::ERR_CONNECTION_REFUSED,说明Java后端没起来,回看第2步日志。
- 在
5.2 常见问题排查:现象→原因→解决
现象1:小程序登录后,首页课程列表为空,控制台报404
→ 原因:2-run.bat启动的是根路径/,但Java后端API实际部署在/fitness下(web.xml中<context-param>未生效)
→ 解决:打开web.xml,确认<context-param>中contextConfigLocation指向classpath:spring-context.xml,且spring-mvc.xml中<mvc:annotation-driven />已开启;更稳妥做法是修改2-run.bat,将mvn tomcat7:run改为mvn tomcat7:run -Dmaven.tomcat.path=/fitness,强制上下文路径。
现象2:预约课程成功,但数据库reserve_count没增加,且日志出现Lock wait timeout exceeded
→ 原因:courseDao.lockCourseById()的FOR UPDATE锁等待超时(默认50秒),通常因前一个事务未提交或死锁
→ 解决:检查CourseService.reserveCourse()方法是否被其他地方调用且未加@Transactional;用SHOW PROCESSLIST;查看MySQL是否有长时间运行的Sleep连接,KILL掉;在my.cnf中增加innodb_lock_wait_timeout = 120。
现象3:小程序上传头像后,Java后端接收的文件流为空,request.getInputStream().available()返回0
→ 原因:微信小程序wx.uploadFile默认Content-Type为multipart/form-data,但SSM未配置CommonsMultipartResolver
→ 解决:在spring-mvc.xml中添加:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <property name="defaultEncoding" value="UTF-8"/> </bean>并确保pom.xml引入commons-fileupload依赖。
现象4:PPT答辩时,演示视频播放卡顿,导出为MP4后体积暴涨至500MB
→ 原因:原始视频用OBS录制为MKV格式,未转码直接插入PPT
→ 解决:用FFmpeg转码:ffmpeg -i demo.mkv -c:v libx264 -crf 23 -preset fast -c:a aac -b:a 128k demo.mp4,可将500MB MKV压至80MB MP4,且PPT内嵌播放流畅。
现象5:答辩现场演示“扫码签到”功能,手机扫二维码后跳转空白页
→ 原因:二维码生成链接为https://localhost:8080/fitness/api/sign?code=xxx,但手机无法解析localhost
→ 解决:部署时用局域网IP替代localhost,如http://192.168.1.100:8080/fitness/api/sign?code=xxx;或使用微信开发者工具的“真机调试”功能,在手机上直接访问。
6. 从97分项目到可交付商业原型的三个进阶改造点
6.1 将SSM升级为Spring Boot的平滑迁移路径
这套SSM代码完全可升级为Spring Boot,但不必重写。我一般会这样做:
- 新建Spring Boot项目,
pom.xml引入spring-boot-starter-web、spring-boot-starter-jdbc、mybatis-spring-boot-starter; - 把原SSM的
src/main/java下所有包复制到新项目,src/main/resources下的*.xml和*.properties全部保留; - 创建
@Configuration类,用@Bean注册SqlSessionFactory和DataSource,替代原spring-context.xml; - 把
web.xml中的DispatcherServlet配置移到application.yml:
spring: mvc: static-path-pattern: /static/** resources: add-mappings: true- 最关键一步:在
CourseController上加@RestController,删掉所有@ResponseBody,因为Spring Boot默认开启; - 启动类加
@SpringBootApplication(scanBasePackages = "com.fitness"),确保扫描到你的包。
这样改完,mvn spring-boot:run就能跑起来,性能提升30%,且后续接入Redis、MQ、Elasticsearch更方便。
6.2 小程序端接入微信运动数据的实操方案
原项目“运动打卡”是模拟加速度,若要真实接入微信运动步数,需调用wx.getWeRunData:
// 用户授权后 wx.getWeRunData({ success: (res) => { const encryptedData = res.encryptedData; const iv = res.iv; // 将encryptedData和iv发送到Java后端,用session_key解密 wx.request({ url: 'https://localhost:8080/api/user/decrypt-werun', method: 'POST', data: { encryptedData, iv }, success: (res) => { console.log('步数数据:', res.data.step); } }); } });Java端解密代码(需引入bcprov-jdk15on依赖):
public static String decryptWeRunData(String encryptedData, String iv, String sessionKey) { try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); SecretKeySpec keySpec = new SecretKeySpec(Base64.decode(sessionKey), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(Base64.decode(iv)); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] result = cipher.doFinal(Base64.decode(encryptedData)); return new String(result, "UTF-8"); } catch (Exception e) { throw new RuntimeException("解密失败", e); } }解密后JSON里step字段就是当日步数。这个功能加进去,答辩时老师会眼前一亮——说明你懂微信生态的真实能力边界。
6.3 数据库从MySQL迁移到PostgreSQL的兼容性清单
若学校机房只提供PostgreSQL,这套代码也能跑,只需改5处:
| 文件位置 | MySQL写法 | PostgreSQL写法 | 说明 |
|---|---|---|---|
jdbc.properties | jdbc:mysql://... | jdbc:postgresql://... | 驱动类改为org.postgresql.Driver |
fitness.sql | AUTO_INCREMENT | SERIAL | 主键自增改为SERIAL类型 |
fitness.sql | DATETIME | TIMESTAMP WITHOUT TIME ZONE | PostgreSQL无DATETIME类型 |
UserMapper.xml | SELECT LAST_INSERT_ID() | SELECT currval('user_id_seq') | 获取自增ID方式不同 |
pom.xml | mysql-connector-java | postgresql | 替换JDBC驱动 |
我一般会写个Python脚本自动转换SQL:匹配AUTO_INCREMENT替换为SERIAL,DATETIME替换为TIMESTAMP,再人工校验外键约束语法。这样半天就能搞定迁移,比重写ORM快得多。
从那以后我每次接手SSM毕业设计,都强制走一遍“三验”:验数据库索引是否覆盖高频查询、验小程序token是否自动刷新、验预约接口是否加了FOR UPDATE。这三个点卡住了90%的答辩翻车现场。希望帮到你。
本文还有配套的精品资源,点击获取