ARTICLE DETAIL

资讯详情

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

微信小程序+SSM框架会议预约系统设计与避坑实践

微信小程序+SSM框架会议预约系统设计与避坑实践 简介一套基于微信小程序的会议发布与预约系统设计与开发源码面向高校毕业设计、Java Web课程实践及SSM框架学习者覆盖会议发布、信息浏览、预约申请、后台审批与提醒通知等完整业务流程整合小程序端、Vue管理后台和SSM后端三层结构适合二次开发或作为论文支撑。压缩包共1139个文件约14.9MB以Java源码、Vue组件、JS逻辑、小程序WXML/WXSS页面为主另含SQL数据库脚本、XML/JSON配置、PNG/SVG图标以及部署运行脚本后端Controller-Service-Mapper分层明确便于按模块定位前后端代码。目前已有89人浏览学习。借助完整工程可快速还原系统运行环境理解Spring MVC与MyBatis的整合方式、小程序与后端的数据交互、预约状态流转及消息提醒实现对完成毕业设计、课程报告或项目实战均有直接参考价值。1. 搜到这份 .rar 的人到底在找什么搜基于微信小程序的会议发布与预约系统的设计与开发ssm框架.rar这个标题的多半是两类人一类带着毕设题目来要一套能跑通、好答辩的源码另一类是真要给部门或社团做一套能用的会议室预约工具。解压这份 .rar你面对的是典型的三段式交付物小程序前端、SSM 后端工程、数据库初始化脚本。它解决的业务闭环不复杂——管理员发布会议普通用户在小程序里看列表、提交预约管理员再在后端查看报名情况——但把微信登录、接口联调、预约冲突判断这些环节串起来正好覆盖了毕设评审最看重的几个点。SSM 不是新架构但资料多、好改、好讲正适合这个级别的项目。2. 小程序端跑通会议列表、登录态与预约动作2.1 微信开发者工具导入先认清目录结构再改代码在微信开发者工具里选择导入项目目录要指向小程序前端那一层。如果解压后看到app.js、app.json、pages/这些文件和目录说明这就是小程序工程如果看到一堆pom.xml、src/那是后端工程你指错目录了。小程序工程不能像网页那样双击用浏览器打开必须靠微信开发者工具加载这是绝大部分新手在第一步就会卡住的地方。导入后第一件事不是点编译而是改app.js里的接口地址。这是整个前后端联调的前置条件// app.js App({ globalData: { baseUrl: http://localhost:8080 // 先保证开发者工具内能通 } })这里说明一个关键认知开发者工具默认勾选了不校验合法域名所以本地开发时用http://localhost:8080能跑通。但真机预览时localhost指向的是手机自己而不是你的电脑后面第 4 章会专门处理这个坑。现在先记住一句话凡是请求失败先看baseUrl再看控制台报错别急着改业务代码。2.2 wx.login() 换 openid登录态闭环的三个步骤小程序端的登录不是传统意义上的账号密码而是微信静默授权。核心只有一个接口wx.login()拿到一次性 code再把 code 交给后端由后端用 appid 和 secret 去微信服务端换取 openid。注意 secret 绝对不能放在小程序代码里否则任何人反编译你的包都能冒充你的小程序。一个标准的登录片段长这样// 首次启动时静默登录 wx.login({ success: (res) { if (res.code) { wx.request({ url: ${app.globalData.baseUrl}/api/login, method: POST, data: { code: res.code }, success: (resp) { // 后端返回的自定义 token wx.setStorageSync(token, resp.data.data.token) } }) } } })三个要点第一code 有效期只有五分钟用完即焚前端不需要关心 openid 长什么样第二后端拿到 code 后调微信的 code2Session 接口拿到 openid 后自己生成一个 token 返回这个 token 才是后续所有请求的身份凭证第三wx.login 是静默的不需要用户点任何授权按钮。很多答辩老师会追问你的登录怎么实现的能把这个 code、openid、token 三者的流转关系讲清楚这一问基本就过了。2.3 会议列表渲染与加载更多一页十条的翻页逻辑会议列表页面是用户进来看到的第一个界面也是整个系统里数据交互最频繁的模块。常见做法是分页加载一页十条滚动到底部自动加载下一页对应小程序的onReachBottom生命周期。这个逻辑本身不复杂真正麻烦的是数据还没回来时用户又滑到底部导致重复请求。// pages/meeting/list.js loadMeetings(pageNum) { wx.request({ url: ${baseUrl}/api/meeting/list?page${pageNum}size10, header: { Authorization: wx.getStorageSync(token) }, success: (res) { const records res.data.data.records || [] // 防止返回 null this.setData({ meetings: this.data.meetings.concat(records), pageNum: pageNum 1, hasMore: records.length 10 // 满一页就假设还有更多 }) } }) }records.length 10这个判断是假设逻辑页数不齐时自然停止对毕设和内部系统够用。|| []是防御性写法后端如果返回nullconcat不会报错。再补一个细节这里的Authorization头是自定义 token 的常见传递方式后端拦截器从这个头里解析用户身份后面第 4 章的请求封装会统一处理。2.4 提交预约防重复点击与前端校验预约按钮是最高频的操作最典型的翻车现场是用户连点两次按钮小程序同时发出两个请求后台插了两条记录。解决方案是前端加 submitting 闸门同时后端用唯一索引兜底两道防线缺一不可。submit(e) { if (this.data.submitting) return const meetingId e.currentTarget.dataset.id this.setData({ submitting: true }) wx.request({ url: ${baseUrl}/api/appointment/create, method: POST, data: { meetingId }, success: (res) { if (res.data.code 0) { wx.showToast({ title: 预约成功, icon: success }) } else { wx.showToast({ title: res.data.msg, icon: none }) } }, complete: () this.setData({ submitting: false }) }) }complete回调里重置 submitting保证无论成功失败都能恢复点击。前端校验是体验层面的真正的幂等靠第 3 章要讲的数据库唯一键。这里说句题外话很多课程设计只做前端校验接口一抓包就能伪造请求重复提交答辩时被问住的不在少数。3. SSM 后端骨架会议模块的接口设计与数据落库3.1 为什么这个项目还选 SSM适用边界与替代方案SSM 是 Spring、SpringMVC、MyBatis 三个框架的组合放在今天看确实不算新Spring Boot 已经是默认选择。但像这种基于微信小程序 SSM的题目框架往往是指定的或者课程还在教 SSM 的 XML 配置方式。SSM 的实际优势是分层清晰Controller、Service、Mapper 三个包一拆答辩时每一步都能展开讲缺点是配置繁琐光 Spring 和 MyBatis 的 XML 加起来就几十行。我的建议是如果题目没锁框架用 Spring Boot 重写会省一大半配置时间如果题目指名叫 SSM也别慌照着下面把三层拆干净就行。这个项目里 SSM 端承担的是纯接口职责只返回 JSON不渲染页面所以 SpringMVC 的视图解析器基本用不上核心工作集中在接口设计和数据落库上。3.2 meeting 表与 appointment 表字段设计与状态机会议室预约系统的核心表只有两张会议表和预约表。设计得好不好直接决定后端代码的复杂度。先看建表 SQLCREATE TABLE meeting ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 会议标题, room VARCHAR(50) DEFAULT COMMENT 会议室, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, capacity INT DEFAULT 30 COMMENT 人数上限, publisher_id INT NOT NULL COMMENT 发布人id, status TINYINT DEFAULT 1 COMMENT 1未开始 2进行中 3已结束 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_start_time (start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE appointment ( id INT PRIMARY KEY AUTO_INCREMENT, meeting_id INT NOT NULL, user_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0有效 1已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_meeting_user (meeting_id, user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;两个表的设计亮点会议表的start_time建索引因为列表页和状态筛选都按时间排序预约表的联合唯一键uk_meeting_user是防重复预约的数据库级兜底这一行值得在答辩时单独强调。状态字段用 TINYINT 而不是字符串是为了后续扩展状态机——比如会议可以加已满员状态预约可以加已签到状态改一个数字比改字符串在数据库里省空间。3.3 Controller-Service-Mapper三层职责与分页查询写法SSM 的代码结构是固定的但很多人把业务逻辑全堆在 Controller 里这是后续维护最大的坑。一个规范的三层长这样RestController RequestMapping(/api/meeting) public class MeetingController { Autowired private MeetingService meetingService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { return Result.success(meetingService.page(page, size)); } }Controller 只做参数接收和结果包装真正的业务判断在 ServiceService public class MeetingServiceImpl implements MeetingService { Autowired private MeetingMapper meetingMapper; public PageResultMeeting page(Integer page, Integer size) { int offset (page - 1) * size; ListMeeting list meetingMapper.selectPage(offset, size); int total meetingMapper.countValid(); PageResultMeeting result new PageResult(); result.setRecords(list); result.setTotal(total); result.setHasMore(offset list.size() total); return result; } }对应的 MyBatis XML 里是这么写分页的select idselectPage resultTypecom.example.entity.Meeting SELECT * FROM meeting WHERE status 1 AND end_time NOW() ORDER BY start_time ASC LIMIT #{offset}, #{size} /select注意LIMIT #{offset}, #{size}是 MySQL 的分页语法offset 在 Service 层算好再传进去。这里的status 1 AND end_time NOW()是未开始且未结束的筛选条件保证用户看不到已经开完的会议。如果一个会议是跨天的NOW()是服务器时间和小程序端的本地时间可能有几分钟偏差这个细节后面避坑章节再说。3.4 预约冲突校验先查后插还是数据库锁预约的核心是防止两种冲突人数满了还让人约同一个人重复约同一场。处理逻辑写在哪一层直接决定系统的健壮程度。常见做法是先查后插 唯一索引兜底代码长这样public boolean createAppointment(AppointmentDTO dto) { Meeting meeting meetingMapper.selectById(dto.getMeetingId()); if (meeting null || meeting.getStatus() ! 1) { throw new BusinessException(会议不存在或已取消); } int count appointmentMapper.countValid(dto.getMeetingId()); if (count meeting.getCapacity()) { throw new BusinessException(预约人数已满); } appointmentMapper.insert(dto); return true; }countValid是查预约表里 status 0 的记录数小于 capacity 才允许插入。这个逻辑在单机低并发场景下完全够用但如果真的多台服务器同时处理count 和 insert 之间存在时间窗口可能超卖。要彻底解决数据库层面可以对 meeting 表加悲观锁或者用分布式锁但对一个会议室预约系统来说严重超卖的可能性极低。我认为把注释写清楚此处并发不严格安全如需强一致可加行锁比硬上一套分布式锁更诚实。4. 前后端联调把小程序连上本地后端4.1 从 localhost 到局域网 IP后端启动与真机预览联调的第一步是让后端跑起来。用 IDEA 打开 Maven 工程等依赖下载完配置 Tomcat端口保持 8080。先别急着点运行先检查项目里的 JDK 版本和本地 JDK 对不对得上——SSM 老项目最常见的就是 JDK 版本不匹配启动直接报 UnsupportedClassVersionError。把 Tomcat 配好后启动浏览器访问http://localhost:8080/api/meeting/list能返回 JSON 就说明后端通了。但小程序真机预览时手机上的localhost是手机自己不是你的电脑。所以要把baseUrl改成电脑的局域网 IP# Windows 查本机 IP ipconfig # macOS 查本机 IP ifconfig | grep inet改完baseUrl后还有一个大坑电脑的防火墙会拦掉手机发来的 8080 端口请求。Windows 上要手动放行 8080 端口的入站规则macOS 一般不用处理。判断方法是手机浏览器直接访问http://192.168.x.x:8080/api/meeting/list能出 JSON说明网络通出不来先查防火墙和同一网段。4.2 合法域名与 HTTPS开发模式下的取舍微信开发者工具右上角详情 → 本地设置 → 不校验合法域名这个开关是开发阶段能跑同http://请求的前提。但真机预览时如果这个开关没勾所有请求都会直接 fail报不在以下 request 合法域名列表中。这里要区分两件事开发模式可以关校验体验版和正式版必须配合法域名。正式环境的规则是request 合法域名必须是备案过的域名并且强制 HTTPS。这就意味着后端不能直接用 IP 加端口的方式上线必须走 Nginx 反向代理把https://yourdomain.com/api转发到localhost:8080。很多毕设项目走到这一步才发现自己还没买域名、没备案硬生生被卡住。我的建议是如果不上线、只在本地演示这个章节的配置完全可以跳过如果计划发布正式版域名备案要提前至少两周准备。4.3 wx.request 封装与 401 拦截会话过期的处理每个页面都写一遍wx.request的 url 和 header 很烦而且 token 过期的时候每个页面都要单独处理。一个简单的封装能解决大部分重复代码// utils/request.js const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: ${baseUrl}${url}, method, data, header: { Authorization: wx.getStorageSync(token) }, success: (res) { if (res.statusCode 401) { // token 失效重新登录 wx.removeStorageSync(token) wx.navigateTo({ url: /pages/index/index }) reject(res) } else { resolve(res.data) } }, fail: reject }) }) }SSM 老项目里后端用的往往是 session 而不是 token。小程序端没有 cookie 的概念所以更常见的变通方案是后端加一个拦截器从Authorization头里解析用户 id再放进ThreadLocal或直接传给 Service 层。这个拦截器是整个联调环节最容易出错的地方头名字写错、拦截器路径配错、token 过期没返回 401任何一个问题都会导致前端所有请求看起来没反应。排查顺序我一般这样走先看 Network 面板请求有没有发出去再看返回状态码最后看后端控制台有没有异常堆栈。另外联调阶段如果接口返回了 JSON 但前端读不到字段大概率是字段命名问题。SSM 项目里数据库字段常是下划线风格user_id而 Java 属性是驼峰userIdMyBatis 如果没有开启驼峰映射返回的 JSON 就是user_id前端用userId去读就是 undefined。遇到这种情况检查mybatis-config.xml里的mapUnderscoreToCamelCase是否设为 true。5. 避坑小程序会议系统最常见的 5 个翻车点5.1 现象 1开发者工具正常真机预览接口全部超时这是联调阶段最打击人的一个问题开发者工具里页面正常加载一切看起来完美点真机预览所有接口请求全部 fail。原因就两层一是baseUrl写的是localhost手机访问的是自己二是手机和电脑不在同一个局域网或者电脑防火墙拦了端口。解决路径很明确先查 IP 连通性手机浏览器直接访问接口地址能出 JSON 就是网络通不通就检查同一 Wi-Fi、防火墙入站规则、端口占用这三个位置。别在业务代码里找原因这个问题的根源 90% 在网络层剩下的 10% 是http://被微信拦截——开发工具勾上不校验合法域名可解。5.2 现象 2iOS 上会议时间显示 NaNAndroid 手机上时间显示正常iPhone 一打开就显示NaN/NaN/NaN。原因很经典iOS 的 JavaScript 引擎解析不了2024-01-01 10:00:00这种带横杠的日期字符串必须转成2024/01/01 10:00:00。后端返回的start_time是 MySQL 的 DATETIME 格式刚好就是带横杠的。解决方式是在前端格式化方法里加一行替换formatTime(str) { if (!str) return // iOS 不认识 2024-01-01 10:00:00替换成斜杠 const safeStr str.replace(/-/g, /) const date new Date(safeStr) return ${date.getMonth() 1}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes()} }这个问题在微信小程序里属于玄学级别安卓和开发者工具都不报错只有真机 iOS 翻车。如果时间在 Android 正常、iOS 异常基本可以锁定是日期格式解析问题。5.3 现象 3订阅消息授权弹框只出现一次预约成功后想给用户发一条会议即将开始的订阅消息调用wx.requestSubscribeMessage时发现弹框只在第一次出现之后不管怎么调用都不弹了。这不是 bug是微信的规则订阅消息是一次性的用户授权一次你只能发送一条消息用户点了拒绝后你需要引导他去设置页重新打开。常见做法是预约成功后弹出订阅框请求授权用户同意就发一条如果用户拒绝下次进入预约页时给个提示按钮引导开启。注意弹框必须由用户主动触发点击按钮的回调里调用不能在小程序启动时直接弹否则会被微信拦截。做一个预约成功 → 弹订阅框 → 用户确认 → 会议开始前推送的链路需要后端记录每个用户对每个会议授权了哪类模板消息。5.4 现象 4自定义导航栏后内容顶到屏幕顶部有些项目为了自定义顶部标题把app.json里的navigationStyle设成了custom结果页面内容直接顶到状态栏下面和胶囊按钮、时间信号重叠。原因是自定义导航后小程序不再自动避让安全区和状态栏所有内容都从屏幕最顶部开始计算。解决方式有两个一是不要自定义导航保留默认导航栏这是最省事的方案二是如果必须自定义在页面里显式获取状态栏高度做 paddingconst info wx.getWindowInfo() const statusBarHeight info.statusBarHeight // 单位 px // 页面 onLoad 里 setData 给 padding-top 使用 this.setData({ statusBarHeight })wx.getWindowInfo()是新版推荐接口老项目里用wx.getSystemInfoSync()也能拿但新版基础库建议迁移。自定义导航还涉及胶囊按钮位置的适配公式是导航栏高度 胶囊按钮顶部 - 状态栏高度 胶囊按钮高度 上下留白。这个计算在 iPhone 刘海屏和非刘海屏上结果不同没有一劳永逸的固定值。5.5 现象 5按会议开始时间排序乱掉列表页明明ORDER BY start_time ASC但有的会议排到了错误位置或者新加的会议显示在旧日期前面。排查后发现很多课程设计的建表脚本里start_time用的是varchar而不是DATETIME字符串排序会逐字符比较2024-09-01 09:00和2024-09-01 10:00碰巧排对但一旦格式不统一有的带秒、有的不带、有的混入中文排序结果就乱了。解决方法是建表时用DATETIME类型。如果表结构已经定死改不了排序时用STR_TO_DATE转一下ORDER BY STR_TO_DATE(start_time, %Y-%m-%d %H:%i) ASC这个坑的隐蔽之处在于数据量小的时候完全看不出问题只有会议多了才暴露。顺带提醒如果end_time也是varchar判断会议是否结束时也要同步转换否则end_time NOW()这个条件在字符串比较下会有严重误导。6. 从能跑到能用验证清单与上线路径6.1 验收测试用例按用户旅程走一遍功能全部写完不代表系统能用我习惯按用户旅程过一遍测试用例测试项操作预期结果会议列表加载进入小程序首页显示未开始的会议按开始时间升序下拉刷新首页下拉列表重新加载分页页码归零预约成功点某个会议的预约按钮提示预约成功会议详情显示人数 1重复预约再次点同一个会议提示已预约过不插入第二条记录满员拒绝预约人数已满的会议提示预约人数已满会议开始后进入已开始的会议详情预约按钮置灰或隐藏自定义导航适配在 iPhone 刘海屏上预览标题不遮挡内容不顶到状态栏这套用例全部过一遍基本能覆盖前面几章提到的 90% 的坑。如果时间紧至少要把重复预约和满员拒绝这两条测到位它们是答辩时最容易被追问的点。6.2 体验版到正式版域名、HTTPS 与认证费用演示环节用开发者工具就够了要发给同学、老师试用需要上传为体验版开发者工具点上传填版本号和备注然后在微信公众平台添加体验成员对方扫码就能在小程序里直接打开——这就是发给别人收集试用反馈的标准路径不需要正式发布。要正式上线路径就长了后端域名备案、配置 HTTPS 证书、Nginx 反向代理到 SSM 服务最后在小程序后台配 request 合法域名。微信公众平台对小程序的认证是按年收费的个人主体和企业主体的费用有差异具体金额以平台当前公示为准但要有这个预算意识。我做这类项目吃过一次亏功能全部跑通却把域名备案拖到最后一周差点误了验收。体验版能解燃眉之急正式上线绕不开域名这道手续。希望帮到你。本文还有配套的精品资源点击获取
返回列表