资讯详情

停车场管理系统课程设计:从数据库设计到Java计费逻辑的完整实现

📅 2026/10/10 9:34:51 | 华诺云谱 👁 阅读
停车场管理系统课程设计:从数据库设计到Java计费逻辑的完整实现
简介一份面向高校计算机相关专业毕业设计或课程设计的停车场管理系统完整资料包。系统围绕车牌识别、车位管理、费用结算等核心流程展开覆盖前端页面、后端业务、数据库设计与支付接口集成具备无接触式车辆识别、预约车位、异常报警等能力适合具备 Java Web 基础、需要完成课程设计或毕设项目的开发者参考。压缩包共 592 个文件大小约 142.93MB包含 java/class 源码、xml 配置、jar 依赖、sql 数据库脚本、html/js 前端页面、jsp 页面等多种类型文件分类清晰便于按模块学习与检索。已有 2528 人学习下载包内可看到停车登记、文件上传、工具类等关键实现类有助于理解系统分层结构与业务逻辑基于这些代码还能快速梳理数据库表设计和后台接口调用关系为二次开发、文档编写及答辩演示提供直接参考。1. 毕业设计选停车场管理系统先想清楚这三件事停车场管理系统几乎是课程设计里最“安全”但也最容易做平庸的选题。我见过不少同学交上去的是一个能登录、能加车位的增删改查页面但被问到“同一辆车连续进出怎么计费”“跨天停车时长怎么算”“两个管理员同时入场会不会分到同一个车位”就答不上来。这套资源把这些环节串成一条完整链路把数据库设计、车辆进出登记、按时计费、车位状态流转都给出了可复现的代码和 SQL。它适合正在做课程设计或毕业设计、想快速搭出能演示系统的人。前置要求不高会一点 Java、能连上 MySQL 就能照着调。它会告诉你哪些地方必须写严谨哪些地方可以做得简单但不翻车避免最后答辩时被老师一问就卡壳。2. 从需求到数据库五张核心表和它们的关系怎么定拿到这类题目先把表建对。课程设计答辩至少一半时间在问“为什么这么设计”字段和关系能自圆其说后面写代码就是“对表操作”一旦表结构有问题后面每写一个功能都要回来改表越改越乱。2.1 需求拆解把“停车场管理系统”拆成三种角色两种车需求一般可以分成管理员、临时车、月卡车三条线。管理员负责登录、车位状态查看、收费规则设置和停车记录查询临时车走“入场登记-出场缴费”的完整闭环月卡车则多一个“会员有效期”的判断未到期按包月或包年计不按次收费到期后自动进入临时车流程。基于这个理解建表前可以先定几条业务规则后面写代码都围绕它们转车位是有限资源入场前必须确认空闲出场后必须释放。一次停车对应一条记录记录里包含入场时间、出场时间、停车时长、费用、状态。计费规则属于运营配置不能写死在页面逻辑里。月卡车辆按车牌识别课程设计层面不做“一卡多车”的扩展。规则定完表结构基本就出来了。不要一上来就设计七八张表课程设计的核心是能讲清楚、能演示而不是堆数量。2.2 五张核心表的结构与关联关系下面这五张表覆盖了停车场的核心业务没有多余的设计。先看一眼概览表名职责关键字段t_admin管理员/操作员账号username、passwordt_member月卡/年卡会员车plate_number、end_datet_parking_space车位area_code、statust_parking_record一次入场到出场的完整记录entry_time、exit_time、fee、statust_fee_rule计费规则car_type、unit_price、free_minutes为什么是这五张表而不是更多课程设计规模下把“入场记录”和“出场记录”合并成一张停车记录表比拆成两张表更维护车辆入场时插入一条 status0 的记录出场时只更新这一行报表统计也只需要扫这一张表不用来回 join。建表 SQL 如下CREATE DATABASE IF NOT EXISTS parking_system DEFAULT CHARACTER SET utf8mb4; USE parking_system; -- 管理员表课程设计可简化密码不能明文存 CREATE TABLE t_admin ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 月卡会员表一张卡绑定一个车牌 CREATE TABLE t_member ( member_id INT AUTO_INCREMENT PRIMARY KEY, plate_number VARCHAR(10) NOT NULL UNIQUE COMMENT 车牌号, member_type TINYINT NOT NULL DEFAULT 1 COMMENT 1月卡 2季卡 3年卡, start_date DATE NOT NULL, end_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0停用 ); -- 车位表 CREATE TABLE t_parking_space ( space_id INT AUTO_INCREMENT PRIMARY KEY, area_code VARCHAR(10) NOT NULL COMMENT 区域编码如A-01, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1占用 ); -- 停车记录表入场时插入出场时更新 CREATE TABLE t_parking_record ( record_id INT AUTO_INCREMENT PRIMARY KEY, plate_number VARCHAR(10) NOT NULL, car_type TINYINT NOT NULL DEFAULT 0 COMMENT 0临时 1月卡, space_id INT NOT NULL, entry_time DATETIME NOT NULL, exit_time DATETIME NULL, duration_min INT NULL COMMENT 停车时长(分钟), fee DECIMAL(10,2) NULL COMMENT 实际应收金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在场 1已离场, INDEX idx_space (space_id), INDEX idx_plate (plate_number) ); -- 计费规则表独立出来改价不用改代码 CREATE TABLE t_fee_rule ( rule_id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(50) NOT NULL, car_type TINYINT NOT NULL COMMENT 适用车型0临时 1月卡, unit_price DECIMAL(10,2) NOT NULL COMMENT 单位价格(元/小时), free_minutes INT NOT NULL DEFAULT 0 COMMENT 免费时长(分钟), max_daily_fee DECIMAL(10,2) NULL COMMENT 24小时封顶金额, is_active TINYINT NOT NULL DEFAULT 1 );两个关键点要说一下。第一fee 字段必须用 DECIMAL(10,2)不要用 FLOAT 或 DOUBLE。计费场景要的是十进制精确结果float 在金额计算上会出现 17.99999 这种数答辩现场被问到会很尴尬。第二t_parking_record 里的 status 是整张表的核心字段“重复出场”“重复缴费”“车位状态错乱”这类问题都靠它兜底后面写 Service 时会反复用到。另外我故意没有建物理外键space_id 只加了普通索引。课程设计阶段经常要手工清数据物理外键会在删除顺序不当时报错用逻辑外键配合代码检查就够了演示时少踩一个坑。注意车牌号字段长度给 10能覆盖新能源绿牌的 8 位汉字加字母组合如果是黄牌大车最多 9 位也够。长度不要省省了后面导入数据就会遇到麻烦。2.3 计费规则独立成表改价不用改代码很多课程设计会把“3 元一小时”直接写死在 Java 代码里这其实是个隐患。答辩老师大概率会追问一句“停车场调价了怎么办”如果回答“改代码重新编译”观感会差很多。常见做法是把计费规则放入 t_fee_rule 表系统启动时读取后台可以维护。插入两条初始规则INSERT INTO t_fee_rule (rule_name, car_type, unit_price, free_minutes, max_daily_fee, is_active) VALUES (临时车标准, 0, 3.00, 15, 30.00, 1); INSERT INTO t_fee_rule (rule_name, car_type, unit_price, free_minutes, max_daily_fee, is_active) VALUES (月卡车费率, 1, 0.00, 0, NULL, 1);临时车 3 元/小时进场前 15 分钟免费单日封顶 30 元月卡车按会员有效期计到期后走临时车逻辑。代码里只需要按 car_type 和 is_active 去匹配规则真正算钱时把规则对象传给计算函数。这里有两个边界需要提前确认。第一“免费 15 分钟”是按自然分钟数判断还是计费时“不足 1 小时直接抹掉首小时”两种规则算出来的费用不同建议在需求阶段就定死。第二“封顶价”是按自然日 0 点重置还是按入场时间起 24 小时重置这套资源实现的是“按入场时间起 24 小时滚动”更符合商业停车场习惯也更容易在答辩时解释清楚。3. 入场与出场核心链路把状态流转写成 Service业务代码的写法其实就一条线入场等于“查空闲车位 插记录 置占用”出场等于“查记录 算时长 算费用 释放车位”。下面代码按照最常见的 Spring Boot 加 MyBatis 分层写法给出类名做了简化核心看方法内部的顺序和状态处理。3.1 入场登记先查车位再写记录顺序别反入场接口是一个事务里做三件事任何一步失败都要回滚否则会留下脏数据。Transactional public Integer enterParking(EntryRequest req) { // 1. 先锁一个空闲车位status0 ParkingSpace space parkingSpaceMapper.selectOneAvailable(req.getAreaCode()); if (space null) { throw new BizException(当前区域没有空闲车位); } // 2. 写入一条在场记录status0 ParkingRecord record new ParkingRecord(); record.setPlateNumber(req.getPlateNumber()); record.setCarType(detectCarType(req.getPlateNumber())); record.setSpaceId(space.getSpaceId()); record.setEntryTime(LocalDateTime.now()); record.setStatus(0); parkingRecordMapper.insert(record); // 3. 把车位置为占用 parkingSpaceMapper.updateStatus(space.getSpaceId(), 1); return record.getRecordId(); }顺序不能反过来。如果先插记录再查车位会出现“有记录但没车位”的脏数据如果先改车位状态再插记录一旦插入失败车位就会被永久占用。三个操作包在 Transactional 里任何一步抛异常前面的 SQL 都会回滚数据一致性有保障。参数说明EntryRequest 只带两个字段plateNumber 必填、areaCode 选填选填时可以在所有空闲车位里任选一个。selectOneAvailable 的 SQL 建议加 FOR UPDATE或者用一个更简单的替代方案更新车位时写UPDATE t_parking_space SET status1 WHERE space_id? AND status0返回受影响行数为 0 就说明车位已经被人抢走了。课程设计里用后者更好讲也避免显式锁带来的死锁讨论。3.2 出场计费时长计算、费用计算、车位释放的固定顺序出场逻辑是整套系统里最容易写错的部分核心问题是“防止重复结算”。Transactional public ExitResult exitParking(Integer recordId, LocalDateTime exitTime) { // 1. 查出记录并校验状态 ParkingRecord record parkingRecordMapper.selectById(recordId); if (record null || record.getStatus() ! 0) { throw new BizException(停车记录不存在或已离场); } // 2. 计算停了多少分钟 LocalDateTime actualExit exitTime ! null ? exitTime : LocalDateTime.now(); if (actualExit.isBefore(record.getEntryTime())) { throw new BizException(出场时间不能早于入场时间); } long minutes Duration.between(record.getEntryTime(), actualExit).toMinutes(); // 3. 取计费规则并计算费用 FeeRule rule feeRuleMapper.findByCarType(record.getCarType()); BigDecimal fee calculateFee(record.getEntryTime(), actualExit, rule); // 4. 更新记录、释放车位 record.setExitTime(actualExit); record.setDurationMinutes((int) minutes); record.setFee(fee); record.setStatus(1); parkingRecordMapper.updateById(record); parkingSpaceMapper.updateStatus(record.getSpaceId(), 0); return new ExitResult(record); }第一步校验 status ! 0 能挡住绝大多数“前端连点两次”的重复缴费。更保险的写法是更新语句追加WHERE record_id ? AND status 0如果影响行数为 0说明已经被处理过直接提示“该车辆已离场”。Duration 是 JDK 8 以上的时间差 API比手写“天数乘 24 乘 60 加小时乘 60 加分钟”更不容易漏掉跨天场景。参数说明minutes 这里取的是“分钟数向下取整后直接展示”实际计费时按分钟向上取整由 calculateFee 处理避免出现“停了 1 分 01 秒账单显示 0 分钟”的笑话。exitTime 入参主要是给单元测试用的真实接口传 null 就会走系统当前时间。3.3 月卡车判断到期日和系统时间比较放 Service 不放 SQL月卡车的判断逻辑看着简单实际上一半的课程设计都栽在这里。很多同学喜欢在 SQL 里写WHERE end_date CURDATE()结果查出“无权限入场”前台根本分不清是“没会员”还是“会员过期了”。public Integer detectCarType(String plateNumber) { Member member memberMapper.selectByPlateNumber(plateNumber); if (member null) { return 0; } // 到期当天还能进次日凌晨起按临时车处理 if (member.getEndDate().isBefore(LocalDate.now())) { return 0; } return member.getMemberType(); }这段代码的判断逻辑放在 Service 层查出来会员信息后前台可以明确提示“您的月卡已到期本次按临时车计费”演示体验会好很多。返回 0 就是临时车返回 1/2/3 分别是月卡、季卡、年卡出场计费时只关心“是不是 0”来决定走哪条费率。这么做还有个好处如果后续想升级为“月卡超时另收临时费”只需要在出场计费里加一个判断分支不用改 SQL 和表结构。4. 课程设计高频踩坑现象、原因与排查顺序这部分是我带着这个题目看代码时最常碰见的问题基本都集中在时间、状态、数据清理三个方向。以下每条都是“现象 → 原因 → 解决”的现场还原答辩前建议照着检查一遍。4.1 时间计算的坑跨天停车与分钟精度第一个坑跨天停车时长少算一天现象车停了两天出场时费用只显示一天的金额或者时长直接变成负值。原因代码里用手写“日期字符串相减”或者用 float 算小时。跨天时“小时数”计算需要同时考虑日期部分和时分部分手写逻辑非常容易漏掉“日期差等于 1”的情况。解决统一用 java.time.LocalDateTime 加 Duration.between(...).toMinutes() 计算时长计费封顶用 24 小时滚动窗口。写完之后用一组跨天数据测试入场时间 23:50出场时间 00:20确认时间是 30 分钟而不是“负 23 小时”。第二个坑演示改系统时间导致“出场早于入场”现象答辩前把电脑时间改回前一天补数据随后演示出场时报“出场时间早于入场时间”。原因entry_time 取自服务器时间出场时又用当前时间时间轴被人工调整后出现倒挂而代码没有做防御性校验。解决在出场方法里先加if (actualExit.isBefore(record.getEntryTime()))的防御报错提示“系统时间异常请检查服务器时间”而不是去计算负数金额。演示环境里用“入场时间加预置时长”来模拟离场也能规避这个问题。4.2 状态与并发重复出场与车位重复分配第三个坑重复点“出场结算”生成两条费用现象前端按钮没有做 loading快速点了两下同一辆车在记录表里出现两条出场记录车位被释放了两次。原因出场接口没有状态校验两次请求都按“在场记录”处理。解决停车记录加 status 字段出场更新时用UPDATE t_parking_record SET status1 ... WHERE record_id? AND status0第二次执行影响行数为 0直接提示“该车辆已离场”。前端按钮层再做个防重复提交双保险。第四个坑两个管理员同时入场分到同一个车位现象演示两台电脑同时给不同车辆入场系统提示成功但库里两条记录挂了同一个 space_id。原因入场时先 SELECT 空闲车位再 UPDATE 为占用两步之间没有原子性两个请求都查到了同一个空闲车位。解决最简单且容易在答辩中解释清楚的做法是更新车位时显式带条件UPDATE t_parking_space SET status1 WHERE space_id? AND status0如果影响行数为 0重新选车位或直接提示无车位。把这条 SQL 写在 Service 代码里比给整个流程加悲观锁更好讲。4.3 数据清理与演示复位第五个坑清空停车记录后所有车位还是“占用”现象演示前想重置数据手工 DELETE 了 t_parking_record界面里车位仍是红色占用进不了场。原因车位表 status 没有被同步重置记录表和车位表的状态脱节了。解决清数据时按固定顺序来先清记录再重置车位或者写一条一键重置的脚本演示前执行一次-- 一键重置演示数据先删记录再释放所有车位 DELETE FROM t_parking_record; UPDATE t_parking_space SET status 0;如果项目里做了“删除单条出场记录”的功能也要在 Service 里同步把对应车位 status 改回 0否则会出现和这条一模一样的现象。注意这是演示环境专用写法别加到生产环境里。5. 答辩前最该做的一件事把计费逻辑抽成纯函数很多同学把计算费用的代码写在 Controller 或 Service 的 if-else 里演示时能跑但答辩老师问“跨天怎么算免费时段怎么减”就得临时翻代码。我一般会把 calculateFee 抽成一个不依赖数据库的 Java 静态方法入参只有入场时间、出场时间、计费规则返回金额。public static BigDecimal calculateFee(LocalDateTime entry, LocalDateTime exit, FeeRule rule) { if (exit.isBefore(entry)) { throw new IllegalArgumentException(出场时间早于入场时间); } long minutes Duration.between(entry, exit).toMinutes(); if (minutes 0) { minutes 1; // 不足1分钟按1分钟计 } if (minutes rule.getFreeMinutes()) { return BigDecimal.ZERO; } // 免费时长之外的部分按小时向上取整 long billableMinutes minutes - rule.getFreeMinutes(); long billableHours (billableMinutes 59) / 60; BigDecimal fee rule.getUnitPrice().multiply(BigDecimal.valueOf(billableHours)); if (rule.getMaxDailyFee() ! null fee.compareTo(rule.getMaxDailyFee()) 0) { fee rule.getMaxDailyFee(); } return fee.setScale(2, RoundingMode.HALF_UP); }这个函数没有任何数据库操作可以脱离项目单独测试。参数 rule 是从 t_fee_rule 查出来的对象unitPrice 用 BigDecimal计算过程不做 Double 转换不会出现金额浮点误差。拿到费用之后再回到之前的出场方法里写回记录并释放车位就完成了完整的出场链路。我习惯在本地写一个只有 main 方法的测试类输入三组用例入场 23:50 出场 00:20、入场 00:00 出场 00:10、入场 08:00 出场 11:01分别验证跨天、免费时段、向上取整这三个边界。答辩时打开测试类跑一遍比现场编数据稳定得多。从那以后我每次做带计费的系统都强制把核心算法先写成不依赖数据库的纯函数再往外接接口。这套资源里的项目已经把上述要点做成了可运行的完整工程下载后改一下数据库连接配置就能跑起来希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑