ARTICLE DETAIL

资讯详情

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

微信小程序+Spring Boot充电桩预订系统实战

微信小程序+Spring Boot充电桩预订系统实战 简介这是一套面向计算机专业本科生的微信小程序毕业设计实战资源聚焦高速公路服务区充电桩在线预订场景完整覆盖游客浏览、会员预订与管理员后台三大角色功能适用于课程设计、毕设选题及小程序全栈开发能力训练。资源包共1131个文件25.72MB含124个Vue页面组件、123个JS逻辑脚本、96个Java后端类与96个Class编译文件辅以SVG图标、PNG地图素材、JSON配置及SQL建表脚本结构清晰体现前后端分离架构另有3个bat批处理脚本安装/运行/构建和1个MP4演示视频便于快速部署与效果验证。目前已有357人学习下载提供从需求分析、界面实现、订单流程到后台管理的全流程源码与说明文档特别适合掌握微信小程序JavaMySQL技术栈的初学者系统性复现与二次开发。1. 这不是又一个“仿美团”小程序它用原生微信框架Java后端把高速服务区充电桩预订做成了可上线的闭环系统你见过多少毕业设计小程序点开首页就卡在「获取用户位置」、登录页报wx.login:fail、订单提交后数据库里查不到记录这个「高速公路服务区充电桩在线预订系统」不是模板拼凑——它跑在真实微信开发者工具 v1.07.2312190 环境下后端用 Spring Boot 2.3.12 MyBatis-Plus MySQL 5.7前端 Vue.js 2.6.14非 uni-app所有.vue.bak文件是开发过程中的备份痕迹3-build.bat能一键编译生成dist目录2-run.bat启动本地 mock 服务验证路由跳转1-install.bat自动安装依赖并初始化数据库表结构。它解决的是真实场景里的三个硬骨头服务区地理信息动态加载非静态 JSON、会员审核流与订单状态机强耦合、管理员后台权限粒度控制到「公告编辑」和「订单导出」两级。适合计算机/软件工程专业本科生做毕设答辩——代码有业务逻辑深度比如CommonController.class里封装了充电桩空闲时段校验算法文档覆盖部署全流程含 MySQL 字符集utf8mb4配置警告演示视频里能看到微信原生顶部导航栏高度适配statusBarHeightnavigationBarHeight双值计算、手机号一键授权登录getPhoneNumberAPI 调用链路完整、订单支付回调模拟/api/order/callback接口返回{code:200,msg:支付成功}。别被「毕业设计」四个字骗了——它比很多商用轻量级 SaaS 的预订模块更扎实。2. 前端工程结构拆解从.vue.bak文件看真实开发迭代痕迹与组件职责划分2.1 核心页面组件命名逻辑与复用边界项目中保留的.bak文件不是冗余垃圾而是关键开发证据IndexMain.vue.bak是首页主容器原始版本用wx:for渲染服务区列表但因性能问题被替换为scroll-view 虚拟滚动方案新版IndexMain.vue中>// utils/nav-height.js export function getNavHeight() { const systemInfo wx.getSystemInfoSync(); const statusBarHeight systemInfo.statusBarHeight || 20; // 注意微信基础库 2.7.0 才支持 getMenuButtonBoundingClientRect() try { const menuButton wx.getMenuButtonBoundingClientRect(); return menuButton.height (menuButton.top - statusBarHeight) * 2; } catch (e) { // 降级方案iOS 默认 44pxAndroid 默认 48px return systemInfo.platform ios ? 44 : 48; } }在app.js的onLaunch中调用App({ onLaunch() { this.globalData.navHeight getNavHeight(); // 存入全局 } })然后在IndexHeader.vue中template view classheader :style{ height: app.globalData.navHeight px } view classnav-bar :style{ paddingTop: app.globalData.statusBarHeight px } !-- 导航内容 -- /view /view /template参数说明statusBarHeight是状态栏高度通常 20~44pxgetMenuButtonBoundingClientRect()返回胶囊按钮的top和height导航栏总高 胶囊按钮高度 2 × (胶囊按钮上边距 - 状态栏高度)。这是解决「顶部导航栏遮挡内容」的唯一可靠方案硬写44px在 iPhone 14 Pro Max 上会留白 12px。2.3 微信小程序登录获取手机号从授权到绑定的完整链路游客注册流程依赖getPhoneNumber但必须绕过两个坑按钮必须用button open-typegetPhoneNumber不能用wx.login()wx.getUserInfo()组合微信已废弃getUserInfo后端需验证codeencryptedDataiv本项目CommonController.class的bindPhone方法做了三重校验解密前校验encryptedData长度是否 ≥ 128解密后检查phoneNumber字段是否存在且符合^1[3-9]\d{9}$检查该手机号是否已被其他账号绑定防止刷号。前端调用示例template button open-typegetPhoneNumber getphonenumberonGetPhoneNumber classphone-btn 一键绑定手机号 /button /template script export default { methods: { onGetPhoneNumber(e) { if (e.detail.code) { // 将 code 发给后端换取 session_key wx.request({ url: /api/user/bind-phone, method: POST, data: { code: e.detail.code, encryptedData: e.detail.encryptedData, iv: e.detail.iv } }) } else { wx.showToast({ title: 授权失败, icon: none }); } } } } /script关键点e.detail.code是临时登录凭证有效期 5 分钟必须立即传给后端encryptedData和iv是加密数据后端用session_key解密才能拿到真实手机号。3. 后端 Java 模块设计MyBatis-Plus 实体类与订单状态机实现3.1 数据库表结构与 MyBatis-Plus 实体映射MySQL 表共 7 张核心三张表名主要字段说明t_service_areaid,name,province,city,lng,lat,charger_count服务区基础信息lng/lat用于地图标记t_orderid,user_id,area_id,book_time,status,pay_time订单主表status用 tinyint(1) 存储状态码t_chargerid,area_id,type,status,last_used_time充电桩明细type区分快充/慢充status0空闲 1占用 2故障对应Order.java实体类Data TableName(t_order) public class Order { TableId(type IdType.ASSIGN_ID) private Long id; private Long userId; private Long areaId; TableField(book_time) private LocalDateTime bookTime; // 预订时间 TableField(status) private Integer status; // 0待支付 1已支付 2已使用 3已取消 4超时失效 TableField(pay_time) private LocalDateTime payTime; // 支付完成时间 // 关联查询字段非数据库列 TableField(exist false) private String areaName; TableField(exist false) private String userName; }参数说明TableField(book_time)显式指定数据库字段名避免驼峰转换错误exist false标记非持久化字段用于联表查询结果封装。3.2 订单状态机用枚举 Service 层校验防脏写状态流转不是简单UPDATE SET status1而是强约束// OrderStatusEnum.java public enum OrderStatusEnum { WAIT_PAY(0, 待支付), PAID(1, 已支付), USED(2, 已使用), CANCELLED(3, 已取消), EXPIRED(4, 超时失效); private final int code; private final String desc; OrderStatusEnum(int code, String desc) { this.code code; this.desc desc; } // 校验状态是否允许变更 public static boolean canTransition(int from, int to) { switch (from) { case 0: return to 1 || to 3 || to 4; // 待支付 → 已支付/已取消/超时失效 case 1: return to 2 || to 3; // 已支付 → 已使用/已取消 default: return false; } } }OrderService.java中的更新方法Transactional public boolean updateStatus(Long orderId, Integer newStatus, String operator) { Order oldOrder orderMapper.selectById(orderId); if (!OrderStatusEnum.canTransition(oldOrder.getStatus(), newStatus)) { throw new BusinessException(状态非法变更 oldOrder.getStatus() → newStatus); } // 更新前检查业务规则已支付订单不可直接取消需先退款 if (oldOrder.getStatus() 1 newStatus 3) { if (!refundService.refund(orderId)) { throw new BusinessException(退款失败无法取消订单); } } Order update new Order(); update.setId(orderId); update.setStatus(newStatus); if (newStatus 1) update.setPayTime(LocalDateTime.now()); return orderMapper.updateById(update) 0; }逻辑说明状态机校验放在 Service 层而非 Controller避免前端绕过校验直接调用接口refundService.refund()是独立服务确保「取消订单」必走退款流程。3.3 管理员权限控制基于角色的 URL 级拦截Spring Security 配置WebSecurityConfig.javaOverride protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/api/admin/**).hasRole(ADMIN) // 所有 /api/admin/ 开头接口需 ADMIN 角色 .antMatchers(/api/user/**).authenticated() // 普通用户接口需登录 .antMatchers(/api/**).permitAll() // 公共接口放行 .and() .csrf().disable(); }管理员登录后JWT Token 中role字段为ADMINSecurityContext自动注入。t_user表中role字段存admin/member/guestUserDetailsServiceImpl加载用户时Override public UserDetails loadUserByUsername(String username) { User user userMapper.selectOne(new QueryWrapperUser().eq(username, username)); if (user null) throw new UsernameNotFoundException(用户不存在); ListGrantedAuthority authorities new ArrayList(); authorities.add(new SimpleGrantedAuthority(ROLE_ user.getRole().toUpperCase())); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), authorities ); }注意ROLE_ADMIN是 Spring Security 的标准前缀数据库存admin代码中自动转为ROLE_ADMIN。4. 部署与运行从1-install.bat到3-build.bat的全链路实操4.11-install.batWindows 下一键初始化环境该批处理文件做了三件事检查 JDK 8 是否安装java -version | findstr 1.8执行mvn clean install -Dmaven.test.skiptrue编译后端运行mysql -u root -p db/init.sql初始化数据库init.sql包含建库、建表、插入默认管理员账号admin/123456。关键参数-Dmaven.test.skiptrue跳过单元测试避免TestOrderService因未配置 H2 内存库而失败init.sql中CREATE DATABASE IF NOT EXISTS charge_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;显式指定字符集防止中文乱码。4.22-run.bat启动后端服务并验证接口连通性echo off echo 正在启动后端服务... start /min java -jar target/charge-server-0.0.1-SNAPSHOT.jar --spring.profiles.activedev timeout /t 5 nul echo 测试接口连通性... curl -s http://localhost:8080/api/test | findstr success if %errorlevel% equ 0 ( echo 后端服务启动成功 pause ) else ( echo 后端服务启动失败请检查端口 8080 是否被占用 pause )逻辑说明start /min后台启动 Java 进程curl测试/api/test接口返回{code:200,msg:success}findstr匹配字符串判断成功timeout /t 5等待服务初始化避免 curl 过早执行。4.33-build.bat微信开发者工具构建发布包echo off echo 正在构建小程序生产包... cd /d %~dp0src :: 删除旧 dist if exist dist rmdir /s /q dist :: 复制源码到 dist xcopy /e /i /y pages dist\pages xcopy /e /i /y components dist\components copy app.js dist\ copy app.json dist\ copy project.config.json dist\ :: 替换开发环境变量 powershell -Command (gc dist\app.js) -replace http://localhost:8080, https://api.charge-system.com | Out-File dist\app.js -encoding utf8 echo 构建完成请将 dist 目录导入微信开发者工具 pause参数说明xcopy /e /i /y递归复制所有子目录、创建目标目录、不提示覆盖powershell替换 API 域名将localhost:8080换成线上域名避免上线后请求失败project.config.json中appid字段需手动填入你的小程序 AppID。5. 避坑指南五个血泪经验总结每一条都踩过真坑5.1 现象小程序首页地图 marker 点击无反应原因map组件的markers数组中id字段类型错误。本项目t_service_area表id是BIGINT但前端markers的id写成字符串如1微信地图 API 要求id必须是数字类型。解决后端ServiceAreaController.java返回 JSON 时用Long类型序列化id禁用Jackson的WRITE_NUMBERS_AS_STRINGSConfiguration public class JacksonConfig { Bean Primary public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); mapper.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, false); return mapper; } }5.2 现象管理员登录后进入/admin/index页面空白原因IndexAsideStatic.vue中wx.getStorageSync(token)返回null但未做空值判断导致axios请求头Authorization: Bearer null后端 JWT 过滤器抛出IllegalArgumentException。解决在mounted钩子中增加校验mounted() { const token wx.getStorageSync(token); if (!token) { wx.navigateTo({ url: /pages/login/login }); return; } this.loadMenu(); }5.3 现象订单支付成功后t_order.status仍为 0待支付原因CommonController.class的支付回调接口/api/order/callback未开启事务orderService.updateStatus()抛异常后数据库回滚但微信侧已认为支付成功。解决在回调方法上加Transactional并捕获所有异常记录日志PostMapping(/callback) Transactional public Result callback(RequestBody MapString, Object params) { try { String outTradeNo (String) params.get(out_trade_no); orderService.updateStatusByOutTradeNo(outTradeNo, 1); return Result.success(); } catch (Exception e) { log.error(支付回调失败订单号{}, params.get(out_trade_no), e); return Result.fail(处理失败); } }5.4 现象2-run.bat启动后端报错Failed to bind properties under spring.datasource原因application-dev.yml中spring.datasource.url的 MySQL 连接字符串缺少useSSLfalseserverTimezoneGMT%2B8参数在 MySQL 5.7 默认开启 SSL 且严格时区校验。解决修改连接字符串为spring: datasource: url: jdbc:mysql://localhost:3306/charge_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneGMT%2B85.5 现象演示视频中「提交订单」按钮点击后无响应控制台报Cannot read property bookTime of undefined原因OrderForm.vue中form.bookTime初始值为null但picker组件绑定value时未设置默认值导致form对象未完全初始化。解决在data()中显式初始化data() { return { form: { areaId: , bookTime: , // 改为 而非 null chargerType: fast } } },6. 进阶技巧用wx.getSystemInfoSync().SDKVersion做 API 兼容性降级微信基础库版本碎片化严重比如getPhoneNumber在 2.4.0 才支持getMenuButtonBoundingClientRect()在 2.7.0 才可用。硬编码版本判断易出错本项目采用运行时检测6.1 创建兼容性工具函数// utils/api-compat.js export function hasPhoneNumberAPI() { const version wx.getSystemInfoSync().SDKVersion; return compareVersion(version, 2.4.0) 0; } export function hasMenuButtonAPI() { const version wx.getSystemInfoSync().SDKVersion; return compareVersion(version, 2.7.0) 0; } // 版本比较工具 function compareVersion(v1, v2) { const v1Arr v1.split(.).map(Number); const v2Arr v2.split(.).map(Number); for (let i 0; i Math.max(v1Arr.length, v2Arr.length); i) { const num1 v1Arr[i] || 0; const num2 v2Arr[i] || 0; if (num1 num2) return 1; if (num1 num2) return -1; } return 0; }6.2 在页面中按需调用template view classlogin-section button v-ifhasPhoneNumberAPI() open-typegetPhoneNumber getphonenumberonGetPhoneNumber 一键登录 /button button v-else clickonManualLogin 账号密码登录 /button /view /template script import { hasPhoneNumberAPI } from /utils/api-compat.js; export default { data() { return { hasPhoneNumberAPI: hasPhoneNumberAPI() } } } /script6.3 后端接口版本路由分流Spring Boot 中用RequestMapping的headers属性区分RestController RequestMapping(/api) public class OrderController { // 基础版接口所有版本兼容 PostMapping(/order/create) public Result createOrder(RequestBody OrderDTO dto) { return orderService.create(dto); } // 新版接口仅 SDKVersion 2.8.0 调用 PostMapping(value /order/create-v2, headers X-Wechat-SDKVersion2.8.0) public Result createOrderV2(RequestBody OrderV2DTO dto) { // 支持新字段预约时长、充电桩偏好 return orderService.createV2(dto); } }前端调用时添加 Headerwx.request({ url: /api/order/create-v2, method: POST, header: { X-Wechat-SDKVersion: wx.getSystemInfoSync().SDKVersion }, data: formData })参数说明headers中的X-Wechat-SDKVersion是自定义 HeaderSpring MVC 的RequestMapping(headers ...)会自动匹配compareVersion函数处理2.10.0和2.7.0的正确比较避免字符串比较2.10.0 2.7.0为 false。从那以后我每次写微信小程序 API 调用都强制走一遍hasXXXAPI()检测哪怕文档说「基础库 2.0.0 支持」也得亲眼看到wx.canIUse(getPhoneNumber)返回true才敢写逻辑——因为真实用户手机里装的可能是 2.3.2 的微信而测试机永远是最新的。希望帮到你。本文还有配套的精品资源点击获取
返回列表