资讯动态

SSM+微信小程序共享停车位系统:数据库设计与订单防超卖实战

发布时间:2026/10/18 11:35:46 来源:尧图企业网站定制
简介基于微信小程序与SSM框架的共享停车位系统面向毕业设计、课程设计及小程序后端开发学习者完整呈现从车位发布、车辆管理到订单计费的一体化流程。资源共391个文件压缩包约23.06MB涵盖java/jsp后端源码、xml配置、class编译文件、jar依赖库以及微信小程序端的js、wxml、wxss、json页面文件并含png图标与数据库SQL脚本目录结构清晰便于按模块对照学习。系统主要实现了共享车位的创建与管理、车辆信息维护、附近车位定位显示、车位编号/位置/状态查看以及订单记录、计费与结束确认等核心功能个人中心模块还支持订单记录与反馈意见管理。对于需要快速搭建类似项目的开发者可通过源码与数据库文件直接部署运行重点学习SSM框架与小程序交互、订单计费逻辑及前后端数据联调思路。目前已有822人学习下载适合作为毕业设计参考或实训项目二次开发。1. 共享停车位小程序一套SSM后端加微信小程序能解决什么共享停车位小程序这套方案本质是把小区、写字楼里闲置的私家车位通过微信小程序撮合给临时需要停车的人。标题里的SSM_car.rar是典型源码包形态小程序前端、SpringSpringMVCMyBatis后端、SQL数据库脚本三件套。它的价值在于场景精准——熟人社区、单位园区这类小范围内车位主发布空闲时段车主按距离和价格预约两端信息不再靠微信群吼。适合谁做课程设计、毕业设计的学生以及想用一个周末验证停车撮合需求的开发者。技术选型也务实单机SSM加一台MySQL足够支撑几百用户量级不需要引入复杂的微服务和中间件先把业务闭环跑通再说。2. 数据库设计先行三张核心表撑起整个车位撮合业务拿到这类源码包我一般不会先看代码而是先解压找SQL脚本把表结构过一遍。原因很简单SSM项目的业务边界基本由表定义决定表字段设错了后面Controller和Mapper全要跟着返工。共享停车位的核心业务可以压缩成三个实体谁发布的、车位的物理信息、谁在什么时间用了它。落到表上就是用户表、车位表、订单表。2.1 用户表与车位表字段设计、索引与状态位用户表不建议做得太复杂。微信小程序端通过wx.login拿到code后端再调用官方接口换取openid所以openid是天然的唯一登录凭证直接建唯一索引。昵称、手机号属于展示和联系字段余额字段是给模拟支付用的如果订单直接走微信支付余额字段可以不要但课程设计阶段保留它更方便演示支付闭环。车位表是这套系统的信息核心。经纬度用DECIMAL(10,6)存储精度在小程序地图上足够用了每小时价格用DECIMAL(8,2)避免浮点误差。这里有一个实战上的取舍可用时段我用VARCHAR存08:00和20:00而不是用TIME类型或时间戳。原因是小程序端展示、表单回填都更省事数据库查询也直观代价是无法做复杂的跨天时段计算但共享停车位场景里每日固定可用时段的模型够用了。CREATE TABLE t_user ( id int(11) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid登录唯一凭证, nickname varchar(64) DEFAULT COMMENT 昵称, phone varchar(20) DEFAULT COMMENT 手机号联系方式, balance decimal(8,2) DEFAULT 0.00 COMMENT 账户余额模拟支付用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_parking_space ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 发布者id关联t_user, title varchar(100) DEFAULT COMMENT 车位标题如B2-018, address varchar(200) DEFAULT COMMENT 地址描述, longitude decimal(10,6) NOT NULL COMMENT 经度, latitude decimal(10,6) NOT NULL COMMENT 纬度, price decimal(8,2) NOT NULL COMMENT 每小时价格单位元, start_time varchar(16) DEFAULT 08:00 COMMENT 每日可用开始时间, end_time varchar(16) DEFAULT 20:00 COMMENT 每日可用结束时间, status tinyint(4) DEFAULT 1 COMMENT 1可预约 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_geo (longitude, latitude), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段说明里最有讲究的是车位表的状态位。我见过有人把车位状态做成空闲/占用/下架三个值但下单场景下这个状态很难维护A用户占用中B用户想预约明天下午这时候车位是空闲还是占用所以更合理的做法是车位表只保留可预约/下架两个状态具体某个时间段是否被占交给订单表去算。这一点是后面防超卖设计的前提很多新手在这里第一节课就交学费。索引方面idx_geo覆盖经纬度范围查询idx_user用于查询我发布的车位列表。两表都用了utf8mb4字符集因为用户昵称里可能带Emoji用utf8会报错这是文本框直接输入的坑。2.2 订单表与状态机时间冲突检测是防超卖的前提订单表是整套系统里最需要花心思的一张表。除了常规的订单号、用户、车位关联外有两个字段容易被忽略owner_id和start_time。owner_id冗余车位主的id是为了结算和通知方便不用每次关联查询车位表再查用户表start_time和end_time用VARCHAR存2025-03-20 14:00这种格式一方面和用户输入天然对齐另一方面做时间冲突检测时可以直接用字符串比较字典序和时间序在这套格式下是一致的。CREATE TABLE t_order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号展示给用户, user_id int(11) NOT NULL COMMENT 车主id下单人, space_id int(11) NOT NULL COMMENT 车位id, owner_id int(11) NOT NULL COMMENT 车位主id冗余字段结算用, start_time varchar(16) NOT NULL COMMENT 预约开始如2025-03-20 14:00, end_time varchar(16) NOT NULL COMMENT 预约结束如2025-03-20 16:00, amount decimal(8,2) NOT NULL COMMENT 订单金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待支付 1已预约 2进行中 3已完成 4已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id), KEY idx_space_time (space_id, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单状态机是这里的设计核心。0到4五个状态流转路径是待支付→已预约→进行中→已完成待支付和已预约都可以取消到已取消。这套状态机撑起了整个业务流程前端按钮文案、后端接口校验、数据库统计都依赖它。我习惯把状态定义写在代码注释和SQL注释双份因为后面写MyBatis的update语句时往往会偷偷加上WHERE status #{expectStatus}来防止状态乱跳。idx_space_time联合索引很关键。时间冲突检测的执行频率最高——每次下单都要查这个车位在目标时段内有没有交叉订单有了这个联合索引查询走索引在几百单的数据量下压力很小。冲突检测的SQL逻辑是查同车位、状态在有效范围内、且时间段与目标时段有交集的订单数NOT (end_time #{start} OR start_time #{end})这个条件能覆盖完全不相交的场景剩下的就是有冲突。3. SSM后端接口实现从微信登录到订单闭环的完整链路后端部分是典型的SSM三层结构Controller接收小程序请求Service处理业务规则Mapper通过MyBatis操作数据库。这个项目不需要多复杂的架构但有一个原则必须守住——Controller只做参数接收和结果返回所有校验、状态流转、并发控制都放在Service层。否则业务逻辑一旦散落在Controller里后面查订单状态异常时根本无从下手。3.1 微信登录与token鉴权code2session的调用细节微信小程序的登录流程和传统账号密码不同。前端wx.login()拿到一个一次性code后端拿这个code去微信官方接口换openid和session_key。这里有几个细节必须处理对第一code只能用一次重复调用会被微信拒绝第二请求需要带上appid和secret这两个配置项放在后端配置文件里绝对不能写进小程序前端第三openid换到后不要直接当token用而是生成一个自定义token返回给前端后续请求都带这个token。RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public Result login(RequestBody LoginDTO dto) { String code dto.getCode(); // 1. 调用微信 jscode2session 接口换 openid String openid wechatService.code2Session(code); // 2. 查不到就自动注册查到了直接返回 User user userService.findOrCreate(openid); // 3. 生成登录态 token 并返回 String token userService.createToken(user.getId()); return Result.ok(token); } }createToken的常见做法是UUID加用户id拼接后存Redis设置7天有效期小程序端每次请求在Header里带Authorization: token后端用拦截器统一校验。如果项目里没引Redis简单点可以查一张登录态表但要注意清理过期数据。这里不建议用JWT——没有状态管理用户封禁、踢人下线的需求不好做。登录态校验拦截器只需要做一件事从Header取token查到有效记录就放行并往ThreadLocal里塞userIdController里就能直接拿到当前登录人。这套机制在多个接口复用避免了每个方法都写一遍拿用户的重复代码。3.2 车位发布与附近检索MyBatis动态SQL的查询组合车位发布接口的逻辑相对简单基本信息校验通过后插入t_parking_space表。真正有技术含量的是检索接口。微信小程序端传过来的搜索条件可能是关键词、价格上限、经纬度范围的任意组合用MyBatis的动态SQL处理这类多条件查询是SSM项目的标准姿势。select idsearchSpaces resultTypecom.demo.entity.ParkingSpace SELECT * FROM t_parking_space where if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR address LIKE CONCAT(%, #{keyword}, %)) /if if testmaxPrice ! null AND price lt; #{maxPrice} /if if testminLng ! null and maxLng ! null AND longitude BETWEEN #{minLng} AND #{maxLng} /if if testminLat ! null and maxLat ! null AND latitude BETWEEN #{minLat} AND #{maxLat} /if AND status 1 /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /select这段SQL里有几个细节值得说明。lt;是XML里小于号转义直接写会导致XML解析报错这是MyBatis新手最常见的报错之一。经纬度范围查询不是精准的距离排序但足够实现附近车位的效果——小程序端拿到用户当前位置后按一个矩形范围把经纬度上下限算出来传给后端。如果项目要求按距离从近到远排序就需要在SQL里实时算Haversine距离数据量大时性能和代码复杂度都会上来小项目没必要上这个强度。分页参数offset和pageSize由前端传入默认offset0、pageSize10。排序用create_time DESC把新发布的车位置顶这是最简单也最不容易出错的策略。关键词检索走LIKE模糊匹配因为车位标题和地址本身短数据量也小不需要上全文索引。3.3 下单核心逻辑先锁车位再建订单下单接口是这套系统里最容易翻车的地方。业务规则有四条车位必须存在且可预约预约时间必须合法结束晚于开始目标时段不能和其他订单冲突同一时刻不能有两个人同时下单抢同一个车位。前三条是纯业务校验第四条是并发控制两者不能混在一起处理。public Order createOrder(CreateOrderDTO dto) { // 1. 校验车位存在且可预约 ParkingSpace space spaceMapper.selectById(dto.getSpaceId()); if (space null || space.getStatus() ! 1) { throw new BizException(车位不存在或已下架); } // 2. 校验时间合法性 if (dto.getStartTime().compareTo(dto.getEndTime()) 0) { throw new BizException(结束时间必须晚于开始时间); } // 3. 校验目标时段是否被占用 int conflict orderMapper.countConflict( space.getId(), dto.getStartTime(), dto.getEndTime()); if (conflict 0) { throw new BizException(该时段已被预约换一个时间试试); } // 4. 乐观锁用条件update抢占车位 int rows spaceMapper.lockById(space.getId()); if (rows 0) { throw new BizException(手慢了车位刚被预约); } // 5. 生成订单并落库 Order order buildOrder(dto, space); orderMapper.insert(order); return order; }核心在第四步。lockById执行的SQL是UPDATE t_parking_space SET status 0 WHERE id #{id} AND status 1影响行数为0就说明车位状态变了直接拒绝。这一步既完成了并发控制又把车位状态置为不可预约一举两得。但要注意一个边界如果订单随后取消必须把车位状态改回1否则这个车位的状态就脏了。时间冲突检测的SQL依赖t_order表里的idx_space_time索引和字符串比较select idcountConflict resultTypeint SELECT COUNT(*) FROM t_order WHERE space_id #{spaceId} AND status IN (0, 1, 2) AND NOT (end_time lt; #{startTime} OR start_time gt; #{endTime}) /select状态过滤用了IN (0,1,2)把待支付、已预约、进行中三种状态都视为占用时段已完成和已取消的订单不参与冲突检测。这里的教训是冲突检测必须和状态机绑定如果只查未取消的订单就会漏掉待支付订单也占着时段的情况。4. 微信小程序端实现页面结构、请求封装与订单状态流转小程序端是用户直接接触的一层核心目标是把后端接口的能力包装成顺手的界面。拿到源码后先看目录结构是否清晰再看请求封装是否统一这两个点决定了后续改代码的效率。我见过不少次拿到的小程序端把wx.request散落在每个页面里改一个baseUrl要翻十几个文件这种结构建议直接重构。4.1 小程序目录结构与request封装换baseUrl就能对接标准的小程序端目录应该是pages放页面utils放请求封装components放可复用组件app.js里做全局初始化。共享停车位项目最核心的就是utils/request.js——所有接口调用都走这一个文件好处是统一带登录态、统一处理错误码、统一弹提示。// utils/request.js const BASE_URL http://localhost:8080/api; function request(path, method, data) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: BASE_URL path, method: method || GET, data: data || {}, header: { Authorization: token }, success: (res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };这里约定后端统一返回{ code: 0, data: ..., msg: ... }结构code非0视为业务失败。前后端约定好这一层页面代码就不需要每个接口都写错误处理。BASE_URL在本地调试时是http://localhost:8080真机预览要改成电脑的局域网IP上线要换成HTTPS域名——这是小程序开发的固定流程。4.2 发布与预约页面表单校验、定位与时段选择发布车位页面要解决三个问题地理位置怎么拿、表单怎么校验、时段怎么选。地理位置用wx.getLocation获取但前提是用户在app.json里声明了requiredPrivateInfos且调用前确认授权。用户拒绝授权后要有兜底——提示去设置页打开定位否则发布不了车位。发布表单的核心代码要注意前后端字段名必须一致Page({ data: { title: , address: , price: , longitude: 0, latitude: 0, startTime: 08:00, endTime: 20:00 }, onPublish() { const { title, address, price, longitude, latitude, startTime, endTime } this.data; if (!title || !address || !price) { wx.showToast({ title: 请填写车位标题、地址和价格, icon: none }); return; } if (isNaN(Number(price)) || Number(price) 0) { wx.showToast({ title: 价格必须是大于0的数字, icon: none }); return; } // 调用统一封装的后端接口提交表单 request(/space/publish, POST, { title, address, price, longitude, latitude, startTime, endTime }).then(() { wx.showToast({ title: 发布成功, icon: success }); wx.navigateBack(); }); } });时段选择在界面上用picker组件实现modetime直接绑定到startTime和endTime。价格字段在提交前不仅要做非空校验还要用isNaN(Number(price))判断是不是合法数字——字符串abc直接传到后端数据库插入时会报类型转换错误这类错误在前端拦截掉是最划算的。预约页的逻辑是首页或列表页点击某个车位跳转时通过onLoad参数带上spaceId页面根据车位信息渲染预约时段选择器。用户选好开始和结束时间后点击预约按钮调用后端下单接口。注意这里要在前端再校验一次结束时间晚于开始时间不能只依赖后端用户体验差一大截。4.3 订单状态的界面呈现一份状态映射搞定所有按钮订单列表和订单详情页最头疼的是状态对应的文案和按钮。后端返回的status是0到4的数字前端直接写一堆if-else是灾难。正确的做法是做一份状态映射对象把文案、按钮文案、按钮是否可用统一管理。// 订单状态映射0待支付 1已预约 2进行中 3已完成 4已取消 const STATUS_MAP { 0: { text: 待支付, action: 去支付, color: #ff9900, actionVisible: true }, 1: { text: 已预约, action: 入场, color: #07c160, actionVisible: true }, 2: { text: 进行中, action: 出场, color: #10aeff, actionVisible: true }, 3: { text: 已完成, action: , color: #999999, actionVisible: false }, 4: { text: 已取消, action: , color: #999999, actionVisible: false } };有了这份映射WXML里一个{{STATUS_MAP[item.status].text}}就把状态文案渲染出来按钮显隐用actionVisible控制。按去支付→入场→出场的顺序用户操作一次订单状态向前推进一格每步对应后端一个状态流转接口。这里的经验是不要让前端自己推断状态比如支付成功后自动把页面改成已预约——如果支付成功但后端写库失败界面的状态就是假的。一切以接口返回为准页面每次onShow重新拉订单列表保证状态和服务器一致。5. 避坑指南从本地启动到并发下单的经典翻车现场这一章的每一条都是实际跑这类SSM项目时高概率踩中的坑。按出现频率排序从环境启动到业务并发能避过去基本就告别了大部分调试时间。5.1 Invalid bound statement (not found)Mapper和XML失联现象项目启动正常但一调用某个接口就报Invalid bound statement (not found)具体指向某个Mapper方法。原因接口方法在Mapper.java里定义了但对应的XML里没有这个id或者XML文件根本没被MyBatis扫描到。解决先检查application.properties里的mybatis.mapper-locations是否指向了classpath:mapper/*.xml再看接口方法名和XML的id是否一模一样。这个报错的本质是Java接口和XML映射断开了调试路线就是确认两者互相能找到。5.2 小程序真机请求失败url not in domain list现象开发者工具里接口调得好好的真机预览就请求失败报url not in domain list。原因微信对wx.request的域名有白名单校验开发工具可以勾选不校验合法域名真机上没有这个选项。解决开发阶段用本地局域网IP调试上线前把后端接口部署到HTTPS域名并在小程序后台配置request合法域名。本地联调时让手机和电脑连同一个WiFiBASE_URL改成电脑的局域网IP后端Tomcat监听0.0.0.0就能真机调试完整流程。5.3 并发下单导致超卖乐观锁不是可选项现象两个用户几乎同时点预约后端日志显示两个请求都通过了冲突检测都创建了订单车位被卖了两次。原因冲突检测的SELECT和下单的INSERT之间没有加锁两个请求都读到无冲突然后同时插入。解决回到3.3的lockById用UPDATE ... WHERE status 1这种条件更新影响行数为0就拒绝。这条经验血泪教训——永远不要在并发场景下只依赖先查再插必须有一个原子操作兜底。5.4 订单时间早8小时时区与JDBC连接串现象小程序端传的预约时间是14:00数据库里存成了6:00。原因服务器或MySQL的时区设置不对JDBC连接串缺时区参数。解决JDBC URL上加serverTimezoneAsia/Shanghai同时检查MySQL全局时区show variables like %time_zone%;常见做法是jdbc:mysql://localhost:3306/parking_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai这样日期时间就统一成东八区了。这类时区问题在日志里特别有迷惑性因为System.currentTimeMillis()永远是对的只有写入数据库后才暴露。5.5 code2session返回40029code只能用一次现象登录接口偶发报错微信返回40029 invalid code。原因前端把同一个code发了两遍或者两个接口同时调登录后端第二次用同一个code去换openid就会被微信拒绝。解决登录接口加防重入小程序端确保wx.login成功后只调用一次后端登录接口网络超时不要盲目重试而是重新走一遍wx.login拿新code。这类问题在真机上比开发工具更容易出现因为真机网络延迟高用户可能连点两次登录按钮。6. 部署验证与进阶方向跑通全流程后再谈扩展6.1 本地跑通的最小部署清单先把环境准备齐JDK 8以上、Maven、Tomcat 8.5以上、MySQL 5.7以上、微信开发者工具。拿到源码包后第一步导入SQL脚本建库建表第二步改后端jdbc.properties里的数据库账号密码第三步mvn package打出war包扔进Tomcat的webapps目录第四步小程序端改BASE_URL指向后端地址。启动顺序是先MySQL、再Tomcat、最后微信开发者工具中间任何一步失败就按第5章的排查路径走。6.2 六步全流程验证系统跑起来后按这条路径完整走一遍才能算真正通了注册登录 → 发布一个车位 → 在列表页搜到它 → 预约一个未来时段 → 模拟支付 → 入场 → 出场 → 订单变成已完成。每一步去数据库看一眼对应表的记录登录后t_user多了一行发布后t_parking_space多一行且status1下单后t_order有记录且车位status0订单取消后车位status要恢复为1。这条链路验证完这套系统的主干功能就是可信的。6.3 三个值得做的进阶方向第一个是按分钟计费把固定的每小时价格改成阶梯计价订单结束后按实际停车时长结算。第二个是微信订阅消息用户预约成功后给他发一条预约成功通知入场前再提醒一次这个功能在微信后台配置模板即可不需要额外引入推送服务。第三个是真实微信支付对接统一下单加支付回调验签注意个人主体的小程序接入微信支付有限制课程设计阶段保留模拟支付更务实。我自己第一次做类似项目时订单状态机没画清楚就急着写代码结果取消订单后车位状态没恢复这种边界问题反复改了两天。后来学乖了先在一张纸上把状态流转和每个接口的前置条件列清楚再动手两个下午就写完了主体功能。这套共享停车位系统的复杂度恰好卡在有业务深度但不至于失控的位置把订单状态定义清楚比写出多少行代码都重要。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑