ARTICLE DETAIL

资讯详情

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

基于Flask与Vue的智能停车场管理系统开发实践

基于Flask与Vue的智能停车场管理系统开发实践 停车场管理这事儿看起来简单真做起来一堆细节。车辆入场要识别车牌、分配车位出场要算时长、算费用管理员还要盯着余位数量和营收数据。我之前帮人做过一个基于 Flask 框架的智能停车场管理系统技术栈是 Python Flask 后端、Vue 前端开发工具用 PyCharm正好把整个流程都趟了一遍。这篇文章把我踩过的坑、设计方案、核心代码逻辑全部分享出来适合正在做课程设计、毕设项目或者想入门 Flask Vue 前后端分离开发的朋友参考。1. 项目定位与技术选型为什么是 Flask 而不是 Django1.1 先搞清楚智能停车场到底要做什么智能停车场系统的核心不是停车而是管理。我接手这个项目的时候甲方提的需求很明确车辆进场能自动记录车牌号和时间出场能根据停车时长自动算钱管理员能实时看到还剩多少个车位同时要有基本的统计报表。听起来不复杂但这里面涉及三个关键环节。第一是数据采集层。真实场景下车牌识别靠的是摄像头加识别算法道闸控制靠的是硬件接口。做软件系统时硬件设备不一定都在所以我用了模拟数据的思路用接口模拟车辆进出留好对接真实硬件的扩展点。这个思路很关键不然项目做完只能面对空气运行。第二是业务逻辑层。计费规则是核心不同停车场规则差异很大有的前半小时免费有的按小时计费上不封顶有的设置了每日最高收费。这些规则不能写死在代码里必须做成可配置的。我在设计的时候就考虑到了这点把计费规则单独做成一张表后来甲方改规则时我只改了数据库记录就搞定了省了重发版本的麻烦。第三是展示层。停车场管理员不是程序员他们需要的是直观的界面一个车位分布图、一个入场登记按钮、一个计费明细列表。用纯后端模板渲染也能做但体验和二次开发的灵活性都差一些。最终我选择了前后端分离后端只管输出 JSON 数据前端用 Vue 来渲染页面。1.2 Flask 与 Django 的抉择逻辑标题里同时出现了 Flask 和 Django很多新手在这个选择上纠结过。我直接说结论这个项目用 Flask 是更合理的。Flask 轻量、灵活没有那么多内置约束。它默认只带路由和模板引擎数据库、表单校验、登录认证这些都可以按需引入插件。对于停车场系统这种以接口为核心的业务Flask 可以让每个路由函数直接对应一个 API逻辑清晰调试起来也方便。Django 则是一个全家桶框架自带 ORM、Admin 后台、迁移机制、认证系统。如果项目需要快速搭建一个内容管理后台Django 的 Admin 确实能省很多工作量。但它的问题是重默认的 ORM 和模型设计思路会限制你的灵活性而且部署和资源占用也比 Flask 大。我在一些内部管理项目里会用 Django但对于停车场这种偏接口服务的场景Flask 明显更顺手。给你一个简单的对照表方便根据自己项目的情况做判断对比维度FlaskDjango项目规模中小型、接口服务型中大型、内容管理型学习成本低上手快较高概念多内置功能少按需装配全开箱即用ORM 灵活度选型自由固定使用自带 ORM适合场景API 后端、微服务后台管理系统、CMS至于 PyCharm我用的是社区版完全免费对 Flask 和 Vue 项目的开发支持都够用。不需要花心思去找什么激活手段社区版配合好用的插件写这个项目绰绰有余。2. 系统功能拆解与数据库设计2.1 功能模块到底怎么划分我把整个系统拆成了五个模块每个模块各司其职互相之间通过接口调用避免代码耦合在一起后期改不动。用户管理模块负责管理员的登录认证这部分我用的是最简单的 Token 方案登录成功后后端返回一个 token前端每次请求带上这个 token后端校验通过才返回数据。没有用复杂的 JWT对这个体量的系统来说没必要有个签名机制就行。车位管理模块负责维护车位的基础信息包括车位编号、所属区域、当前状态空闲/占用/预留。设计上我预留了区域字段因为停车场通常分 A 区、B 区、负一层负二层有了区域字段才能做分区统计。进出管理模块是整个系统的核心。入场接口接收车牌号系统自动生成一条入场记录分配一个空闲车位把车位状态改成占用。出场接口根据车牌号找到最近的入场记录计算停车时长和费用把车位释放。计费规则模块做成了一张独立的配置表字段包括规则名称、免费时长、每小时单价、每日封顶金额、生效时间。计算费用时后端优先读取生效中的规则按照规则里的参数算钱。统计报表模块提供基础的数据分析能力今日进场数、出场数、营收金额、当前占用率。前端用图表库渲染成柱状图和折线图。2.2 数据表设计与模型层实现数据库设计是这种管理系统的地基表结构没设计好后期写接口会到处难受。我最终设计了四张核心表字段如下表名主要字段说明userid, username, password_hash, role, created_at管理员账号密码存哈希parking_spaceid, space_no, area, status, vehicle_plate车位信息status 用 0/1 表示空闲/占用entry_recordid, plate_number, entry_time, exit_time, fee, space_id进出场记录exit_time 为空表示在场fee_ruleid, rule_name, free_minutes, hourly_rate, daily_cap, is_active计费规则is_active 为 1 表示启用模型层用 Flask-SQLAlchemy 实现代码不长但每个字段的类型和约束我都是认真斟酌过的。比如车牌号字段很多人直接存成字符串但忘了加唯一约束和索引。停车场系统每天的进出记录量大这个字段后续要频繁查询索引必须建上。再比如计费金额我统一用整数分来存避免浮点数精度问题。这是很多新手容易踩的坑Python 的 float 在涉及金额计算时会出现莫名其妙的误差比如 0.1 0.2 不等于 0.3。用分做单位金额计算永远是整数运算稳得很。from flask_sqlalchemy import SQLAlchemy from datetime import datetime db SQLAlchemy() class ParkingSpace(db.Model): __tablename__ parking_space id db.Column(db.Integer, primary_keyTrue) space_no db.Column(db.String(16), uniqueTrue, nullableFalse) area db.Column(db.String(16), nullableFalse, defaultA区) status db.Column(db.SmallInteger, default0) # 0空闲 1占用 vehicle_plate db.Column(db.String(16), default) class EntryRecord(db.Model): __tablename__ entry_record id db.Column(db.Integer, primary_keyTrue) plate_number db.Column(db.String(16), indexTrue, nullableFalse) entry_time db.Column(db.DateTime, defaultdatetime.now) exit_time db.Column(db.DateTime, nullableTrue) fee db.Column(db.Integer, default0) # 单位分 space_id db.Column(db.Integer)这四张表之间是有业务关联的但我不建议在 ORM 层把所有外键约束都加上。实际操作中外键在某些边界场景下会拖累灵活性比如人工补录记录的场景。我选择在应用层维护关联逻辑数据库表保持相对独立这样数据迁移和排障的时候反而清爽。3. 环境搭建与后端接口实现3.1 开发环境与项目骨架搭建开发环境这部分我用的组合是Python 3.10、PyCharm 社区版、MySQL 8.0。数据库生产环境用 MySQL本地开发用的 SQLite切换只需要改一行连接串。这个技巧很实用SQLite 是文件型数据库本地跑测试零配置等联调阶段再切到 MySQL 模拟真实环境。创建虚拟环境是第一步很多新手直接把包装到全局环境里后来越装越乱。我的习惯是每个项目建一个独立虚拟环境python -m venv venv # Windows下激活 venv\Scripts\activate # macOS/Linux下激活 source venv/bin/activate接下来安装项目依赖我列一下最核心的几个包pip install flask pip install flask-sqlalchemy pip install flask-cors pip install requestsflask-cors 必须装前后端分离项目跨域问题躲不过后面单独讲。requests 库是给项目预留的跟硬件设备对接时需要通过 HTTP 调用摄像头或道闸的接口。项目目录结构我习惯这样组织parking_system/ ├── app.py # 应用入口注册所有蓝图 ├── config.py # 配置类数据库连接、密钥等 ├── models/ │ ├── __init__.py │ └── db.py # 数据模型定义 ├── api/ │ ├── __init__.py │ ├── auth.py # 登录认证接口 │ ├── entry.py # 进出场接口 │ ├── space.py # 车位管理接口 │ └── stats.py # 统计报表接口 ├── utils/ │ ├── __init__.py │ └── fee.py # 计费计算工具 └── requirements.txt用蓝图来组织路由刚开始看起来多了一层目录但接口越来越多之后这种结构会救你一命。所有路由写在一个文件里到后期几百行代码找 bug 找到怀疑人生。3.2 核心接口入场、出场与计费逻辑入场接口的逻辑流程是接收车牌号检查该车牌是否已在场内防止重复入场分配空闲车位写入场记录。这里有个边界情况必须处理车辆重复入场。比如有车出场时系统没记录成功车又进来了会导致数据混乱。我的做法是查询场内的记录如果车牌号已存在且没有出场时间直接返回该车辆已在场内。from flask import Blueprint, request, jsonify from models.db import db, ParkingSpace, EntryRecord from datetime import datetime entry_bp Blueprint(entry, __name__, url_prefix/api) entry_bp.route(/entry, methods[POST]) def vehicle_entry(): data request.get_json() plate data.get(plate_number, ).strip() if not plate: return jsonify({code: 1, msg: 车牌号不能为空}) # 检查车辆是否已入场 exist EntryRecord.query.filter( EntryRecord.plate_number plate, EntryRecord.exit_time.is_(None) ).first() if exist: return jsonify({code: 1, msg: 该车辆已在场内}) # 分配空闲车位 space ParkingSpace.query.filter_by(status0).first() if not space: return jsonify({code: 1, msg: 车位已满}) record EntryRecord(plate_numberplate, space_idspace.id) db.session.add(record) space.status 1 space.vehicle_plate plate db.session.commit() return jsonify({code: 0, msg: 入场成功, data: { space_no: space.space_no, entry_time: str(record.entry_time) }})出场接口要复杂一些因为要算钱。计费逻辑我单独写在了 utils/fee.py 里核心算法是总停车时长减去免费时长按小时向上取整收费如果设置了每日封顶超过封顶按封顶算。def calc_fee(entry_time, exit_time, rule): from math import ceil total_minutes (exit_time - entry_time).total_seconds() / 60 free_minutes rule[free_minutes] hourly_rate rule[hourly_rate] daily_cap rule[daily_cap] if total_minutes free_minutes: return 0 billable_minutes total_minutes - free_minutes hours ceil(billable_minutes / 60) fee hours * hourly_rate if daily_cap and fee daily_cap: fee daily_cap return fee # 单位分这里有个细节很多人忽略小时计费要向上取整而不是四舍五入。停了 1 小时零 1 分钟如果四舍五入按 1 小时算停车场就亏了。正常停车场的规则都是超过一分钟按一小时算。还有一个坑是免费时长的计算如果用户停了 29 分钟免费时长 30 分钟按理说不收费。但如果把免费和计费分开两次判断边界值很容易出错。我直接用总时长减去免费时长再判断是否大于零一步到位避免边界值漏洞。数据库事务的问题也要提一下。入场逻辑里有两个写操作插入记录、更新车位状态。这两步必须在一个事务里完成要么都成功要么都失败。SQLAlchemy 的 db.session 本身就是事务管理只要在最后统一 commit中途任何一步报错都会自动回滚。我在开发过程中验证过这个机制确实能避免脏数据。4. 前端 Vue 界面与联调4.1 Vue 项目初始化与路由设计前端用的 Vue 3 加 Element Plus 组件库状态管理用的 Pinia。选择 Vue 的原因很直接组件化开发适合这种管理类界面车位状态可以在一个组件里循环渲染进场出场逻辑做成独立组件复用。创建项目的命令很简单需要 Node.js 环境我用的 Node 18npm create vuelatest # 按提示选择需要的特性router、pinia 都选上 npm install element-plus npm install axios路由设计上我用了左右布局的通用后台框架左侧菜单右侧内容区。路由表配置了三个核心页面一个是概览页显示统计数据和车位图一个是入场管理页一个是记录查询页。用 Vue Router 的懒加载模式页面代码块按需加载首屏速度会好很多。// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, component: () import(../layout/AdminLayout.vue), children: [ { path: , name: overview, component: () import(../views/OverviewView.vue) }, { path: entry, name: entry, component: () import(../views/EntryView.vue) }, { path: records, name: records, component: () import(../views/RecordsView.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes })车位管理页面的设计是一个网格图每个格子代表一个车位空闲显示绿色占用显示红色上面印着车位编号。管理员扫一眼就知道哪里有空位。这个效果在 Vue 里实现起来很简单就是 v-for 循环渲染卡片根据 status 字段切换样式类。4.2 前后端联调的那些坑前后端联调是新人最容易摔跤的地方。第一个大坑就是跨域。前端跑在 5173 端口后端跑在 5000 端口不是同一个源浏览器默认会拦截后端的响应。解决跨域有两个思路后端加 CORS 头或者前端配代理。后端方案最简单装 flask-cors 然后一行代码启用from flask_cors import CORS app Flask(__name__) CORS(app) # 开发环境直接全放开生产环境就不要这么粗犷了最好指定允许的来源域名CORS(app, resources{r/api/*: {origins: http://your-frontend-domain}})。第二个坑是时间格式。后端返回的 datetime 对象默认是类似2025-01-15T10:30:00的 ISO 格式前端拿到的是字符串。如果前端直接用这个字符串去计算停车时长或者传给 Element Plus 的日期组件会有各种问题。我推荐的做法是后端在时间字段上统一用strftime(%Y-%m-%d %H:%M:%S)格式化输出前端解析和展示都省心。第三个坑是请求体格式。Flask 的request.get_json()要求前端请求头里必须有Content-Type: application/json。用 axios 的时候如果直接传一个 JavaScript 对象axios 会自动正确设置但如果有人图省事用 URLSearchParams 或者 FormData 传后端接到的就是空字典排查起来非常头大。我建议前端所有 POST 请求统一用 axios 封装别在业务代码里手动拼请求头。4.3 前端核心交互逻辑进场登记的交互逻辑前端要处理的是用户输入车牌号点击确认后端返回成功或失败前端根据结果刷新车位状态。这里我给了一个防重复提交的保护请求发出后按钮置灰等响应回来再恢复。别小看这个细节真实使用中网速慢一点用户连续点两下就会产生两条入场记录。出场计费的交互是用户输入车牌号查询后端返回该车辆的入场时间和预计算费金额用户点击确认出场再调用出场接口。这里设计了一个展示账单后再确认的环节用户体验更好也让管理员有机会核对金额。统计数据页面的图表我用的是 ECharts通过 npm 安装后按需引入。折线图展示最近七天的进出场数量对比柱状图展示各时段的停车高峰。数据来源是后端的统计接口返回按天聚合的数据后前端直接丢给 ECharts 的 series 配置即可。5. 常见问题与排障实录5.1 新手最容易碰到的几个报错我在开发和带人做项目的过程中整理了一张高频问题排查表直接对照着看能省不少时间问题现象根本原因解决办法前端请求后端接口 404路由前缀不匹配或蓝图未注册检查蓝图 url_prefix 和 app.register_blueprint跨域报错 No Access-Control-Allow-Origin未启用 CORS后端安装 flask-cors 并启用sqlite3.OperationalError: no such table未创建数据库表在应用启动时执行db.create_all()中文乱码数据库字符集问题MySQL 建库时指定 utf8mb4连接串加 charset 参数请求提交后数据没变化未调用 db.session.commit()检查事务是否提交前端路由刷新后 404Vue Router history 模式未配置回退后端加 catch-all 路由或改用 hash 模式第一个问题是我见过最多的。明明写了接口路径也没错但前端就是请求不到。十有八九是蓝图注册时漏了 url_prefix或者蓝图对象没加到 app 上。排查方法是启动 Flask 后在浏览器访问http://localhost:5000/api/entry看返回的是 JSON 还是 404很快就能定位。第二个问题是前后端分离项目的标配坑。我之前带人做项目对方把 flask-cors 装上了但忘了CORS(app)这一行排查半小时才发现。建议在写 app.py 的时候先把 CORS 配置好再写其他逻辑。还有一个隐蔽问题Flask 默认的端口是 5000但 macOS AirPlay 接收器也占着 5000 端口。我同事在 Mac 上跑 Flask 一直报Address already in use折腾半天才发现是这个原因。解决办法是启动时指定端口app.run(port5001)或者直接关闭系统服务。这个情况在写教程或看文档时很少被提到但实际开发中碰到的概率很高。5.2 一些值得记下来的实操心得第一接口返回格式要统一。我习惯用{code: 0, msg: 成功, data: {...}}的结构。code 为 0 表示成功非 0 表示各种错误。前端 axios 拦截器统一判断 code不用每个页面单独写异常处理。这个习惯后期扩展接口时收益很大前端轮询、定时刷新逻辑都会好写很多。第二数据库密码和密钥绝对不能写死在代码里。我用环境变量加载配置config.py里读取os.environ.get()。哪怕只是课设项目这个习惯一旦养成以后进公司工作就少一道踩坑。第三硬件对接的扩展思路。真实停车场的摄像头识别车牌的流程是摄像头抓拍 - 识别软件输出车牌号 - 调用系统接口录入。我在系统里做了一个模拟器用脚本定时随机生成车牌号调用入场接口用来测试高并发场景。如果你想对接真实的识别设备只需要在接口层多加一个鉴权参数防止外部设备随意调用接口。这个鉴权参数可以用设备编号加时间戳做签名逻辑不复杂但能挡住大部分乱调用。第四重新审视一下需求中的 Django 技术栈。如果你做的是一个需要复杂后台管理的项目Django 的 Admin 真的能省不少事。但停车场系统这种场景后台管理页面的复杂度远低于前台业务逻辑Flask 的灵活性更值钱。我把两者的对比写在前面了做选型时不要被技术栈越全越好绑架要看业务的实际需要。第五关于 PyCharm 的使用心得。社区版其实已经很好用了配置好虚拟环境解释器、开启自动保存再装一个 Romanian 风格的主题插件提升写代码的舒适度。调试时尽量用断点调试模式而不是在代码里到处 print。Flask 项目在 PyCharm 里配置运行配置时把 working directory 设成项目根目录环境变量里加FLASK_ENVdevelopment开启调试模式这样改代码后服务会自动重载不用手动重启。最后再说一个小技巧开发阶段数据库用 SQLite 确实方便但要记得在真正部署前完整测试一遍 MySQL 下的所有接口尤其注意大小写敏感和日期函数的差异。我遇到过 SQLite 下跑得好好的日期查询切到 MySQL 后语法不兼容的情况所以这种切换测试一定要提前做等上线了再发现就麻烦了。这个项目做下来我最大的体会是技术选型没有绝对的对错但一定要想清楚业务需要什么。Flask 的轻量让这个系统的每个接口都清晰可控Vue 的组件化让界面维护起来不费劲而 PyCharm 只是承载这一切的工具。抓住这些核心点你的停车场系统也能稳稳落地。
返回列表