尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java+微信小程序停车场系统:核心表、接口与避坑指南

Java+微信小程序停车场系统:核心表、接口与避坑指南 简介这份资源是面向高校计算机相关专业学生与Java初学者的一套停车场微信小程序毕业设计完整源码采用Java后端与微信小程序前端组合配合MySQL数据库适合作为毕业设计、课程设计或实训项目的参考方案。项目划分管理员、商家与用户三类角色覆盖车主管理、停车场信息维护、预约与取消预约、进场停车、商场收费、留言板及系统管理等模块业务链路较为完整。压缩包共1242个文件约15.84MB包含119个Java源文件、175个JavaScript脚本、134个Vue组件、86个WXML模板与88个WXSS样式另有SQL数据库脚本、JSON配置及PNG、SVG等界面素材前后端与数据库文件齐备。资源同时给出JDK1.8、MySQL5.7、Maven3.3、Tomcat7及微信开发者工具等环境说明便于读者快速还原运行环境。目前已有50人学习适合需要完整赛题方案、模块划分参考与排错思路的读者借鉴。1. 停车场微信小程序一套能跑通的毕设工程到底长什么样很多同学搜「停车场微信小程序的设计与实现源码」真正卡住的不是不会写代码而是不知道一套能过答辩、能演示、能讲清楚数据流的工程应该长什么样。我见过太多翻车现场小程序端页面做得挺漂亮点「预约车位」直接报 500因为后端根本没连上 MySQL或者数据库里就一张 user 表答辩老师问「车位状态怎么流转」当场哑火。这套基于 Java 微信小程序 MySQL 的停车场系统核心要解决的是「车主找位—预约—入场—计费—出场」这条闭环适合正在做微信小程序毕业设计、想拿一套结构完整工程改吧改吧交差的同学。下面我按真实落地顺序把表结构、接口、小程序页面和联调排错一次讲透你照着能复现也能看出哪些地方该自己改。2. 先定数据模型停车场系统的 5 张核心表和状态流转动手写代码之前表结构定错后面全是返工。停车场系统看着简单本质是一个「资源占用 时间计费」模型核心就三件事车位是有限的、车位有状态、停车有时间段。我一般会先把这 5 张表定下来再写接口不然写到一半发现少字段改起来血泪经验。2.1 五张核心表的设计与字段说明表名作用关键字段user车主账号id、openid、nickname、phone、balanceparking_lot停车场id、name、total_spots、free_spots、price_per_hourparking_spot具体车位id、lot_id、spot_no、status0空闲/1占用/2预约reservation预约记录id、user_id、spot_id、start_time、statusorder停车订单id、user_id、spot_id、entry_time、exit_time、fee、pay_status这里最容易踩的坑是parking_spot.status和parking_lot.free_spots两个字段不同步。常见做法是车位状态变更时用同一个事务去更新parking_lot.free_spots别指望定时任务去算实时性差还容易对不上。2.2 车位状态流转从空闲到释放的完整链路状态机是这个系统的灵魂答辩老师最爱问。车位只有三种状态但流转路径要闭环空闲(0) --用户预约-- 预约(2) --入场扫码-- 占用(1) --出场结算-- 空闲(0) 空闲(0) --直接入场-- 占用(1) --出场结算-- 空闲(0) 预约(2) --超时未入场-- 空闲(0)预约超时释放这条分支很多人不做结果车位被预约后一直锁死演示时点几次就没车位了。我一般会加一个定时任务每 5 分钟扫一次reservation表把start_time超过 30 分钟且状态还是「预约中」的记录置为「已取消」同时把车位释放回空闲。2.3 建表 SQL 与索引要点CREATE TABLE parking_spot ( id INT PRIMARY KEY AUTO_INCREMENT, lot_id INT NOT NULL COMMENT 所属停车场, spot_no VARCHAR(20) NOT NULL COMMENT 车位编号 A-001, status TINYINT DEFAULT 0 COMMENT 0空闲 1占用 2预约, KEY idx_lot_status (lot_id, status) -- 按停车场状态查空闲车位 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, spot_id INT NOT NULL, entry_time DATETIME NOT NULL, exit_time DATETIME DEFAULT NULL, fee DECIMAL(10,2) DEFAULT 0, pay_status TINYINT DEFAULT 0 COMMENT 0未支付 1已支付, KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;idx_lot_status这个联合索引是必须的小程序首页要频繁查「某停车场还有哪些空闲车位」没索引数据量一上来就慢。order表按user_id建索引因为「我的订单」列表是高频查询。注意金额用DECIMAL不用FLOAT浮点算钱迟早出精度问题这是常识但每年都有人栽。3. Java 后端预约、计费、释放三个接口怎么写后端是整个系统的黑匣子小程序端只是壳。用 Spring Boot 起服务Controller 层薄、Service 层放业务逻辑这是最稳的分层。下面挑三个最核心的接口讲其余增删改查照着套就行。3.1 预约车位接口并发下怎么防止一车位两卖预约接口最大的风险是并发两个用户同时点同一个车位都查出来是空闲都去更新结果一车位两卖。解决方式有两种简单的是加悲观锁SELECT ... FOR UPDATE性能好一点的是用乐观锁版本号。毕设场景我一般用悲观锁够用且好讲。Transactional public Result reserveSpot(Integer userId, Integer spotId) { // 1. 加行锁查车位防止并发抢占 ParkingSpot spot spotMapper.selectForUpdate(spotId); if (spot null || spot.getStatus() ! 0) { return Result.fail(车位已被占用或预约); } // 2. 改车位状态为预约 spot.setStatus(2); spotMapper.updateById(spot); // 3. 写预约记录 Reservation r new Reservation(); r.setUserId(userId); r.setSpotId(spotId); r.setStartTime(new Date()); r.setStatus(0); // 预约中 reservationMapper.insert(r); // 4. 同步停车场空闲数 lotMapper.decreaseFreeSpots(spot.getLotId()); return Result.success(r); }selectForUpdate对应的 SQL 是SELECT * FROM parking_spot WHERE id ? FOR UPDATE它会在事务提交前锁住这一行第二个请求进来会阻塞等待等第一个提交后发现状态已变直接返回失败。Transactional注解不能少否则锁在方法结束前就释放了等于没锁。decreaseFreeSpots用UPDATE parking_lot SET free_spots free_spots - 1 WHERE id ?别用「先查再减再存」那样并发下会丢更新。3.2 计费逻辑按小时还是按分钟边界怎么算计费规则答辩必问。常见做法是按小时计费不足一小时按一小时算或者按分钟精确计费。我一般写成可配置的price_per_hour存在停车场表里计费时算时间差。public BigDecimal calcFee(Date entry, Date exit, BigDecimal pricePerHour) { long minutes (exit.getTime() - entry.getTime()) / (1000 * 60); if (minutes 0) return BigDecimal.ZERO; // 不足1小时按1小时向上取整 long hours (minutes 59) / 60; return pricePerHour.multiply(BigDecimal.valueOf(hours)); }(minutes 59) / 60是向上取整的经典写法比Math.ceil少一次类型转换。这里有个边界坑如果用户入场和出场时间相同minutes为 0直接返回 0 元别让它算出负数。另外免费时长比如前 15 分钟免费如果要做就在这基础上减逻辑别写散在各处统一放这个方法里。3.3 出场结算与车位释放的事务处理出场是「改订单 改车位 改停车场空闲数」三件事必须在一个事务里否则中途失败数据就乱了。Transactional public Result checkout(Integer orderId) { Order order orderMapper.selectById(orderId); if (order null || order.getExitTime() ! null) { return Result.fail(订单不存在或已结算); } ParkingSpot spot spotMapper.selectById(order.getSpotId()); ParkingLot lot lotMapper.selectById(spot.getLotId()); // 1. 算费用、写出场时间 order.setExitTime(new Date()); order.setFee(calcFee(order.getEntryTime(), order.getExitTime(), lot.getPricePerHour())); orderMapper.updateById(order); // 2. 释放车位 spot.setStatus(0); spotMapper.updateById(spot); // 3. 停车场空闲数 1 lotMapper.increaseFreeSpots(lot.getId()); return Result.success(order); }三个更新操作任意一个抛异常Transactional会整体回滚不会出现「车位释放了但订单没结算」这种脏数据。注意increaseFreeSpots的 SQL 要加free_spots total_spots条件防止空闲数超过总车位数的玄学 bug。4. 微信小程序端首页车位列表、预约、订单三个页面小程序端是给答辩老师看的第一印象页面不用花哨但流程要顺。用原生小程序开发就行不用上 uniapp毕设场景原生更简单、报错更少。三个核心页面首页车位列表、车位详情预约、我的订单。4.1 首页车位列表与分页加载首页要展示停车场信息和空闲车位列表数据量大时要分页。小程序列表加载更多是高频需求用onReachBottom触底加载。Page({ data: { spots: [], page: 1, hasMore: true }, onLoad() { this.loadSpots(); }, loadSpots() { if (!this.data.hasMore) return; wx.request({ url: http://localhost:8080/api/spot/list, data: { lotId: 1, page: this.data.page, size: 10 }, success: (res) { const list res.data.data.list; this.setData({ spots: this.data.spots.concat(list), page: this.data.page 1, hasMore: list.length 10 // 返回不足10条说明到底了 }); } }); }, onReachBottom() { this.loadSpots(); } });hasMore的判断逻辑是「本页返回条数是否等于 pageSize」小于就说明没数据了这是最省事的做法。注意wx.request的url在开发阶段用localhost但真机调试时手机访问不到电脑的 localhost要换成电脑局域网 IP并且在小程序后台配置合法域名否则请求直接被拦。这是新手第一个翻车点。4.2 预约按钮与登录态处理预约前必须拿到用户身份小程序用wx.login换 code后端换 openid。别在预约接口里裸传 userId那样谁都能伪造。reserve(e) { const spotId e.currentTarget.dataset.id; wx.login({ success: (loginRes) { wx.request({ url: http://localhost:8080/api/reserve, method: POST, header: { content-type: application/json }, data: { code: loginRes.code, spotId: spotId }, success: (res) { if (res.data.code 200) { wx.showToast({ title: 预约成功 }); } else { wx.showToast({ title: res.data.msg, icon: none }); } } }); } }); }后端拿到code后调微信接口换openid再根据 openid 查或建用户。这样用户身份由微信保证不用自己维护登录密码。showToast的icon: none用于显示较长的错误文案默认的 success 图标会截断文字。4.3 订单列表与状态展示订单页要区分「进行中」和「已完成」用pay_status和exit_time判断。列表项展示车牌、车位号、入场时间、费用进行中的订单给一个「结算出场」按钮。onShow() { wx.request({ url: http://localhost:8080/api/order/my, data: { code: this.data.code }, success: (res) { const orders res.data.data.map(o ({ ...o, statusText: o.exitTime ? 已完成 : 进行中, feeText: o.fee ? ¥ o.fee : 计费中 })); this.setData({ orders }); } }); }在onShow里请求而不是onLoad这样从结算页返回时列表会自动刷新用户能看到最新状态。这个细节答辩时容易被夸因为很多人只在onLoad请求返回后数据是旧的。5. 避坑排查联调阶段最容易翻车的 5 个问题代码写完只是开始联调才是真正掉头发的地方。下面这 5 个是我和身边同学踩过最多的按「现象 → 原因 → 解决」写清楚。5.1 小程序请求报「不在以下 request 合法域名列表中」现象开发者工具里能跑真机预览或上传体验版后所有接口报错。原因小程序正式环境要求所有请求域名在后台白名单里且必须是 HTTPS。解决开发阶段在开发者工具「详情—本地设置」勾选「不校验合法域名」真机调试时用局域网 IP 并同样关闭校验如果要上线必须备案域名 HTTPS 证书毕设演示用开发版即可不用折腾上线。5.2 预约成功但首页空闲车位没减少现象点了预约数据库reservation有记录但首页刷新后车位还是显示空闲。原因只改了parking_spot.status忘了同步parking_lot.free_spots或者首页查的是缓存数据。解决确认预约事务里调了decreaseFreeSpots首页每次onShow重新拉数据别把列表缓存在data里不刷新。5.3 计费金额出现 0.30000000000000004 这种小数现象费用算出来一堆小数位。原因用了FLOAT或DOUBLE存金额浮点运算精度丢失。解决数据库金额字段用DECIMAL(10,2)Java 用BigDecimal且BigDecimal比较用compareTo不用equalsequals会比较精度。这是 Java 基础里的经典坑面试也爱问。5.4 并发预约时两个用户抢到同一个车位现象压测或手动快速点击时同一车位出现两条预约记录。原因查询和更新之间没有锁两个事务都读到空闲状态。解决用SELECT ... FOR UPDATE加行锁或给parking_spot加版本号字段做乐观锁。注意FOR UPDATE必须在事务里才生效方法上别忘了Transactional。5.5 MySQL 连接报「Public Key Retrieval is not allowed」现象Spring Boot 启动连 MySQL 8 报这个错。原因MySQL 8 默认认证插件是caching_sha2_passwordJDBC 驱动需要显式允许公钥检索。解决连接串加allowPublicKeyRetrievaltrueuseSSLfalse或者把用户认证插件改成mysql_native_password。MySQL 安装配置教程里很少提这个但用 8.x 版本基本都会遇到。6. 让这套工程在答辩时更站得住的两个进阶技巧基础功能跑通只能算及格想拿高分得让老师看到你「想过边界」。第一个技巧是加一个简单的车位状态看板用定时任务或 WebSocket 把车位状态实时推到小程序哪怕只是轮询也能讲出「实时性」这个点。实现上不用复杂后端加个/api/spot/status接口返回各状态车位数量小程序每 10 秒轮询一次页面上用不同颜色区分空闲、占用、预约。答辩时你可以说「考虑到停车场场景对实时性要求高我用了轮询 状态机保证一致性」比干巴巴说「我做了个列表」强得多。第二个技巧是把计费规则做成配置化而不是写死在代码里。在parking_lot表加free_minutes免费时长和billing_type按小时/按分钟两个字段calcFee方法根据配置走不同分支。这样老师问「如果停车场想改计费规则怎么办」你能答「改数据库配置即可不用改代码重新部署」。这个思路在真实项目里叫「配置驱动」毕设里用上就是加分项。public BigDecimal calcFee(Date entry, Date exit, ParkingLot lot) { long minutes (exit.getTime() - entry.getTime()) / (1000 * 60); if (minutes lot.getFreeMinutes()) return BigDecimal.ZERO; long billable minutes - lot.getFreeMinutes(); if (MINUTE.equals(lot.getBillingType())) { return lot.getPricePerHour() .multiply(BigDecimal.valueOf(billable)) .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); } long hours (billable 59) / 60; return lot.getPricePerHour().multiply(BigDecimal.valueOf(hours)); }divide的第二个参数 2 是保留两位小数第三个参数RoundingMode.HALF_UP是四舍五入不指定会抛ArithmeticException除不尽时。这个细节我当年就是没写跑起来直接异常查了半天才反应过来。最后说个我自己的习惯这套工程交付前我一定会把「预约—入场—出场—再预约」这条链路手动走三遍每遍都去数据库里核对parking_spot.status、parking_lot.free_spots、order三张表的数据是否一致。数据对不上页面再好看也是纸糊的。毕设答辩老师不一定懂你的技术栈但数据一致性这种硬伤一眼就能看出来。把这条闭环跑顺比堆十个花哨页面都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表