ARTICLE DETAIL

资讯详情

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

健身小程序+SSM后端实战:从SSM常用注解到小程序抓包联调

健身小程序+SSM后端实战:从SSM常用注解到小程序抓包联调 做课程设计或者毕业设计选型的时候很多人一看到“健身小程序”就直接往uni-app、Vue、云开发那边跑了结果做到一半发现前后端联调、数据权限、课程预约状态同步这些问题一个比一个麻烦。如果你手头拿到的是一套weixin159健身小程序SSM文档源码这种项目那其实走的是另一条更稳的路子微信小程序原生前端 SSM后端 MySQL数据库整套前后端分离、接口联调、部署上线都有完整流程可以复现。这篇博文我按自己实际开发这类项目的经验把这个健身小程序项目中你会遇到的核心需求、技术选型逻辑、关键代码实现、数据库设计以及最容易踩坑的联调环节全部拆开讲一遍。无论你是拿它做课设、毕设还是想改造成商用健身预约系统里面的思路和代码结构都能直接参考。折痕的东西我少讲直接讲能落地的。1. 健身类小程序项目的整体需求与技术选型1.1 一份健身管理系统的典型需求拆解别小看健身小程序名字听起来简单实际上业务线不少。我拿到这类项目的第一步是先梳理角色和核心流程。一般来说这套系统涉及三类角色用户端普通健身者注册登录、浏览课程、预约私教课程、查看健身计划、记录身体数据体重、体脂、围度、查看预约记录、取消预约、个人资料管理。教练端或管理员角色课程发布与管理、私教可约时段设置、处理预约申请、维护学员信息、录入课程反馈。管理后台SSM侧用户管理、课程分类管理、教练信息管理、预约单状态管理、数据统计大屏可用ECharts做简单报表。我在帮朋友改过一套类似的系统时发现需求方最看重的核心功能是“课程预约流程”也就是用户浏览课程 → 查看课程时间段 → 提交预约 → 后台确认或自动确认 → 预约状态更新 → 用户端显示结果。整个链路涉及两张核心表和至少5个接口如果能把这个主流程跑通系统的主干就算立住了。1.2 为什么是“小程序 SSM”而不是云开发或前后端不分离这个问题很多人问。我的看法是选SSM这套方案看重的是三点第一定制空间大。云开发确实快但业务逻辑一旦复杂起来比如多种角色权限、复杂的预约时间校验、课程库存扣减云函数那套模板反而束缚手脚。SSM三层架构下Controller-Service-Mapper各司其职改一个预约规则不需要动前端直接改Service层就行。第二文档和源码配套完整。这听起来不像是技术理由但对我们做课设、毕设的人来说太关键了。一套附带了完整文档的项目意味着你可以从需求分析、数据库设计说明、接口文档一路看到源码实现不用盲人摸象。文档里的ER图和数据字典甚至可以加分项直接搬到论文里。第三面试或答辩可讲性强。SSM是Java后端面试的经典话题Spring的IOC/AOP、SpringMVC的请求映射流程、MyBatis的Mapper代理机制这些你在改造项目时都会真实碰到答辩时随便挑一个点都能聊十分钟。顺带提一句如果你非要用uni-app写小程序端这套SSM后端照样能对接因为接口就是纯HTTP JSON前端框架随便换这是我不推荐小程序端绑定任何后端云服务的原因。前后端保持独立将来扩展Web管理端、App端都容易得多。1.3 项目目录结构与代码分层思路一套合格的SSM健身小程序项目后端目录通常会这样组织ssm_fitness/ ├── src/main/java │ ├── controller/ # 接口层UserController, CourseController, OrderController... │ ├── service/ # 业务逻辑层接口实现类 │ ├── dao/ # MyBatis的Mapper接口 │ ├── entity/ # 实体类User, Course, CourseOrder... │ ├── common/ # 统一返回结果、异常处理、工具类 │ └── config/ # Spring配置、拦截器配置 ├── src/main/resources │ ├── mapper/ # MyBatis的XML映射文件 │ ├── spring/ # Spring、SpringMVC、MyBatis配置文件 │ └── jdbc.properties # 数据库连接配置 └── pom.xml小程序端则按微信小程序的官方结构组织miniprogram/ ├── pages/ │ ├── index/ # 首页课程推荐、轮播图 │ ├── course/ # 课程列表页 │ ├── courseDetail/ # 课程详情页 │ ├── order/ # 预约下单页 │ ├── myOrder/ # 我的预约 │ ├── user/ # 个人中心 │ ├── login/ # 登录页 │ └── bodyData/ # 身体数据记录 ├── utils/ │ ├── request.js # 封装wx.request │ ├── auth.js # 登录态判断 │ └── util.js ├── app.js ├── app.json └── project.config.json我拿到项目后的第一步永远是先看目录结构确认Controller和Mapper的对应关系是否清晰因为这决定了后面改代码时要不要满世界找文件。如果分包分得不干净后面每加一个功能都要多花半小时。2. 核心功能的数据表设计与SSM关键注解实践2.1 数据库表设计五张表撑起主业务这一块必须认真说因为很多同学拿着源码跑起来之后想改需求结果一改表结构就报错。我们先看这套项目最核心的表设计以下是我见过的一套比较标准的健身系统数据库实际源码中的字段可能略有增减但思路一致。表名核心字段说明userid, openid, nickname, avatar, phone, height, weight, create_time用户表openid是微信登录唯一标识coachid, name, avatar, specialty, introduce, stars教练表主要存储教练个人主页信息courseid, name, type, cover, coach_id, start_time, end_time, max_people, booked_count, price, status课程表与教练表多对一course_orderid, order_no, user_id, course_id, order_status, create_time预约/订单表记录谁约了哪节课body_recordid, user_id, weight, body_fat, waistline, record_date身体数据记录表用于记录用户的健身进度这些表之间最需要想清楚的是course和course_order的关系一节课可以被多人预约一对多一个人可以预约多节课一对多即多对多所以中间用订单表关联。在代码里体现为CourseOrder实体类持有courseId和userId两个外键字段。有一个细节很容易被忽略课程表里的booked_count和max_people是一对儿。秒杀系统超卖的问题在这类小项目里同样存在如果预约时只校验booked_count max_people再插入订单高并发下就可能超卖。解决办法后面我会在实战章节细说。如果你要改表强烈建议先用ALTER TABLE加字段再去实体类里同步属性最后去Mapper XML里检查resultMap或者SQL字段映射顺序反了会导致各种神奇的报错。我检查过很多份源码最常见的低级错误就是实体类加了字段但XML的sql片段里没加结果前端拿不到新数据。2.2 SSM常用注解与它们在实际项目里的用法热词里出现了SSM常用注解这里按实际使用频率把我认为最重要的注解逐个说明Spring部分Component/Service/Repository这三个注解本质都是把Bean交给Spring容器管理。在健身项目中UserServiceImpl上用ServiceUserMapper接口上用Repository需要配合MapperScan或者Mapper接口上加Mapper。注意如果Spring扫描不到Mapper最典型的报错是NoSuchBeanDefinitionException。Autowired依赖注入。我在代码里看到不少同学用字段注入其实更推荐构造器注入比如Service public class CourseServiceImpl implements CourseService { private final CourseMapper courseMapper; private final CourseOrderMapper orderMapper; Autowired public CourseServiceImpl(CourseMapper courseMapper, CourseOrderMapper orderMapper) { this.courseMapper courseMapper; this.orderMapper orderMapper; } }这样写的好处是依赖关系显性化而且测试的时候可以直接传mock对象进去。SpringMVC部分RestController直接让Controller类所有方法返回JSON健身项目里所有接口都建议用这个省得每个方法都写ResponseBody。RequestMapping/GetMapping/PostMapping接口路径映射。推荐组合使用语义更清晰。比如RestController RequestMapping(/api/course) public class CourseController { GetMapping(/list) public Result getCourseList(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size) { // 分页查询逻辑 } PostMapping(/book) public Result bookCourse(RequestBody BookRequest req) { // 预约逻辑 } }RequestParam接收URL上的参数常用于分页参数、课程类型筛选。注意前端如果是小程序传参经常用的wx.request里的data字段POST请求默认是application/json如果你后端接口用的是RequestParam小程序端就得用header: {content-type: application/x-www-form-urlencoded}或者手动拼参数字符串。这个坑我踩过后面前后端联调再细讲。RequestBody接收JSON对象前端传复杂对象比如预约请求里包含用户ID、课程ID、备注信息时必用。MyBatis部分Mapper/MapperScan让MyBatis自动扫描并生成Mapper代理对象。小项目直接在启动类上写MapperScan(com.example.dao)最省事。Param当Mapper接口方法有多个参数时XML里引用参数名全靠它。比如ListCourseOrder selectByUserIdAndStatus(Param(userId) int userId, Param(status) int status);XML里写#{userId}和#{status}才能取到值。2.3 实体类、Controller、Service、Mapper之间如何对齐这个小节说的是代码结构层面的对齐很多同学拿到别人的源码后会困惑这个字段好像没用到啊这个Service方法在哪定义我的经验是先按调用链读代码小程序前端发起请求 → Request.js工具类 → 后端Controller接收 → Service处理 → Mapper查数据库反着读也行从Mapper层往外读。任何一个接口功能都必须能在这一条链上找到完整的落点否则就是死代码。拿课程列表加载更多这个功能举例。前端小程序一般在pages/course/course.js里有onReachBottom() { if (this.data.page * this.data.size this.data.total) { return; } this.setData({ page: this.data.page 1, loading: true }); this.fetchCourses(); }对应的后端Mapper就是一个分页查询select idselectCoursePage resultTypeCourse SELECT * FROM course where if testtype ! null and type ! AND type #{type} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectController里接收page和size参数计算offset (page - 1) * size返回数据时附带一个total字段。前端根据total判断是否还有更多数据可加载。这个模式在微信小程序里叫上拉加载更多在开发工具里的操作是在页面json里开启enablePullDownRefresh: false然后在onReachBottom里触发属于必考功能。3. 环境搭建与实战联调从源码到跑通全流程3.1 环境准备清单不管你手里是什么样的SSM项目第一步永远是把环境对齐。经常有同学问我为什么他的项目在我电脑上跑不起来答案大概率是环境版本不一致。这里我给一个经过验证的版本组合组件推荐版本说明JDK1.8或11部分老源码在JDK17下会报反射异常先按源码默认来Maven3.6.x用IDEA内置的也行Tomcat8.5或9.0老项目用8.5更稳MySQL5.7或8.0如果源码用了utf8mb4字符集8.0没问题微信开发者工具最新稳定版注意在详情里设置不校验合法域名IDEA2021.xUltimate版对Spring支持更好先启动数据库创建数据库并执行源码中提供的SQL脚本一般叫fitness.sql或init.sql然后改jdbc.properties里的数据库账号密码再启动Redis如果项目用了和Tomcat。跑通后端之后微信开发者工具里导入小程序目录开通服务端口在utils/request.js里把baseUrl改成后端地址比如http://localhost:8080/ssm_fitness。一个小技巧如果小程序端请求后端时出现ERR_CERT_COMMON_NAME_INVALID之类的错误八成是后端用了HTTPS证书而本地是HTTP。开发环境直接勾选不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书别浪费时间折腾本地证书。3.2 微信登录的完整流程与openid获取整个健身系统里用户身份是功能的基础你要记录用户约了什么课总得知道是谁约的。微信小程序的登录流程是标准的code2Session模式前端调用wx.login()获取临时code→ 把code发给后端 → 后端拿着code 小程序的 appid secret 去https://api.weixin.qq.com/sns/jscode2session换取openid和session_key→ 后端用openid查用户是否存在不存在就自动注册 → 返回自定义登录态一般用token给前端。核心后端代码类似这样RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public Result login(RequestBody LoginRequest req) { String code req.getCode(); // 调用微信接口获取openid String openid userService.getOpenidByCode(code); // 根据openid查用户不存在则创建 User user userService.findOrCreateUser(openid); // 生成token并返回 String token UUID.randomUUID().toString().replace(-, ); // 实际项目会存Redis简单项目可存内存Map或数据库字段 return Result.success(token); } }这里说一个容易被忽视的细节code 是一次性的且有效期只有5分钟。如果你在调试时发现第二次调用login接口报invalid code大概率是后端打了断点导致code被重复使用了重新wx.login()拿新code就行。另外小程序的appid和secret不是写在代码里的硬编码而是要放到配置文件里别提交到Git仓库否则答辩结束你的secret可能就泄露了。很多课程设计项目为了省事直接硬编码我是强烈不建议的。3.3 课程预约的核心链路与并发防超卖现在聊重点中的重点课程预约。这条链路是这样的前端提交预约请求包含courseId、userId或token。后端Controller接收调用Service层。Service层先校验课程是否存在、是否已下架、是否达到人数上限。校验通过后插入订单记录。更新课程表的booked_count。返回预约成功。代码示意Transactional public Result bookCourse(int courseId, int userId) { Course course courseMapper.selectById(courseId); if (course null) { return Result.error(400, 课程不存在); } if (course.getStatus() ! 1) { return Result.error(400, 课程已下架); } // 判断是否已经预约过 int count orderMapper.selectCountByUserAndCourse(userId, courseId); if (count 0) { return Result.error(400, 您已预约过该课程); } if (course.getBookedCount() course.getMaxPeople()) { return Result.error(400, 课程人数已满); } CourseOrder order new CourseOrder(); order.setOrderNo(FT System.currentTimeMillis()); order.setUserId(userId); order.setCourseId(courseId); order.setOrderStatus(0); // 0待确认 1已确认 2已取消 orderMapper.insert(order); // 更新已预约人数 courseMapper.addBookedCount(courseId); return Result.success(预约成功); }这里要特别提醒这个写法在单个用户串行请求下没问题但两个人同时抢最后一个名额就可能超卖。因为两个请求都查到bookedCount maxPeople - 1然后都插入订单最终课程超载。解决办法有两种第一种是给订单表加唯一约束比如UNIQUE(user_id, course_id)数据层面杜绝重复预约。第二种是在更新已约人数时使用乐观锁SQLUPDATE course SET booked_count booked_count 1 WHERE id #{courseId} AND booked_count max_people然后判断受影响行数如果为0说明没抢到。这比Java代码层面先查后改更可靠。我在自己写课设时更倾向于第二种因为便于在答辩时解释防止超卖这个亮点。3.4 小程序端请求封装与列表加载更多实战前端这里我多说两句因为很多同学拿到源码后发现小程序端请求写得杂乱无章甚至每个页面都写一遍wx.request后期维护起来很痛苦。我习惯统一封装在utils/request.js里const BASE_URL http://localhost:8080/api; 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, token: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200) { resolve(res.data); } else { wx.showToast({ title: 请求失败, icon: none }); reject(res); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };封装好之后页面里调用就干净了fetchCourses() { request(/course/list?page this.data.page size this.data.size) .then(res { if (res.code 200) { // 追加数据 this.setData({ courseList: this.data.courseList.concat(res.data.list), total: res.data.total, loading: false }); } }); }列表加载更多这个功能在真正的小程序页面上需要处理几个小细节首次加载时展示loading框翻页时底部显示加载中数据加载完成后显示已全部加载。这看起来简单但实际写的时候很多人会把concat忘了导致每次翻页都覆盖旧数据或者是onReachBottom下拉触底时还没来得及防抖就连续发了一堆请求。我的经验是加一个布尔开关isLoading在请求未返回前拦截新的触底事件这一步很重要但经常被忽略。4. 工具链与调试技巧抓包、看日志、查接口4.1 小程序抓包Charles与开发工具自带Network面板热词里反复出现了charles抓包微信小程序小程序抓包这是真实开发中非常刚需的技能。调试后端接口时你光看后端日志还是不够的有时候前端传出来的参数就是不对你需要看真实请求长什么样。Charles在电脑上抓包小程序流量有两种常见方式方式一微信开发者工具自带Network面板。在开发者工具里打开调试器切到Network标签能看到所有请求的Headers、Payload、Response。这最简单开发环境优先用它。方式二真机调试用Charles。电脑和手机连同一个WiFi手机设置HTTP代理指向电脑IP和Charles的8888端口然后在Charles里安装SSL证书并开启SSL Proxying设置*:443重启小程序就能看到请求内容了。我在帮别人排查问题时最常干的动作就是打开Network面板看请求路径、请求头、响应具体内容这往往能在10秒内定位问题比漫无目的地看代码快得多。需要注意真机抓包时Charles里要开启SSL Proxying否则看到的HTTPS流量是乱码。电脑上还要在代理设置里放行localhost。4.2 后端排查三板斧日志、接口自测、SQL打印后端出问题的时候顺序很重要。我总结了一套三板斧排查方法第一斧看控制台日志。SpringMVC启动时如果报Invalid bound statement (not found)说明MyBatis的XML文件路径没对上或者没被扫描到。这是最常见的错检查mybatis.mapper-locations配置是否为classpath:mapper/*.xml。第二斧直接后端自测。使用Postman或者Apifox先不经过小程序端单独测接口。比如测试预约接口填好JSON数据如{userId: 1, courseId: 2}看返回结果。如果后端接口通问题就在前端如果后端不通就锁定到后端逻辑。第三斧打印SQL语句。在MyBatis配置里开启日志让每一条执行的SQL都打印到控制台。MyBatis 3.4 可以设置setting namelogImpl valueSTDOUT_LOGGING /这样你能直观看到执行了什么SQL、参数值是什么、有没有执行成功。我遇到过不少同学说后端没报错但数据库数据没变打开SQL日志一看原来SQL压根就没执行。4.3 会话保持与Token机制由于是小程序不能像网页端那样依赖CookieSSM项目里常见做法是登录成功后返回一个token存到小程序的wx.setStorageSync(token, token)。每次请求都在header里带上这个token后端用一个拦截器来校验token。拦截器核心逻辑public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(token); if (token null || !tokenService.checkToken(token)) { response.setStatus(401); return false; } return true; } }然后在SpringMVC配置里注册拦截器并排除/api/user/login、/api/course/list这些公开接口。这里有个容易让新手困惑的点小程序端设置请求头里的token和服务端取请求头的token两边字段名必须一致。前端写的是header: {token: value}后端就要用request.getHeader(token)大小写不敏感但单词要拼对。我见过前端写Token后端读token结果还能通因为HTTP头大小写不敏感但如果前端写了authorization后端读token那肯定401没跑了。5. 常见典型问题与源码改造扩展方向5.1 真机预览与开发工具调试差异很多人在开发者工具里调得好好的一进真机预览就各种问题。最常见的原因第一局域网IP问题。后端地址写成http://localhost:8080开发者工具里能跑通但手机上的localhost是手机自己不是电脑。改成电脑的局域网IP比如http://192.168.1.100:8080并且确保防火墙放行了8080端口。第二域名校验问题。微信要求所有请求必须是HTTPS且域名备案。开发阶段可以在开发者工具里勾选不校验合法域名但真机预览这个选项不生效除非开启调试模式。你需要在project.config.json里设置urlCheck: false或者用预览功能时的调试模式来绕过校验。第三手机和电脑不在同一网络。这个比较基础了但真的会有人忽略。务必确认手机和电脑连的是同一个路由器的网络公司或学校网络可能隔离设备也会导致访问不上。5.2 微信小程序页面配置、导航栏与标题动态设置热词里提到了小程序动态设置标题和小程序头部标题这是两个不同问题。关于头部标题在app.json里可以统一设置{ window: { navigationBarTitleText: Fitness健身, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black } }微信小程序的导航栏样式是通过app.json全局配置和页面json局部配置来控制的每个页面的navigationBarTitleText可以在对应页面的.json文件中覆盖。动态设置标题指的是通过wx.setNavigationBarTitle({title: 课程详情})实时修改当前页面标题。这在课程详情页很常见比如一打开页面标题先显示课程详情等数据加载完后动态把课程名设置成标题。这里有个小技巧有些深色背景的健身类界面可以把navigationBarBackgroundColor设成深色navigationBarTextStyle设成white两种颜色搭配观感上会更符合健身App的气质。5.3 源码改造建议从课设项目到可商用系统拿到源码跑通只是第一步真正让项目在答辩或简历上出彩的是你自己动手做的改造。我梳理了一下健身项目常规的几个提升方向第一个方向使用Redis改造会话与热点数据。登录token存Redis并设置过期时间课程列表缓存到Redis减少数据库压力。这个改造成本不高但面试官一听就懂。第二个方向引入Spring Security或Shiro做权限控制。原生的拦截器虽然够用但在安全框架面前还是显得单薄加入PreAuthorize(hasRole(ADMIN))这种注解会让代码显得更正规。第三个方向增加统计报表功能。用ECharts在小程序端展示最近7日的预约量、用户增长趋势、热门课程排行。后端只需要提供几个聚合SQL查询接口前端用ec-canvas组件绘图视觉效果和社会热度都有明显提升。第四个方向引入uni-app或Taro进行多端适配。如果你想把小程序端沉淀成一套跨端方案将原生小程序迁移到uni-app的Vue语法上后端SSM接口不用改同时可生成H5和App。迁移过程我写过很多次只要你的页面不是特别依赖微信原生组件80%的代码可以直接复用而SSM后端完全不需要动。这些方向我都有实践过尤其第三个是最容易的毕竟健身项目的课程预约数据天然就是一张统计报表加一个SELECT DATE(create_time) AS day, COUNT(*) AS cnt FROM course_order GROUP BY day就够画第一张图了。最后说点实际的体会我见过太多人拿到源码后跑起来就说做完了然后答辩被问一句这个项目有什么难点就哑口无言。源码只是个基线你的价值在于能在基线之上做思考、做改造、做取舍要花时间把核心预约流程、登录机制、防超卖方案彻底看懂然后在文档之外跑一跑、改一改这套系统才真正属于你。遇到问题别急着找人带先按抓包、SQL日志、断点调试的顺序自己排查一遍很多时候你离答案只差一个Network面板的距离。
返回列表