Flask+Vue校园电动车短租平台全栈开发实战

发布时间:2026/10/20 8:07:55
Flask+Vue校园电动车短租平台全栈开发实战 今年帮几位师弟改毕设的时候发现一个现象校园里电动车已经成了刚需但“买车怕丢、骑车怕没电、充电排长队”的问题几乎每个学校都存在于是不少人都把“校园电动车短租平台”当成选题方向。用Flask还是Django、前端选什么框架、怎么把租车-计费-还车整条链路跑通这些问题反反复复被问到。今天干脆把我自己做的一个Flask Vue校园电动车短租平台的完整开发过程拆开来讲从技术选型、数据库设计到后端API、前端页面再到联调阶段踩过的坑一次性说清楚。这个项目面向的读者很明确正在做毕业设计的学生、想练手前后端分离全栈流程的开发者以及学校里真实想落地一个分时租赁小系统的同学。后端我用的是Python Flask前端是Vue配合PyCharm开发数据库用MySQL本地也可以用SQLite过渡。如果你正卡在“不知道从哪下手”“模块怎么划分”“跨域报错怎么解决”这类问题上这篇文章应该能给你一条直接可以照着走的路径。1. 为什么是Flask Vue而不是Django全家桶1.1 业务规模决定技术骨架很多人在选题初期都会纠结既然标题里写着“pythonflask”为什么还要看到Django实际上这个项目用Flask还是Django都能做区别在于你的业务复杂度和个人对框架的掌控力。校园电动车短租平台的核心业务是用户注册登录、车辆浏览、下单租车、计费结算、还车、订单查询和管理员后台。这个规模放在Flask里用Blueprint划分模块后每个模块只有几百行代码非常清晰如果上DjangoORM、Admin、迁移工具这些重量级武器确实方便但也会让你把大量时间花在“学习框架约定”而不是“理解业务逻辑”上。我的建议是如果这是你的毕设或者全栈练手项目Flask的轻量反而是一个优势——你能更清楚地看到请求进来之后路由怎么分发、视图怎么处理、ORM怎么操作数据库整个链路没有魔法。如果以后需要快速上线一个带复杂后台权限管理的商业项目Django当然更合适但那是另一个故事了。1.2 Flask和Django的实感对比用表格直接说结论对比维度FlaskDjango上手门槛低一个文件能跑通中高概念多、约定多自由度高想怎么组织就怎么组织受框架约束但结构规范ORM需单独使用SQLAlchemy自带ORM迁移方便Admin后台需自己写或用第三方库自带admin开箱即用适合场景API服务、中小型系统、教学项目内容管理、大型业务系统本项目中的角色后端API主力仅在对比和设计中参考实际开发时我用Flask的Blueprint划分了auth、vehicle、order、admin四个模块配合Flask-SQLAlchemy操作数据库用Flask-JWT-Extended做登录态前后端完全分离。Flask的灵活在这种中小型项目中体现得很明显我完全按照自己的业务习惯组织目录不需要被框架自带结构绑架。1.3 PyCharm下的项目初始化与虚拟环境开发环境我用的是PyCharm Professional创建项目时选择虚拟环境Python版本用的3.10。初始化依赖只需要五个核心库其他用到再加Flask3.0.3 Flask-SQLAlchemy3.1.1 Flask-JWT-Extended4.6.0 Flask-CORS4.0.1 PyMySQL1.1.0项目目录结构我是这样划分的electric_scooter_backend/ ├── app.py # 应用入口注册蓝图 ├── config.py # 配置类数据库、JWT密钥 ├── models/ # SQLAlchemy模型 │ ├── __init__.py │ ├── user.py │ ├── vehicle.py │ └── order.py ├── api/ # 蓝图模块 │ ├── __init__.py │ ├── auth.py │ ├── vehicle.py │ ├── order.py │ └── admin.py ├── utils/ # 工具函数统一响应、计费等 │ ├── __init__.py │ ├── response.py │ └── billing.py └── requirements.txt配置类单独放在config.py里方便本地和部署环境切换。核心配置就三块数据库连接地址、JWT密钥、前后端跨域白名单。密钥在本地可以用secrets.token_hex(32)生成一串提交代码前一定不要暴露到公共仓库这点单独提一句是因为我见过太多demo把密钥直接明文写到配置里。2. 校园电动车短租的业务建模先理清需求再动手2.1 三类角色和核心流程动手写代码之前我习惯把业务规则先列成文字。这个项目里有三类角色学生用户、管理员、维护人员可以合到管理员角色里。用户的完整流程是注册登录 → 浏览车辆 → 选择车辆发起租车 → 模拟支付押金和预扣费用 → 开始计时 → 还车结算 → 查看订单。管理员的核心操作是管理车辆信息上下架、修改电量、查看所有订单、处理异常还车比如用户上报故障。开发时我用一个role字段区分用户和管理员没有单独建用户表。租车流程里的关键规则是同一辆车同一时刻只能被一个用户租用租车订单必须有“使用中”状态还车后才能生成费用费用计算按分钟计费部分场景可以叠加里程系数。这些规则不提前定清楚后端的接口设计就一定反复返工。2.2 数据库表的划分与设计思路数据库我设计了五张核心表用户表、车辆表、订单表、车辆维护记录表、优惠券表。对毕设规模来说足够没有过度设计。用户表字段id、username、password_hash、real_name、student_id、phone、balance余额、role、create_time。车辆表字段id、plate_number车牌号、brand、battery_level电量百分比、latitude、longitude、status0空闲 1使用中 2维护中 3下线、hourly_rate时租价、deposit押金、image_url。订单表字段id、order_no订单号、user_id、vehicle_id、start_time、end_time、start_mileage、end_mileage、total_amount、status0待支付 1使用中 2已完成 3已取消 4异常、create_time。这里有三个细节容易忽略第一订单号和主键id分开订单号用于前端展示和后期对账用时间戳随机数生成第二车辆表里必须存经纬度因为前端地图找车功能需要直接读取车辆位置来打点第三里程和时长都要记录计费策略变化时历史订单还能回溯。2.3 计费规则的设计计费是这个项目的核心展示点。我采用的规则是起步价 时长费 里程费叠加起步价定为2元包含前30分钟超出部分每分钟0.1元里程每公里0.3元。这个规则在utils/billing.py里实现为一个纯函数输入租用时长和骑行里程输出费用# utils/billing.py def calculate_amount(duration_minutes: int, mileage_km: float) - float: 计费规则 - 起步价 2 元包含前 30 分钟 - 超出时间按 0.1 元/分钟计 - 里程按 0.3 元/公里计 base_price 2.0 if duration_minutes 30: duration_fee 0.0 else: duration_fee (duration_minutes - 30) * 0.1 mileage_fee mileage_km * 0.3 total round(base_price duration_fee mileage_fee, 2) return total把计费逻辑抽成纯函数而不是散落在视图函数里是我这次做的比较正确的一个决定。后期调整计费规则只改这个文件所有引入它的接口自动生效。测试的时候直接写个循环用例把边界情况29分钟、30分钟、31分钟、50公里全跑一遍不用起服务。3. Flask后端API设计从目录结构到接口令牌3.1 用Blueprint划分模块后端不把所有路由写在一个文件里。Blueprint是Flask里组织模块的标准方式我的四个模块这样注册# app.py from flask import Flask from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_jwt_extended import JWTManager from api.auth import auth_bp from api.vehicle import vehicle_bp from api.order import order_bp from api.admin import admin_bp db SQLAlchemy() jwt JWTManager() def create_app(): app Flask(__name__) app.config.from_object(config.Config) db.init_app(app) jwt.init_app(app) CORS(app, resources{r/*: {origins: http://localhost:5173}}) app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(vehicle_bp, url_prefix/api/vehicle) app.register_blueprint(order_bp, url_prefix/api/order) app.register_blueprint(admin_bp, url_prefix/api/admin) return appcreate_app工厂模式在这里的主要好处是方便测试——测试环境可以直接创建一个带不同配置的app实例不用在环境变量和启动命令之间反复切换。3.2 JWT登录态与统一响应格式前端每次请求都要知道当前用户是谁我用Flask-JWT-Extended签发token登录成功后在response里返回access_token前端把它存在localStorage后续请求在header里带上Authorization: Bearer token。需要当前用户信息的接口直接通过get_jwt_identity()拿到用户id。为了保证前后端对接不出歧义我统一了所有接口的返回格式# utils/response.py def ok(dataNone, messagesuccess): return {code: 0, message: message, data: data} def fail(messageerror, code1, dataNone): return {code: 1, message: message, data: data}前端axios只需要拦截code ! 0的情况统一弹错误提示不用每个接口单独处理异常分支。实际开发中统一响应格式的价值远比想象中大尤其是你一个人同时写前后端的时候能省下大量对接口的时间。3.3 车辆与订单API的核心逻辑车辆模块中最核心的接口是获取车辆列表和获取车辆详情。列表接口支持按状态筛选前端地图页只需要空闲车辆。订单模块是重点我拆成五个接口创建订单租车检查车辆状态 → 检查用户余额 → 创建订单 → 锁定车辆模拟支付支付押金预扣费更新订单状态为使用中、生成支付记录还车计算费用 → 扣费 → 更新车辆状态取消订单仅限使用前取消超过时间不可取消订单列表区分用户视角和管理员视角创建订单的代码重点在于车辆状态的并发检查# api/order.py order_bp.route(/create, methods[POST]) jwt_required() def create_order(): user_id get_jwt_identity() data request.get_json() vehicle_id data.get(vehicle_id) vehicle Vehicle.query.filter_by(idvehicle_id, status0).first() if not vehicle: return fail(该车辆当前不可租用) user User.query.get(user_id) if user.balance vehicle.deposit: return fail(余额不足请先充值) order Order( order_nogenerate_order_no(), user_iduser_id, vehicle_idvehicle.id, start_timedatetime.now(), start_mileagevehicle.current_mileage, status0 ) db.session.add(order) vehicle.status 1 # 锁定车辆 db.session.commit() return ok({order_id: order.id, order_no: order.order_no})注意这个操作不是一个严格意义上的事务性原子操作高并发下两个用户同时抢同一辆车可能都通过了状态检查。我在本地开发时靠vehicle.status 1加数据库唯一约束兜底后续可以引入行级锁或者用select_for_update()彻底解决。3.4 SQLAlchemy模型定义要点模型定义层面我踩过的坑是字段类型精度。金额相关的字段一定不要用Float用Numeric(10, 2)避免出现0.1 0.2不等于0.3的情况。订单号字段直接设置unique约束。车辆表的经纬度用Numeric(10, 6)存精度足够定位。# models/vehicle.py class Vehicle(db.Model): __tablename__ vehicle id db.Column(db.Integer, primary_keyTrue) plate_number db.Column(db.String(20), uniqueTrue, nullableFalse) battery_level db.Column(db.Integer, default100) latitude db.Column(db.Numeric(10, 6)) longitude db.Column(db.Numeric(10, 6)) status db.Column(db.Integer, default0) hourly_rate db.Column(db.Numeric(10, 2), default10.00) deposit db.Column(db.Numeric(10, 2), default50.00)用PyCharm的Database面板连接MySQL后可以直接可视化看表结构和测SQL开发体验比命令行好不少。表结构改动频繁的初期可以先不建外键约束靠应用层保证数据一致性后期稳定了再补约束。4. Vue前端实战从地图找车到订单结算4.1 前端项目初始化与UI组件库选择前端我用Vue 3 Vite搭建UI组件库用Element PlusHTTP库用axios。创建项目直接用官方脚手架npm create vuelatest npm install element-plus axios vue-router pinia组件库我选Element Plus而不是Vant原因是这个项目既有移动端小程序风格的页面找车、租车也有后台管理端的表格页面订单列表、车辆管理Element Plus的表格和表单组件能覆盖两种场景。如果只做移动端H5Vant会更轻。前端目录结构按功能拆成四个viewLoginView、MapView地图找车、OrderView订单流程、AdminView后台管理。路由用createRouter创建配上登录守卫未登录的用户统一跳转到登录页。4.2 地图找车与列表模式的实现地图找车是项目的门面功能。我接入了高德地图JS API在MapView中初始化地图后从后端拉取空闲车辆列表用AMap.Marker在经纬度位置打点。每个Marker绑定点击事件弹窗里展示车辆信息和“立即租用”按钮。这里有一个实际操作上的注意点高德地图在本地开发用file://方式打开会报安全错误必须通过本地服务访问。开发时直接npm run dev生产环境build后放到后端静态目录或用Nginx托管。列表模式作为地图模式的补充用表格展示车辆名称、电量、租金、距离方便按电量排序筛选。我在页面上加了一个电量显示条电量低于20%的车辆直接标注“电量低”并置灰避免用户租到骑不了的车。4.3 订单流程的状态拆解订单流程在前端可以分为四个状态视图待支付创建订单后、使用中支付成功后、待结算点击还车后、已完成费用结算完显示详情。前端用一个orderStatus字段控制页面显示不同状态下展示不同按钮组。https://api.xxx.com/upload 这个接口是模拟上传还车照片的实际操作中我把它设计成用户点击还车先强制上传一张车辆照片用于确认车辆无损然后调用还车接口。后端接图片上传后可以直接存本地把文件路径存到订单表里。前端代码如下const returnScooter async () { if (!photoUrl.value) { ElMessage.warning(请先拍摄或上传还车照片) return } const res await returnOrder({ order_id: orderId.value, end_mileage: endMileage.value, photo_url: photoUrl.value }) if (res.code 0) { orderStatus.value completed totalAmount.value res.data.total_amount } }这个设计逻辑是还车不是简单点一下按钮必须确认车辆物理归还和实际里程数业务上合理答辩演示时也更有话讲。4.4 axios封装与路由守卫axios封装我单独放在utils/request.js拦截器里做两件事取token加到请求头统一处理响应码。401状态码跳转登录页。这样每个页面调接口时不用重复写token逻辑。路由守卫用的是Vue Router的beforeEachrouter.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })这个守卫逻辑要放在项目初期就写好不然后续每写一个页面都要重复判断登录态很容易漏。我在实际开发中就因为一开始没写守卫导致某些接口在没有token时直接返回401排查了半夜才发现是前端没跳转。5. 联调阶段踩过的坑跨域、时间、并发与Mock支付5.1 跨域问题与CORS配置前后端分离开发最常见的第一个坑就是跨域。前端跑在5173端口后端跑在5000端口直接请求必然报CORS错。我在后端用Flask-CORS解决# app.py 中 CORS(app, resources{r/api/*: {origins: http://localhost:5173}})注意resources要写在/api/*前缀上不是全放行*。全放行开发时省事但上线有一点安全隐患关闭后反而能逼着你理清哪些域名可以访问你的API。5.2 时间字段的时区问题Flask默认存的是UTC时间前端拿到后直接用会显示比北京时间慢8小时。我在创建订单时手动指定datetime.now()就会用服务器本地时间但为了统一还是建议在config里配置APP_TIMEZONE Asia/Shanghai同时前端在展示时间时用dayjs做格式化不要直接用字符串。我测试时发现订单列表页的时间显示错乱排查思路就是先看接口返回的原始时间字符串再确认前端格式化时有没有指定时区最后发现问题出在数据库连接参数里没加时间设置。5.3 并发租车的锁车逻辑两个用户同时点击租同一辆空闲车如果没有并发控制两个订单都能创建成功车辆状态被覆盖。我的处理方案是在车辆表中增加一个version字段更新时用乐观锁updated Vehicle.query.filter_by(idvehicle_id, status0, versionversion) \ .update({status: 1, version: version 1}) if updated 0: return fail(车辆已被其他用户租用)这是我在测试接口时用两个浏览器标签同时提交请求抓到的bug当时很震惊页面看起来一切正常查数据库才发现记录重复。这个坑写出来是想提醒大家只要涉及状态变更必须考虑并发不要觉得本地测试没有并发就跳过。5.4 Mock支付与演示环境真实对接第三方支付接口需要商户资质而且需要企业认证学生项目很难申请通过。我采用了Mock支付方案前端调用“模拟支付”接口后端直接生成一条支付流水把订单状态改为已支付支付结果页面提示“演示环境未发生真实扣款”。Mock支付模块设计为独立接口这样以后真的接入微信或支付宝只要替换支付实现订单主流程不用动。6. 验收清单与经验沉淀6.1 上线前必须跑通的验收路径项目做完之后我给自己列了一张验收清单按顺序逐项检查注册 → 登录 → 获取token → 获取用户信息地图加载 → 获取空闲车辆列表 → 车辆打点 → 点击弹窗创建订单 → 检查余额 → Mock支付 → 车辆锁定开始骑行 → 结束骑行 → 上传还车照片 → 费用计算用户订单列表 → 管理员订单列表 → 车辆上下架边界测试重复租车、余额不足、电量不足每一项都是一个完整的用户故事按这个顺序跑一遍基本能覆盖主流程。不要跳着测不然经常是“功能都能启动”但“关键路径是堵的”。6.2 答辩演示时值得讲的三个技术点如果这个项目是毕设答辩时不要从头到尾念PPT重点讲三件事一是计费模块的纯函数设计与边界测试这体现你的代码抽象能力二是并发锁车的乐观锁方案这体现你思考过真实业务问题三是Mock支付的分层设计这体现你的架构意识。演示时提前准备一辆水温正常的车数据库里造好3-5辆车的数据、一个余额充足的测试账号、几笔历史订单。不要让评委看着你现场注册账号。6.3 我在实际开发中的几点体会把整个项目从零跑通之后我最大的感觉是一个校园电动车短租平台的难点不在某个技术点而在状态流转。车辆状态、订单状态、支付状态任何一个状态没走通整条链路就断。先花一天时间把状态图画清楚写代码的效率会高出一大截。另外我强烈建议开发过程中多打调试日志后端每个核心接口在关键节点加一行logger.info比如“创建订单成功订单号xxx车辆id为xxx”。联调时出了问题通过日志能快速定位是前端没调接口、后端逻辑错了还是数据库没落库。还有一个小技巧数据库种子脚本一定不要省。启动项目之后手动一条条插数据太浪费时间。准备一个scripts/seed.py执行后自动创建5辆车、2个用户、3笔订单每次重置数据库后一键恢复演示数据省下来的时间足够多做两个功能。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询