
1. 停车位租赁平台到底要做什么需求拆解与角色建模先说结论停车位租赁平台的核心不是“租车位”这个动作本身而是解决“闲置车位的时间错配”问题。很多写字楼的停车位白天满负荷、晚上空一大片而周边小区恰好相反——白天业主开车上班位子空着晚上回家抢车位。这种天然的潮汐互补就是共享停车位业务能成立的根基。我当初拿到这个题目时第一反应是把它当成一个普通的CRUD管理系统来做后来发现如果真这么做交上去的只能是一个“看起来很全、实际没法用”的演示工程。真正的停车位租赁平台必须把以下三类角色和一条核心业务闭环想清楚。1.1 系统涉及的角色与各自的真实诉求车位主手里有闲置车位想出租赚钱。他的核心诉求是“发布要快、钱要能到账、租客别把车位搞坏”。租客/车主想临时租个车位通常是按小时或按天租。核心诉求是“附近有空位、能快速下单、到现场能找到”。管理员处理纠纷、审核违规发布、看平台运营数据。这个角色在毕设里经常被弱化但我强烈建议保留一个简易后台。这里有个很容易被忽略的点一个用户既可能是车位主也可能是租客。所以数据库设计上不要在user表里用字段区分“是房东还是租客”而是用“行为”来定义角色——发布过车位的就是车位主下过单的就是租客。我见过很多同学的demo把这两个角色做成互斥的结果真实场景一跑就露馅。1.2 核心业务闭环与状态流转整个系统的业务主线只有一条车位主发布车位 → 租客搜索浏览 → 租客下单支付 → 车位主确认/系统自动确认 → 租客入场停车 → 租客出场结算 → 双方互评围绕这条主线衍生出的子功能包括车位收藏、按距离排序、订单取消与退款、租赁时段冲突检测、订单超时自动取消、历史订单查询。1.3 功能清单的取舍逻辑既然是毕业设计级别的项目我的建议是做“完整的最小闭环”而不是盲目堆功能。我当时的功能清单分成了三个优先级优先级功能模块说明P0必须有微信登录、车位发布、车位浏览/搜索、下单/取消、订单列表缺了任何一项业务闭环都跑不通P1强烈建议按距离排序、车位收藏、租赁时段选择、订单状态流转、个人中心这些功能能显著提升系统“像真实产品”的程度P2量力而行在线支付、评价系统、后台管理、消息推送支付和推送涉及资质审核后面单独讲这个取舍逻辑很简单评审老师看系统时先看的是“这个系统能不能完整走通一个业务”而不是“功能多不多”。一个能跑通的闭环比十个静态页面有价值得多。2. 技术选型的底层逻辑为什么是Flask 微信小程序这个题目限定了Python、Flask、微信小程序三个关键词。但要真正答辩时站得住脚你必须能解释“为什么是这个组合”而不是评委一问技术选型就卡壳。2.1 Python后端框架Flask凭什么排在Django和FastAPI前面Flask被称为“微框架”它的核心哲学是“不替你做决定”。相比Django全自动的ORM、Admin后台和模板系统Flask只给你路由、请求响应和模板渲染的最小核心其余全部靠扩展。选Flask做毕设有几个非常现实的好处代码量少容易讲清楚。一个停车位租赁平台用Flask写核心接口可能就几百行代码答辩时你可以逐行解释清楚每个函数干什么。换成Django大量行为被框架“魔法”隐藏了评委一问“这个QuerySet底层怎么执行的”很多同学就懵了。灵活性高。微信小程序后端有大量接口需要返回JSON数据Flask配合jsonify几乎是零成本。而且Flask的路由写法直观app.route(/api/parking, methods[GET])这种形式评审老师扫一眼就明白。部署成本低。一个Flask应用用Gunicorn加Nginx就能跑起来对服务器内存的要求也不高学生机、轻量云服务器完全带得动。顺便回应一下网上经常有人讨论的“Flask和FastAPI怎么选”的问题。FastAPI确实在性能、自动生成API文档和异步支持上更先进但对毕设场景来说Flask的生态更成熟、参考资料更多——你随便搜一个问题Stack Overflow上十年前就有人问过了。FastAPI的资料相对少遇到坑时可能卡很久。2.2 前端载体为什么是微信小程序而不是App或H5停车位租赁是一个典型的“LBS交易”场景用户是在户外、在车附近产生需求的。微信小程序在这个场景下的优势是碾压级的免安装微信本身就是国民应用扫一扫或搜索就能进入小程序没有下载App的门槛。微信生态打通登录直接用wx.login()拿到code换openid支付可以直接调微信支付虽然个人主体有门槛后续还能用订阅消息给用户发“租赁成功”“即将到期”的通知。地图组件成熟小程序的chooseLocation接口直接调起微信内置地图选点不需要自己开发地图SDK这对车位定位功能来说是巨大的省事。我当时也纠结过要不要用H5网页代替——想着反正Flask可以直接渲染模板。但后来想明白了一个关键点这是一个以“位置”为核心场景的产品用到的是手机端的GPS定位和微信的地图选点能力这是H5网页很难做顺滑的。小程序的getLocation接口能拿到经纬度配合后端算距离排序体验非常流畅。2.3 架构总览小程序、Flask、数据库三者如何协作整个系统是一个标准的前后端分离架构微信小程序端负责页面展示、用户交互、获取定位信息。通过wx.request向后端发送HTTP请求数据格式为JSON。Flask后端提供RESTful API处理登录认证、车位管理、订单逻辑与数据库交互后返回JSON数据。数据库我选择的是MySQL。相比SQLiteMySQL更接近生产环境而且Navicat等可视化工具可以直连查看数据调试时非常方便。这三者的协作逻辑是这样的小程序端捕捉用户行为如“点击下单”把参数通过POST请求发给Flask接口Flask校验参数、更新数据库返回订单信息小程序收到结果后跳转页面或弹出提示。整个过程无状态依赖token做身份认证每次请求小程序的header里带上tokenFlask端解析出用户身份。3. 数据库与API设计把业务落成表和接口在整个项目里数据库设计和API设计是最能拉开差距的部分。很多同学上来就建表建到一半发现业务跑不通根源在于没有先用“业务语言”把流程描述清楚。我当时的做法是先在纸上画出订单状态流转图再回头建表这样每张表的每个状态字段都有业务含义。3.1 核心数据表设计与字段说明按照业务闭环我最终设计了六张核心表用户表users字段类型说明idINT PK AUTO_INCREMENT用户IDopenidVARCHAR(64) UNIQUE微信openid唯一标识nicknameVARCHAR(32)微信昵称avatar_urlVARCHAR(255)头像地址phoneVARCHAR(20)手机号备选created_atDATETIME注册时间这里有个经验分享openid是微信生态里的“用户身份证”必须设为唯一索引。但同一个用户在不同小程序里的openid是不同的后续如果要扩展多端登录需要引入unionid机制这里先不展开。车位表parking_spots字段类型说明idINT PK车位IDowner_idINT FK发布者IDtitleVARCHAR(64)车位标题如“XX小区地下B2层018号”addressVARCHAR(255)详细地址latitude / longitudeDECIMAL(10,7)经纬度用于距离计算price_per_hourDECIMAL(10,2)小时单价start_time / end_timeDATETIME可租时段的起止时间statusTINYINT0空闲/1已出租/2下架imagesTEXT图片URL多个用逗号分隔descriptionTEXT补充描述created_atDATETIME发布时间订单表orders字段类型说明idINT PK订单IDorder_noVARCHAR(32) UNIQUE订单编号如“20240409103012345678”spot_idINT FK车位IDuser_idINT FK租客IDstart_time / end_timeDATETIME租赁起止时间total_amountDECIMAL(10,2)总金额statusTINYINT0待支付/1进行中/2已完成/3已取消/4退款中created_atDATETIME下单时间收藏表favorites字段就三个——id、user_id、spot_id加一个唯一约束UNIQUE(user_id, spot_id)防止重复收藏。评价表reviewsid、order_id、user_id、spot_id、rating、content、created_at。注意order_id要加唯一约束保证一个订单只能评价一次。交易流水表transactionsid、order_id、user_id、amount、type支付/退款、created_at。这张表在接入支付后会非常有用哪怕前期用虚拟支付也建议先把流水表建好。3.2 订单状态机设计订单状态是整个系统最核心的业务逻辑我强烈建议把状态的流转规则画出来再写代码。我的设计是待支付(0) → 进行中(1) → 已完成(2) ↓取消 ↑超时 已取消(3) 待支付(0)超24小时自动取消细化一下用户下单后订单进入“待支付”状态。如果用户选择“模拟支付”或接入微信支付后支付成功订单变为“进行中”车位状态同步变为“已出租”。订单到期后系统自动或用户手动确认完成订单变为“已完成”车位状态恢复“空闲”。用户在“待支付”状态可以主动取消如果超时未支付需要定时任务自动取消并释放车位。这里有个容易踩坑的地方车位状态和订单状态必须联动更新。我在初版代码里只更新了订单状态忘了更新车位状态结果出现“车位已出租但列表里还显示空闲”用户下单后两个订单指向同一个车位直接数据错乱。后来加了事务处理才解决。3.3 API接口清单与统一返回格式我设计的API遵循RESTful风格按照模块划分模块方法路径功能认证POST/api/auth/login微信登录code换openid车位GET/api/spots车位列表支持经纬度、关键字过滤车位POST/api/spots发布车位车位GET/api/spots/车位详情车位PUT/api/spots/编辑车位车位DELETE/api/spots/下架车位订单POST/api/orders创建订单订单GET/api/orders我发布的订单区分角色订单PUT/api/orders/ /cancel取消订单收藏POST/api/favorites添加收藏收藏DELETE/api/favorites/取消收藏所有接口统一返回格式为{ code: 0, msg: success, data: {} }这个格式虽然简单但联调时能省大量时间——小程序端只需要统一判断code是否为0然后处理data异常情况统一弹msg就行。4. Flask后端落地从路由到业务的完整实现这一章我直接分享Flask后端的关键实现细节包含登录、车位发布与查找、订单创建三个核心模块。4.1 项目结构设计我习惯用一个清晰但不过度复杂的结构parking-backend/ ├── app.py # 应用入口 ├── config.py # 配置数据库连接、密钥等 ├── models.py # SQLAlchemy模型定义 ├── auth.py # 登录认证与token校验装饰器 ├── api/ │ ├── spots.py # 车位接口 │ ├── orders.py # 订单接口 │ └── users.py # 用户接口 ├── utils.py # 工具函数距离计算、订单号生成等 └── requirements.txt对于毕设规模的项目不需要搞蓝本Blueprint那么复杂的分层但也不能把所有接口堆在一个app.py里——那会让答辩时讲代码非常痛苦。4.2 微信登录后端的完整链路整个登录流程分两步。第一步小程序端调wx.login()拿到临时凭证code把这个code发给后端。第二步后端拿code加上小程序的appid和secret请求微信接口换取openid和session_key。核心代码bp.route(/api/auth/login, methods[POST]) def login(): data request.get_json() code data.get(code) # 向微信服务器换取openid url ( https://api.weixin.qq.com/sns/jscode2session f?appid{APP_ID}secret{APP_SECRET} fjs_code{code}grant_typeauthorization_code ) resp requests.get(url).json() openid resp.get(openid) if not openid: return jsonify(code1, msg微信登录失败, data{}) # 查询或创建用户 user User.query.filter_by(openidopenid).first() if not user: user User(openidopenid, nickname微信用户) db.session.add(user) db.session.commit() # 生成token返回给前端 token generate_token(user.id) return jsonify(code0, msgsuccess, data{token: token, user_id: user.id})这个接口有几个细节值得注意jscode2session接口的域名是api.weixin.qq.com不是api.qq.com不要拼错。曾经在这个域名上卡了半小时。token不要自己发明加密算法直接用itsdangerous库或pyjwt。我用的pyjwt签发时带上用户ID和过期时间后续请求解析即可。每次请求获取用户身份的逻辑封装成一个装饰器login_required注入current_user接口内部直接使用。4.3 车位发布与“附近车位”搜索发布车位接口的核心逻辑是校验必填字段、处理经纬度、检查时段合法性。这里有个容易忽略的点车位发布必须校验可租时段的起止时间且不能与已有订单冲突。附近车位的搜索依赖经纬度的距离计算。我用的Haversine公式计算球面距离from math import radians, sin, cos, asin, sqrt def haversine(lat1, lng1, lat2, lng2): R 6371 # 地球半径单位公里 dlat radians(lat2 - lat1) dlng radians(lng2 - lng1) a sin(dlat / 2) ** 2 cos(radians(lat1)) * cos(radians(lat2)) * sin(dlng / 2) ** 2 return R * 2 * asin(sqrt(a))然后在SQLAlchemy查询时把坐标从Python端算好后传给数据库。如果数据量大更规范的做法是用MySQL的ST_Distance_Sphere函数直接在SQL层计算但对于毕设规模的数据量Python端算就足够了。查询接口还需要支持“只看当前可租的空闲车位”过滤条件是车位状态为空闲且当前时间落在可租时段内。这一条过滤在SQL里要用时间区间条件表达我第一次写的时候漏了结果列表里出现了“时间段还没到”的未来车位。4.4 订单创建与并发下的车位抢占创建订单的接口是整个系统里最容易出并发问题的环节。两个用户同时下单同一个车位必须有机制保证只有一个能成功。我用的方案是“乐观锁状态校验”bp.route(/api/orders, methods[POST]) login_required def create_order(): data request.get_json() spot_id data.get(spot_id) start_time datetime.fromisoformat(data[start_time]) end_time datetime.fromisoformat(data[end_time]) # 关键开启事务并用行锁锁定车位记录 spot db.session.execute( text(SELECT * FROM parking_spots WHERE id:id FOR UPDATE), {id: spot_id} ).fetchone() if spot.status ! 0: return jsonify(code1, msg车位已被占用, data{}) # 校验时间段是否在车位的可租时段内 if start_time spot.start_time or end_time spot.end_time: return jsonify(code1, msg租赁时段超出可租范围, data{}) # 计算金额 hours (end_time - start_time).total_seconds() / 3600 total round(hours * spot.price_per_hour, 2) # 更新车位状态为已出租 spot.status 1 db.session.commit() # 创建订单 order Order( order_nogen_order_no(), spot_idspot_id, user_idcurrent_user.id, start_timestart_time, end_timeend_time, total_amounttotal, status0 # 待支付 ) db.session.add(order) db.session.commit() return jsonify(code0, msgsuccess, data{order_id: order.id, amount: total})这段代码里有两个关键点一是用SELECT ... FOR UPDATE对车位行加锁防止并发重复下单二是即使订单创建后用户没支付车位也先标记为已出租由定时任务在超时后释放。这种“先锁后写”的设计能在并发测试时扛住压。5. 微信小程序前端页面结构与核心交互后端接口就绪后小程序端的开发重点就变成了“如何把接口数据变成好用的界面”。我用的是微信原生开发没有用Uniapp原因很简单——毕设题目写的是“微信小程序”原生框架最对口评审老师也最熟悉。5.1 页面划分与底部导航设计小程序采用经典的“四Tab”结构Tab页面核心内容首页pages/index/index车位列表列表地图切换、搜索框发布pages/publish/publish发布车位表单、地图选点订单pages/orders/orders我发布的/我租用的订单列表我的pages/profile/profile用户信息、我的收藏、关于平台app.json中配置tabBar注意图标需要自己准备小程序自带的图标库有限。我当时选的是一组线性图标风格统一整体观感会好很多。5.2 首页车位列表与距离排序交互首页是用户进入小程序的第一屏我把车位列表设计成卡片式每张卡片展示车位标题、地址、距离、价格和图片。距离数据由后端根据用户当前位置计算后返回前端直接展示。列表页最核心的交互是“获取用户定位”。wx.getLocation接口需要用户授权如果用户拒绝要有兜底方案。我的做法是失败时提示用户去“设置”页手动打开定位权限或者让用户手动选择城市。Page({ onLoad() { wx.getLocation({ type: gcj02, success: (res) { this.setData({ latitude: res.latitude, longitude: res.longitude }); this.fetchSpots(); }, fail: () { wx.showToast({ title: 授权定位失败无法按距离排序, icon: none }); this.fetchSpots(); // 不传定位后端返回全部 } }); }, fetchSpots() { const { latitude, longitude } this.data; wx.request({ url: ${BASE_URL}/api/spots, data: { latitude, longitude }, success: (res) { if (res.data.code 0) { this.setData({ spots: res.data.data }); } } }); } });5.3 发布车位的表单与地图选点发布页面需要录入车位标题、地址、价格、可租时段还要上传车位图片。地址录入不推荐手打文本框——用户根本不知道自己的车位经纬度是多少。正确做法是调wx.chooseLocation接口让用户在地图上选一个点系统自动填充详细地址和经纬度wx.chooseLocation({ success: (res) { this.setData({ address: res.address, name: res.name, latitude: res.latitude, longitude: res.longitude }); } });这个接口返回的latitude和longitude类型是gcj02坐标系也就是俗称的“火星坐标系”可以直接用于地图展示和距离计算。如果你拿它去对接高德、腾讯地图都没问题但跟GPS原始坐标混用会导致位置偏移几十米到几百米这是集成地图时最常踩的坑。5.4 订单页的角色区分与操作入口订单页要区分“我发布的”我作为车位主收到的订单和“我租用的”我作为租客下的订单。我用了两个Tab页签切换各自调不同的接口。租客侧展示“取消订单”“去支付”“确认完成”等操作按钮车位主侧展示“确认订单”“完成订单”等入口。按钮的出现逻辑由订单状态决定写成一个orderStatusText()函数统一映射。联调时最常见的报错是request:fail url not in domain list这是没有在小程序后台配置合法域名导致的。开发模式下可以勾选“不校验合法域名”但正式上线必须配好HTTPS域名否则真机预览一片报错。6. 支付、订阅消息与地图三个“看起来很难”的集成点这三个功能是评审老师最爱的提问对象也是网上资料最零散的部分。我分别说一下真实落地的方案和个人经验。6.1 微信支付的现实处境个人主体绕不开的限制首先必须明确一个现实个人主体注册的微信小程序无法开通微信支付。微信支付要求小程序完成企业认证且经营类目符合要求。如果你用的是个人开发者的账号做毕设直接用微信支付大概率卡在资质审核。我的处理方案是“模拟支付预留真实支付接口”。具体做法是在创建订单后如果检测到当前环境为“演示模式”显示一个“模拟支付成功”按钮点击后直接把订单状态从“待支付”置为“进行中”。在订单支付接口预留好真实的微信支付调用逻辑代码注释里写清楚“正式上线时替换为wx.requestPayment”。这样既不影响演示效果又能让答辩老师看到你对微信支付流程的理解——我建议在文档或代码注释里写明完整的微信支付流程统一下单→小程序调起支付→微信回调通知→后端更新订单状态。6.2 订阅消息做订单提醒停车位租赁有一个天然的消息需求订单即将到期时提醒租客挪车或者车位主收到新订单时得到通知。微信小程序的“订阅消息”功能可以做到这一点。关键限制是订阅消息需要用户主动授权一次且一次性订阅只能推送一条消息。所以不能靠前端预授权必须在用户“下单成功后”弹窗请求订阅授权这样用户更愿意点。后端推送通过subscribeMessage.send接口完成需要拿到用户的openid和授权表单ID。我对这个功能的建议是作为P1功能时间充裕再上。它的演示效果其实很加分——在后台手动触发一条“您的订单即将到期”的推送现场看到手机弹出微信服务通知整个系统的完成度立刻不一样了。6.3 地图集成选点、定位与坐标系地图相关的功能分三块用户定位用wx.getLocation获取type: gcj02。发布车位时选点用wx.chooseLocation用户在地图上拖选并确认位置。车位详情展示可以在详情页内嵌一个地图组件也可以用wx.openLocation调起微信地图导航到目的地这对“用户找到停车位”来说是刚需功能。我强烈建议用wx.openLocation展示车位位置一行代码就能调起微信原生地图导航比在小程序内嵌地图省事且体验更好。7. 部署上线与踩坑实录这个项目从开发到跑通我实际踩过的坑比想象中多。这章把印象最深的几个问题列出来希望能帮你少走弯路。7.1 部署Flask应用的完整链路本地跑app.run(debugTrue)没问题但上线部署时不能这么干。我当时的部署方案是云服务器CentOS安装Python 3.8、MySQL、Nginx。用gunicorn启动Flask应用gunicorn -w 4 -b 127.0.0.1:5000 app:app。配置Nginx反向代理把/api路径的请求转发到本地的5000端口。需要注意的一点是Flask自带的服务器是单进程的并发能力很弱一旦上线稍微有点并发就会卡住。Gunicorn的-w 4表示启用4个worker进程能扛住毕设演示级别的并发。7.2 小程序合法域名与HTTPS小程序正式环境要求所有wx.request的URL必须是HTTPS且域名要在小程序后台配置为“合法域名”。如果没有自己的域名和SSL证书可以申请免费的SSL证书比如腾讯云和阿里云都提供一年期的免费证书配到Nginx上。还有一个坑后端接口如果发生CORS跨域问题虽然小程序原生wx.request不受浏览器CORS限制但如果你用浏览器调试接口跨域问题就出现了。建议在Flask中加上flask-cors扩展统一处理省得后面调试时反复踩。7.3 我实际开发中踩过的几个真实问题坑一session_key泄露风险。微信登录接口返回的session_key是敏感数据绝对不能返回到小程序端。它用来解密用户手机号等敏感信息只应该保存在后端或直接丢弃。我初版把所有登录返回信息一股脑传给前端后来看文档才发现这个安全隐患。坑二订单超时未支付的释放逻辑。一开始我用apscheduler定时任务每分钟扫一次待支付订单超过10分钟未支付就自动取消并释放车位。后来发现了一个问题——用户刚下单还没到支付页面定时任务正好扫到这条记录把它取消了。解决方案是只在订单创建后10分钟未支付才算“超时”扫描条件要排除刚创建的头几分钟或者用created_at now - 10min判定。坑三图片上传临时路径失效。小程序端wx.chooseMedia选择的图片直接拿临时路径上传后端数据库里存的是临时路径用户一刷新页面图片就失效了。正确的做法是先wx.uploadFile把图片上传到自己的服务器后端保存文件并返回可访问的URL数据库存这个URL。我当时因为偷懒直接存了临时路径被测试的室友吐槽“图片加载不出来”返工了一次。坑四数据库时区问题。MySQL默认时区是UTCPython端用本地时间存入后时间差了8小时导致订单的时段计算、超时判断全部错乱。解决方案是在数据库连接串中加?charsetutf8mb4timezone08:00并在后端统一用Asia/Shanghai时区处理时间。8. 最后聊聊如果重新做一遍我会怎么规划项目收尾时回头看整个停车位租赁平台的开发和部署核心难点其实不在技术上而在“业务流程理解”和“边界情况处理”上。如果你从一开始就能把订单状态机画清楚、把车位与订单的联动逻辑想明白后面的编码工作纯粹是体力活。最后分享几个具体的建议答辩演示前一定要准备一个“测试账号测试数据”的完整演示脚本从登录、发布车位、搜索、下单到完成订单一气呵成。演示过程中如果网络不好微信API偶发超时要有“演示环境后端已提前跑好”的应急预案。源码里数据库的连接信息如果是公开演示用的环境可以用环境变量的方式配置避免硬编码。车位主的收款、车位的预约锁定、过期订单的自动释放——这些真实产品才需要深入打磨的功能对毕设来说可能来不及做但你可以把设计思路写进文档这也是一种加分项。