
简介微信小程序高校体育场管理系统是基于SSM框架开发的完整课程设计源码包面向高校软件工程、计算机相关专业学生及需要完成微信小程序Java后端项目的开发者。系统覆盖场地预约、扫码签到、设施维护、活动发布、健康数据分析、会员积分、智能安防监控及后台管理等功能前端为微信小程序后端采用Spring、Spring MVC、MyBatis适合用于毕业设计、课程实训或项目二次开发。压缩包共1299个文件约50.11MB主要包含java后端源码、vue后台管理页面、微信小程序wxml/wxss/js文件、png图标及jpg素材并附带sql数据库脚本和安装运行脚本1-install.bat、2-run.bat目录结构清晰便于直接导入开发工具运行调试。已有191人学习下载。资源不仅给出完整可运行项目还涵盖前后端分离实现思路、后台管理界面、数据表设计及配置文件能帮助读者快速理解SSM与小程序对接过程节省从零搭建的时间适合边看边练、按需拆解学习。1. 用微信小程序 SSM 做高校体育场管理先想清楚这三件事高校体育场管理的核心矛盾从来不是“场地不够”而是“信息不同步”。学生不知道哪个场子空着管理员不知道谁来核销周末羽毛球馆排队长龙工作日篮球场空置一整天。用微信小程序做前端、SSM 做后端接口本质上是在解决一条预约链路里三个角色的协作问题学生在线查场、选时、预约管理员审核、排期、核对入场系统本身处理冲突检测和状态流转。这套技术选型在高校场景里比 Vue Spring Boot 更“顺手”的原因有三个第一微信小程序天然覆盖学生群体不用装 App、不用注册额外账号wx.login 就能拿到 openid第二SSM 虽然“老”但 MyBatis 对复杂 SQL 的控制力极强场地时间段冲突检测这种逻辑写在 XML 里比写在 ORM 注解里直观得多第三高校机房和毕设服务器普遍是低配 TomcatSSM 的部署成本远低于微服务全家桶。如果你是学生要复现这个项目或者刚入职接手类似系统下面这套方案就是最直接的落地路径。2. SSM 后端先把场地、预约、用户三张表设计对2.1 为什么不绕过 SSM 直接用 Spring Boot很多人的第一反应是都什么年代了还 SSM确实Spring Boot 在配置上碾压 Spring MVC XML但高校体育场管理系统这种项目用 SSM 有它不可替代的理由。一是很多高校的选修课、毕设指导仍然以 SSM 为教学蓝本代码评审时导师看的就是三层架构拆得清不清楚二是我见过不少校内服务器还跑着 JDK 8 Tomcat 8Spring Boot 2.7 在这些环境里兼容性反而没 SSM 稳。SSM 的分层在体育场预约场景里对应得很清晰Spring 管 Service 层的事务边界SpringMVC 管 Controller 层的参数绑定和异常处理MyBatis 管数据访问。事务尤其关键——用户提交预约时要做“查时段 锁记录 插入订单”三步这三步必须在一个事务里。Spring 的Transactional直接标在 Service 实现类上比 Spring Boot 的声明式事务更直观排查问题时一眼能看出边界在哪。2.2 三张核心表场地表、订单表、用户表的字段取舍体育场管理系统的表结构核心是这五张venue(场地)、venue_schedule(可预约时段)、reservation(预约单)、user(用户)、notice(公告)。场地表别只存名称和类型要带open_time和close_time散场打扫时间也要留一个字段maintain_duration否则后续做时段生成时会发现保洁时间没法处理。订单表的状态字段用 tinyint 而不是 varchar0待审核、1已确认、2已核销、3已取消、4已过期比存字符串省空间也方便 SQL 里直接做统计。CREATE TABLE reservation ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号格式日期场地编号随机数, user_id bigint(20) NOT NULL, venue_id bigint(20) NOT NULL, schedule_id bigint(20) NOT NULL COMMENT 对应venue_schedule表, date date NOT NULL COMMENT 预约日期, start_time time NOT NULL, end_time time NOT NULL, status tinyint(4) NOT NULL DEFAULT 0, create_time datetime NOT NULL, audit_time datetime DEFAULT NULL, audit_user varchar(50) DEFAULT NULL, cancel_reason varchar(200) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_date (user_id, date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的schedule_id不是必须的但加上它有个实际好处后台排期时如果调整了某个时段的起止时间已预约但未核销的订单可以通过关联 schedule 直接批量变更不用逐条改时间。唯一索引建在order_no上防重复提交idx_user_date是为“我的预约列表”这页准备的避免 date 条件触发全表扫描。2.3 MyBatis 里写冲突检测 SQL比在 Java 里做内存判断可靠预定场地的核心并发问题是两个用户同时提交同一个时间段的预约数据库在事务隔离级别下会有幻读。解决方式是在 MyBatis 的 XML 里写一条带锁的查询用SELECT ... FOR UPDATE锁住目标时段的排期记录然后再执行插入。select idcheckConflict resultTypeint SELECT COUNT(*) FROM reservation WHERE venue_id #{venueId} AND date #{date} AND status IN (0, 1) AND #{startTime} lt; end_time AND start_time lt; #{endTime} FOR UPDATE /select这段 SQL 的逻辑是判断新预约的时间区间是否与已存在的有效预约重叠新开始时间小于已有结束时间、且新结束时间大于已有开始时间两个条件同时成立即为冲突。状态只查 0待审核和 1已确认已取消和已过期的订单不参与冲突判断。FOR UPDATE是行级锁事务提交后释放。配合 Service 层的Transactional就能在低并发场景下做到不超卖。这里的关键点是lt;是 XML 里的转义写法直接写会报 XML 解析错误。3. 微信小程序端从 tabBar 到预约页面的完整实现3.1 小程序项目结构和 tabBar 配置别用 HBuilderX 硬套模板微信小程序端的目录结构按照页面功能划分pages/index(首页)、pages/venue(场地列表)、pages/reserve(预约)、pages/my(个人中心)、pages/admin(仅管理员可见)。如果你先写的是 uniapp编译到微信小程序时要注意rpx和px的混用问题——直接改 app.json 里的tabBar最稳。tabBar 的图标尺寸严格限制在 81px * 81px超过会警告但不报错实际显示会变形。{ pages: [ pages/index/index, pages/venue/venue, pages/reserve/reserve, pages/my/my ], tabBar: { color: #999999, selectedColor: #1AAD19, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/venue/venue, text: 场地 }, { pagePath: pages/reserve/reserve, text: 预约 }, { pagePath: pages/my/my, text: 我的 } ] } }tabBar只能是页面级文件不能指向组件页面。如果你把预约页做成了分包加载切记不要放进 tabBar分包页面作为 tabBar 项是直接报错的。另外pages/reserve/reserve这种路径写全不要试图用相对路径../reserve/reserve。3.2 wx.login 拿到 code 后后端必须做的事小程序登录的核心流程是wx.login拿临时code发送到后端后端用codeappidsecret调微信的jscode2session接口换取openid和session_key。openid是你系统里用户的唯一身份session_key只是用来解密用户手机号的钥匙不需要持久化。后端拿到 openid 后第一件事是查user表里有没有这个用户没有就自动创建一条记录默认角色设为学生。然后生成你自己的token用 UUID 或者 JWT 都行返回给小程序端。小程序端把这个 token 存进wx.setStorageSync(token, ...)之后所有请求在 header 里带Authorization: token。别直接把 openid 返回给前端一旦被抓包openid 泄露等于用户身份被冒用。PostMapping(/login) ResponseBody public Result login(RequestBody MapString, String params) { String code params.get(code); String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code; // 用 HttpClient 发送 GET 请求拿回 openid String openid getOpenidFromWx(url); User user userMapper.findByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setRole(0); // 0学生, 1管理员 userMapper.insert(user); } String token UUID.randomUUID().toString().replace(-, ); redisTemplate.opsForValue().set(token, openid, 2, TimeUnit.HOURS); return Result.success(token); }这一步里appid和secret不要硬编码在代码里请放在配置文件用Value注入。回调地址要保持和微信公众平台后台配置的域名一致本地调试时可以在开发者工具里勾选“不校验合法域名”但真机预览必须配 HTTPS 域名。3.3 预约页面的时间选择Uniapp 和原生组件的差异处理如果你用的是原生小程序picker组件的modedate和modetime是最直接的方案。但高校体育场预约通常要按场地类型不同显示不同时段比如羽毛球馆每 40 分钟一个场次、篮球场每 2 小时一个场次这就要动态渲染时段列表而不是用picker。用 uniapp 开发时要注意uni-datetime-picker在 iOS 上的渲染 bug——它如果被放在scroll-view里快速滚动时可能出现选完日期后弹层不消失的问题。解决办法是不在scroll-view内嵌这个组件把日历弹层通过v-model绑定到页面根节点或者干脆自己写一个时段 grid。你去看网上那些“uniapp 微信小程序兼容”的帖子讨论最多的就是这类弹层组件在 WebView 和原生渲染下的行为差异。最省事的方式后端一次性返回该场地未来 7 天的可预约时段数组前端用普通 view 渲染成卡片用户点选。4. 前后端联调Session、日期解析、滚动加载的实战处理4.1 别用 Session 存登录态改 Redis否则小程序请求拦不住SSM 传统做法是用 HttpSession 保存用户状态但小程序端每次请求都是独立的 HTTPS 请求Cookie 在部分场景下不会被正确携带尤其是用户关闭小程序再打开后Session 恢复是个大坑。常见做法是后端写一个拦截器从 header 里取 token查 Redis——为什么不是查数据库因为用户表命中的频率远高于业务表如果用数据库每次请求多一次查询高峰期能把数据库连接池打满。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } String openid redisTemplate.opsForValue().get(token); if (openid null) { response.setStatus(401); return false; } request.setAttribute(openid, openid); return true; } }拦截器要在 spring-mvc.xml 里注册mvc:interceptors同时把/login路径排除掉白名单里再加一个/notice/list公告列表不需要登录就能看。Redis 的 key 过期时间设为 2 小时学生一次体育课时长基本够了管理员操作频率低可以单独延长。4.2 日期参数绑定DateTimeFormat 处理小程序传的时间字符串小程序端传日期一般有两种格式2024-05-20和2024-05-20 14:00:00。如果你的接口用Date类型接收不加DateTimeFormat会直接报 400 错误。这个坑几乎每个写 SSM 小程序联调的人都会踩一次。GetMapping(/available) ResponseBody public Result getAvailableTime(RequestParam Long venueId, RequestParam DateTimeFormat(pattern yyyy-MM-dd) Date date) { ListTimeSlot slots venueService.getSlots(venueId, date); return Result.success(slots); }DateTimeFormat只处理入参的字符串转 Date出参返回给小程序时用JsonFormat(pattern yyyy-MM-dd HH:mm:ss)标注在实体字段上。这里还有个常见错误DateTimeFormat和JsonFormat一个是 Spring 的、一个是 Jackson 的混用时要搞清楚你项目里 fastjson 和 Jackson 谁生效否则这边转了那边又变了。4.3 场地列表滚动加载onReachBottom 不是唯一解体育场列表如果只有十几个场地一次性返回没问题。但如果是大型高校场地可能分校区、分运动类型列表超过 30 条就需要分页。小程序端用onReachBottom触发加载下一页是原生的做法。配合 SSM 后端的分页插件 PageHelper写法上有个约定要遵守PageHelper 的startPage必须紧跟着 Mapper 的查询方法中间不能有任何其他 SQL 操作否则分页会串到别的查询上。Override public ListVenue getVenueList(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); return venueMapper.selectAll(); }前端在onReachBottom里判断hasMore字段为 true 才累加页码重新请求。注意下拉刷新用enablePullDownRefresh在微信开发者工具里模拟下拉是鼠标按住顶部向下拖真机是手势操作这两者触发的行为不完全一致测试时要分开测别只在开发者工具里点一下就结束。4.4 预约成功后的反馈微信订阅消息还是公众号模板消息场地审核通过后需要通知学生。微信在 2020 年后收紧了模板消息现在小程序只能用订阅消息而且需要用户主动点击授权按钮才能发送一次。常见做法是预约提交成功后弹出一个授权面板用户点“允许”后存下form_id等管理员审核通过后后端拿着这个form_id调发送接口。wx.requestSubscribeMessage({ tmplIds: [模板ID在这里], success(res) { if (res[模板ID在这里] accept) { // 用户授权了一次可以发送一条订阅消息 } } })这里有个细节订阅消息一次性授权只能发一次学生如果约了 10 次球每次都要点一次授权。所以在预约流程里授权弹窗要放在“提交预约”按钮之前还是之后是个产品决策。放在之前转化率高但体验突兀放在之后审核通过时才发现没授权就发不了。我一般会放在提交成功后的结果页作为补发动作而不是必选动作。5. 管理员端的 SSM 实现审核、排期、统计三个接口的写法5.1 管理员的角色控制拦截器里做减法Service 里做校验管理员功能不建议单独做一个小程序同一个微信小程序里通过 role 字段做页面级控制就够了。后端在 AuthInterceptor 里已经存了 openid再来一个AdminInterceptor继承它在 preHandle 里额外查一次用户角色。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String openid (String) request.getAttribute(openid); User user userMapper.findByOpenid(openid); if (user null || user.getRole() ! 1) { response.setStatus(403); return false; } return true; }不要把角色判断写进每一个 Controller 的 if 里拦截器统一处理新加管理接口时只要注册 URL 规则就行不容易漏。URL 规则建议/admin/**全部拦截Controller 里的管理接口统一放在/admin路径下。5.2 时段生成用 date 和 schedule 两张表组合出可预约时段场馆的开放时间是固定的比如周一至周五 18:00-22:00周六日 09:00-21:00。生成可预约时段不需要手动一条条插入写一个定时任务每天凌晨生成未来 7 天的时段记录。Scheduled(cron 0 0 2 * * ?) public void generateSchedule() { Date today DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH); for (int i 1; i 7; i) { Date date DateUtils.addDays(today, i); if (isWeekend(date)) { venueScheduleMapper.insertBatch(date, weekendStart, weekendEnd, 60); } else { venueScheduleMapper.insertBatch(date, weekdayStart, weekdayEnd, 60); } } }时段粒度按 60 分钟切分这个值要能配置。如果场地类型不同羽毛球馆要 40 分钟的间隔篮球场要 120 分钟venue表里就要加一个slot_minutes字段而不是写死。定时任务用 Spring 的Scheduled在 spring-mvc.xml 里开task:annotation-driven/注意 SSM 项目中这个命名空间的缺失会导致Scheduled完全不执行没有任何报错排查时很难发现。5.3 预约统计的 SQL按天、按场地的使用率计算管理后台常常要一个“今日场地使用情况”的数据面板。不用引入额外的报表工具MyBatis 里写一条按场地和日期分组统计的 SQL 就够。SELECT v.venue_name, COUNT(r.id) AS total_orders, SUM(CASE WHEN r.status 1 THEN 1 ELSE 0 END) AS confirmed_orders, ROUND(SUM(CASE WHEN r.status 1 THEN 1 ELSE 0 END) / COUNT(r.id) * 100, 2) AS usage_rate FROM venue v LEFT JOIN reservation r ON v.id r.venue_id AND r.date #{date} GROUP BY v.id ORDER BY usage_rate DESCLEFT JOIN 保证了没有预约记录的场地也能出现在列表里usage_rate 为 0。ROUND(x, 2)保留两位小数前端展示时不需要再格式化。这条 SQL 的数据量级在高校场景里极小即使全表关联也没压力不用优化。真正要优化的是按月份统计预约趋势那时数据量上来了建议在reservation表上加date的索引单独查。6. 上线部署前必查清单与三个隐藏瓶颈小程序项目在开发者工具里跑通只代表逻辑正确真正上线前有十个点必须逐项过后端域名必须是 HTTPS 且备案小程序后台配置 request 合法域名SSL 证书别用免费的单域名证书腾讯云的免费证书一年一换到期后小程序直接请求失败而你看后端日志啥报错都没有wx.login的 code 只能用一次前端重复调用会报invalid code必要时在每次onShow重置登录态数据库连接池的maxActive要按 Tomcat 默认线程数 200 来配设成 50 的后果是高峰期连接池被占满Tomcat 线程池还在排队处理时长直接翻几倍MyBatis 的cache/二级缓存默认不开开了之后场地列表和预约列表会出现脏读因为reservation表频繁更新缓存失效策略很难精确控制定时任务的时区要统一用Asia/Shanghai服务器默认 UTC 的话凌晨 2 点生成的是北京时间 10 点的数据小程序端的地图组件如果要做位置打卡功能需要声明requiredPrivateInfos否则wx.getLocation返回隐私授权错误图片上传要压缩到 200KB 以下微信服务器对上行包体限制是 1MB学生上传篮球场实拍图很容易超限接口返回的null字段在小程序端setData时会报Cannot read property后端要统一Result包装类兜底空数组本地调试时 charles 抓包要安装证书真机预览时抓包则要在微信开发者工具里设置代理否则只看得到 TCP 层的 HTTP 请求看不到业务响应体。这三个隐藏瓶颈分别出现在场地列表首次加载慢、审核操作偶发超时、定时生成时段偶尔跳过一天。第一个瓶颈是场地列表接口在 PageHelper 分页时count 查询执行了一次全表解决方式是手写 count SQL第二个瓶颈是审核接口事务里查了 Redis 又查了 MySQL两次网络往返把 Redis 的 token 校验提前到 Controller 层第三个瓶颈是服务器休眠策略导致 JVM 时钟偏差Scheduled用的是系统时间而非数据库时间把生成时段的基准时间从new Date()改成查数据库的SELECT NOW()一次改动解决所有时间漂移问题。最后验证方法超过瘾拿两台手机真机同时抢同一个场地同一个时段的最后一个位置看是不是只有一个能提交成功另一个收到“该时段已被预约”的提示。这个测试通过你的事务和锁就算真正落地了。再测一遍把手机时间改到预约日期的前一天看小程序端会不会出现日期选择器可选的日期不包括今天。这几个测试跑完系统离上线就只差后续运维了。本文还有配套的精品资源点击获取