ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

公司用车管理系统Java Web大作业:数据库设计、权限拦截与避坑指南

公司用车管理系统Java Web大作业:数据库设计、权限拦截与避坑指南 简介一份基于Java开发的公司用车管理系统设计源码源自南昌航空大学软件学院21级Web大作业面向高校学生、Java初学者及需要快速搭建管理系统的开发者用于学习Web项目分层结构、业务逻辑与数据库设计。压缩包共175个文件约914KB核心以Java源代码、HTML页面、JavaScript脚本和CSS样式表为主兼有XML配置文件、JSP页面、SQL脚本、PNG图片及Markdown文档等涵盖前端展示、后端控制、持久化映射及数据库初始化等多层资源。目前已有343人浏览学习。其中包含申请用车、司机请假、调度派车等业务模块以及新增表、数据库视图代码和SQL语句等数据库设计文档方便对照理解表结构、视图与接口设计进而掌握从需求分析到代码落地的完整过程完整的目录结构与多种辅助文件也便于直接导入项目进行二次开发或课程答辩演示。1. 公司用车管理系统这类Java web大作业为什么值得认真做公司用车管理系统在南昌航空大学软件学院21级这类web大作业里出现频率很高。题目表面是管车实际要把Java Web课程最该考的几样东西全串起来登录会话、多角色权限、带状态流转的业务单以及车辆、司机这些基础数据的维护。对还在补java基础、又要在截止日期前交出一份能跑、能讲、经得起提问的源码的人这类项目比商城系统少一半业务噪音却比纯增删改查多一层审批状态机性价比很高。这篇就按一套可运行的web项目源码来拆数据库怎么设计、前后端怎么写、权限怎么拦以及提交前最容易翻车的几个点。适合软件工程大作业、Java web课程设计参考。2. 业务建模与技术选型公司用车管理系统设计源码从哪几块开始拆拿到这种题第一反应不要是打开IDE建项目而是先把现实里的公司用车流程走一遍。员工想用车得先填申请写明时间、地点、事由审批人看完决定放不放行放行之后管理员要派一辆车出去车回来后要登记里程和状态。把这个流程映射成系统里的用例和状态字段就成功了一多半。2.1 角色与用例先别急着写代码把三种人的权限边界画出来大多数公司用车管理系统源码会收敛成三个角色既覆盖考察点又不失控角色能做什么不能做什么员工EMPLOYEE提交用车申请、取消自己的待审批申请、查看自己的历史记录不能审批、不能看到车辆管理菜单审批人APPROVER查看待审批清单、通过或驳回并填写意见不能派车、不能修改车辆信息管理员ADMIN车辆/司机/用户维护、派车、归还登记、查看全部记录与统计规范设计里不替员工填申请单有些版本会额外做一个司机角色我一般不建议在大作业里加。角色一多拦截器、菜单渲染、会话字段全都要多一套分支答辩时老师还会问司机为什么不能由普通员工兼任。把司机做成一张基础表的字段由管理员派车时选择一个关联司机已经足够体现多表关联这个考察点。用例清单建议控制在11条左右登录/退出、提交用车申请、取消未审批申请、审批通过/驳回、车辆信息维护、司机信息维护、派车、归还登记、我的申请记录、全部记录查询、统计报表。把这张清单贴进需求文档答辩时一页就能讲清系统边界。很多同学上来就做注册、忘记密码把最核心的审批流程压缩得可怜。大作业的评分点通常不在功能数量而在主流程能不能完整闭环。2.2 数据库设计6张表的字段、状态值和常见约束我一般按用户-车辆-司机-申请单-审批记录-用车记录拆成6张表。不追求范式多漂亮但求能支撑主流程和答辩。建表SQL常用做法如下CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码演示可用MD5推荐BCrypt, real_name VARCHAR(50) NOT NULL, dept_name VARCHAR(50) DEFAULT 综合部 COMMENT 部门, role VARCHAR(20) NOT NULL COMMENT ADMIN/APPROVER/EMPLOYEE, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;role字段用字符串而不是整数判断时语义直观写user.getRole().equals(ADMIN)比role 1好懂得多答辩也更好解释。password建议至少用MD5不让明文入库如果自己熟悉可以用BCrypt老师随口问密码怎么存的时这道题就加分了。CREATE TABLE vehicle ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) NOT NULL UNIQUE COMMENT 车牌号, brand VARCHAR(50), model VARCHAR(50), seat_count INT DEFAULT 5, status TINYINT DEFAULT 0 COMMENT 0空闲 1使用中 2维修, remark VARCHAR(255) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE driver ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20), license_type VARCHAR(20) DEFAULT C1, status TINYINT DEFAULT 1 COMMENT 1可出车 0休息 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE vehicle_apply ( id INT PRIMARY KEY AUTO_INCREMENT, apply_no VARCHAR(32) NOT NULL COMMENT 单号日期自增序列, user_id INT NOT NULL, vehicle_id INT COMMENT 申请时可不选派车时再定, driver_id INT, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, start_place VARCHAR(100), end_place VARCHAR(100), purpose VARCHAR(255) COMMENT 事由, passenger_num INT DEFAULT 1, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审批 1已通过待派车 2已派车 3已完成 4已驳回 5已取消 6已过期, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_apply_user (user_id), KEY idx_apply_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE approval_log ( id INT PRIMARY KEY AUTO_INCREMENT, apply_id INT NOT NULL, approver_id INT NOT NULL, action TINYINT NOT NULL COMMENT 1通过 2驳回, opinion VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE use_record ( id INT PRIMARY KEY AUTO_INCREMENT, apply_id INT NOT NULL, vehicle_id INT NOT NULL, driver_id INT, actual_start_time DATETIME, actual_end_time DATETIME, actual_mileage DECIMAL(10,2) COMMENT 实际里程, fee DECIMAL(10,2) DEFAULT 0 COMMENT 费用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段设计里有几个细节容易被忽略。apply_no一定要给唯一单号yyyyMMddHHmmss加三位随机数也行否则列表页、报表页很难对一张申请的生命周期做追溯。vehicle_id允许为空因为员工提交申请时不一定知道哪个车可用由管理员在派车阶段再指定这样申请单从待审批到已完成每一步都不缺数据。status枚举值统一维护在一张常量表或注释里很多大作业翻车就因为这个字段的中文含义换一个页面就换一套说法。外键我一般只建普通索引不建物理外键。原因有两个大作业数据量小物理外键的约束意义有限而且删除车辆、删除用户时物理外键会直接拦住而逻辑上你更希望系统提示该车辆有未完成申请不能删除而不是抛一个数据库异常。靠程序控制关联答辩反而能讲出业务完整性在Service层维护这句话。2.3 技术选型Java web大作业为什么默认走Spring Boot MyBatis MySQL常见做法是Maven Spring Boot 2.x MyBatis MySQL JSP打成war包部署到Tomcat。相比传统ServletJSPSpring Boot省掉大量web.xml配置MyBatis代理接口让SQL完全在手边相比Spring Cloud那一套又不会把大作业推成微服务。网上这类java课程设计案例源码数量多遇到问题能搜到答案选这个组合不算炫技但足够经典。如果学校没有强制要求SSH我建议别用StrutsSpringHibernate。Struts的拦截器链和Hibernate的懒加载问题对只有几周时间的大作业来说性价比很低。数据访问层用MyBatis而非JPA也同理JPA自动生成的SQL对新手像个黑匣子答辩时老师问这条查询走了哪几张表MyBatis XML里一眼就能指出来。这是很现实的选型逻辑不是鄙视链。目录结构常用做法如下src/main/java/com/example/car/ ├── controller/ # MVC控制层只做参数接收和跳转 ├── service/ # 业务层申请、审批、派车、归还 ├── mapper/ # MyBatis Mapper接口 ├── interceptor/ # 登录与角色拦截器 ├── entity/ # 实体类 └── config/ # WebMvcConfig等配置类 src/main/resources/ ├── mapper/ # MyBatis XMLSQL集中在这里 └── application.yml src/main/webapp/ ├── WEB-INF/jsp/ # 页面放WEB-INF下禁止直接URL访问 └── static/ # css/js/images把页面放在WEB-INF/jsp下是最容易做到却被忽略的安全习惯。不放在WEB-INF下用户直接访问/user_list.jsp能猜出路名就等于裸奔。放在WEB-INF里页面只能通过Controller转发访问拦截器才有机会先执行。3. 后端核心链路登录、申请、审批怎么写成可维护的代码3.1 项目骨架与pom.xml依赖别多够用就好Spring Boot的依赖管理比较省心但大作业常见的坑反而是该配的没配不该引的引了一堆。核心pom.xml片段如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-jasper/artifactId scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId /dependency /dependencies packagingwar/packaging几个参数说明spring-boot-starter-web是基础含Spring MVC和Tomcatmybatis-spring-boot-starter要注意版本和Spring Boot 2.7适配2.3.2是常用稳定版本tomcat-embed-jasper加provided是JSP编译必需不配这个依赖页面访问时会直接白屏或404最终packaging设为war方便部署到外部Tomcat也方便老师直接用IDEA以war exploded方式运行。application.yml配置server: port: 8080 servlet: context-path: /car encoding: charset: UTF-8 enabled: true force: true spring: datasource: url: jdbc:mysql://localhost:3306/car_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.car.entity configuration: map-underscore-to-camel-case: truecontext-path: /car表示整个应用挂在/car下页面所有链接都要带这个前缀我统一用${pageContext.request.contextPath}生成防止硬编码写死。view.prefix/suffix和tomcat-embed-jasper要同时存在缺一不可。map-underscore-to-camel-case开启后plate_no自动映射成plateNo实体类能少写一大堆手动映射。3.2 实体类与统一返回写Mapper之前先定好R对象Controller、Service、JSP之间传数据很多大作业是每个接口各传各的。统一用一个R对象包一层前端取值字段固定答辩时讲统一返回结构也是加分点。public class R { private int code; // 0成功 1失败 private String msg; // 提示信息 private Object data; // 携带的数据 public static R ok(Object data) { R r new R(); r.code 0; r.msg success; r.data data; return r; } public static R fail(String msg) { R r new R(); r.code 1; r.msg msg; return r; } // getter / setter 省略 }code约定0成功、1失败不要学后端项目里塞一堆200/500自定义码大作业场景没人会消费那么细。Ajax提交后前端判断data.code 0即可配合msg弹出错误调试时非常直观。实体类以申请单为例public class VehicleApply { private Integer id; private String applyNo; private Integer userId; private Integer vehicleId; private Integer driverId; private Date startTime; private Date endTime; private String startPlace; private String endPlace; private String purpose; private Integer passengerNum; private Integer status; // 0待审批 1已通过待派车 2已派车 3已完成 4已驳回 5已取消 6已过期 private Date createTime; // 以下非表字段关联查询时使用 private String userName; private String plateNo; private String statusDesc; }时间类型用java.util.Date就好配合数据库datetime前端用JSTLfmt:formatDate格式化。想用LocalDateTime可以但和MyBatis、JSON的类型映射多出配置不是必需。statusDesc在Mapper XML里用case when按status生成中文比在JSP里写一串if/else干净很多。3.3 用车申请与审批状态机一个status字段如何保证流程不乱申请单的核心是状态机。状态流转必须固化在Service里不能在Controller里到处setStatus。我维护过的很多源码翻车都是同一个状态字段在三个Controller里各写了一套流转最后数据一团乱。Service public class VehicleApplyService { Autowired private VehicleApplyMapper applyMapper; Autowired private ApprovalLogMapper approvalLogMapper; Transactional(rollbackFor Exception.class) public void submit(VehicleApply apply, Integer userId) { apply.setUserId(userId); apply.setStatus(0); // 新建单待审批 apply.setApplyNo(generateNo()); // 生成唯一单号 applyMapper.insert(apply); } Transactional(rollbackFor Exception.class) public void approve(Integer applyId, Integer approverId, String opinion, boolean pass) { VehicleApply apply applyMapper.selectById(applyId); if (apply null) { throw new BusinessException(申请单不存在); } // 只允许待审批状态进入审批逻辑其他状态一律拒绝 if (apply.getStatus() ! 0) { throw new BusinessException(当前状态不能审批); } int nextStatus pass ? 1 : 4; // 1已通过待派车4已驳回 applyMapper.updateStatus(applyId, nextStatus); ApprovalLog log new ApprovalLog(); log.setApplyId(applyId); log.setApproverId(approverId); log.setAction(pass ? 1 : 2); log.setOpinion(opinion); approvalLogMapper.insert(log); } }approve方法前半段是典型的状态校验落库追加日志。校验status ! 0看起来多余但审批人在两个窗口各点一次通过时第二次进入会因为状态不再是0而抛异常避免重复审批把记录改出脏数据。给approval_log插入日志是必须项答辩时审批留痕基本会被问到有这张表就能现场演示。Transactional(rollbackFor Exception.class)建议写全不写的话Spring默认只在RuntimeException时回滚Service里抛检查异常时容易造成状态更新成功但日志没写入的脏数据。generateNo我习惯返回日期时间加三位随机数并发时概率撞车靠数据库唯一索引兜底。派车和归还也是同样的套路派车要求status为1归还要求status为2归还时顺带把vehicle表的status更新为0并插入use_record。这三步必须在同一个事务里否则会出现申请单已完成但车辆还显示使用中的经典脏数据。把三张表的状态更新写进一个Service方法是我给这类系统定的硬性要求。4. 前端与权限控制JSP页面怎么和三个角色联动对web前端开发不熟的同学大作业的前端最容易做成每页复制一份导航栏。其实用include和拦截器就能把重复的事情一次做完页面还会显得很统一。4.1 登录会话与公共布局用include把导航抽出来登录成功后的会话设计很简单session里放一个user对象JSP页面用${sessionScope.user.realName}显示当前登录人。真正的问题在菜单管理员能看到车辆管理、司机管理、派车、统计员工只能看到我要用车、我的申请。如果每个页面都写死菜单后面加一个角色就要改几十个页面。%-- top.jsp 放在 /WEB-INF/jsp/common/ 下 --% % page contentTypetext/html;charsetUTF-8 % div classnavbar span classbrand公司用车管理系统/span ul c:if test${sessionScope.user.role ADMIN} lia href${pageContext.request.contextPath}/admin/vehicle/list车辆管理/a/li lia href${pageContext.request.contextPath}/admin/driver/list司机管理/a/li lia href${pageContext.request.contextPath}/admin/apply/dispatch派车处理/a/li /c:if c:if test${sessionScope.user.role APPROVER} lia href${pageContext.request.contextPath}/approver/apply/list审批中心/a/li /c:if c:if test${sessionScope.user.role EMPLOYEE} lia href${pageContext.request.contextPath}/employee/apply/add申请用车/a/li lia href${pageContext.request.contextPath}/employee/apply/my我的申请/a/li /c:if /ul span你好${sessionScope.user.realName}/span /div菜单用c:if按角色渲染而不是把三个角色的菜单全显示再靠CSS隐藏后者在页面源码里能被直接看到安全上很减分。${pageContext.request.contextPath}取代硬编码的/car之后换context-path时全部链接不需要改。业务页面只写% include file/WEB-INF/jsp/common/top.jsp %下面接自己的内容区最后再include一个bottom.jsp统一放底部和js公共引用。静态文件也走${pageContext.request.contextPath}/static/...不要写相对路径../css/在两级目录的页面下相对路径会算错这个翻车非常高频。4.2 基于拦截器的权限控制不用Spring Security也能守住后台大作业里引Spring Security容易把自己绕进去BCrypt、角色继承、CSRF配置每一个都要学核心业务还没写完就被安全框架卡住。常见做法是自己写一个HandlerInterceptor十几行代码解决登录校验和角色匹配。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object user session.getAttribute(user); String uri request.getRequestURI(); String contextPath request.getContextPath(); String path uri.substring(contextPath.length()); // 登录页、登录接口、静态资源放行 if (/login.equals(path) || /doLogin.equals(path) || path.startsWith(/static/)) { return true; } if (user null) { response.sendRedirect(contextPath /login); return false; } User loginUser (User) user; String role loginUser.getRole(); // 按URL前缀粗粒度拦截精细判断丢给业务层 if (path.startsWith(/admin/) !ADMIN.equals(role)) { response.sendError(403); return false; } if (path.startsWith(/approver/) !APPROVER.equals(role)) { response.sendError(403); return false; } return true; } }注册拦截器Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /doLogin, /static/**); } }大作业的路径设计从一开始就按/admin/**、/approver/**、/employee/**来分拦截器就能用URL前缀做粗粒度权限控制。比如员工拼接/admin/vehicle/list在拦截器就被403挡下根本进不到Controller。这也要求Controller的方法映射严格按角色前缀划分不能图省事全部写到/apply/**下否则拦截规则会很别扭。excludePathPatterns里必须放行/static/**否则页面引用的CSS、JS全被拦掉登录页样式全丢这也是个高频排查点。另外细粒度控制不能只靠URL前缀。员工虽然进不了/admin/**但还是可以猜接口手动提交applyId18passtrue。审批接口里必须再加一层数据权限判断比如审批人只能处理待审批单且approverId和自己匹配。拦截器只做能不能进这个模块Service层再做能不能动这条数据两层一起才完整。4.3 表单校验用车时间冲突和起止时间倒挂在前端先拦一道用车申请最典型的错误是结束时间早于开始时间以及同一辆车被两个申请单占用。前者纯前端校验就能挡住function checkForm() { const start document.getElementById(startTime).value; const end document.getElementById(endTime).value; if (!start || !end) { alert(请填写完整的用车时间); return false; } if (start end) { alert(结束时间必须晚于开始时间); return false; } const purpose document.getElementById(purpose).value.trim(); if (purpose.length 5) { alert(请至少填写5个字的事由); return false; } return true; }时间字段建议直接用input typedatetime-local浏览器自带格式校验。但同一辆车的时间冲突不能只在前端做因为当前页面查不了数据库。这个校验放在Service的insert之前Integer count applyMapper.countOverlap(vehicleId, startTime, endTime); if (count ! null count 0) { throw new BusinessException(该车辆在所选时间段已有申请); }countOverlap的SQL是时间重叠判断的核心SELECT COUNT(*) FROM vehicle_apply WHERE vehicle_id #{vehicleId} AND status IN (0, 1, 2) AND start_time #{endTime} AND end_time #{startTime}判断逻辑就是新申请的开始时间早于已有申请的结束时间且新申请的结束时间晚于已有申请的开始时间。别忘了状态限定为IN (0,1,2)已驳回、已取消的单子不参与冲突。SQL里的startTime和endTime格式要统一用Date或格式一致的String都行否则比较结果会很诡异。数据库层面加普通索引就够大作业不需要锁表那么重的方案。5. 避坑指南Java web大作业提交前必查的5个翻车点这些坑不分成绩好坏几乎是每届都会出现的而且全部发生在能启动但是打不开、数据乱、删不掉这种最磨人的阶段。对照检查能省下半天调试时间。5.1 java环境变量配置IDEA能跑命令行却报错现象IDEA里项目能正常启动但换台电脑用命令行打包或者部署到Tomcat提示java不是内部或外部命令Maven也报找不到JAVA_HOME。原因IDEA内置了JDK开发时感受不到系统环境变量的问题。课程设计提交时老师很可能让你在命令行执行java -version或者用mvn clean package打war包只要没配好系统PATH和JAVA_HOME就当场翻车。解决确认JDK安装路径后配置两个环境变量JAVA_HOME D:\dev\jdk1.8.0_202以真实路径为准PATH里追加%JAVA_HOME%\bin不要写jre/bin。配置完重新打开命令行执行java -version和javac -version都正常才算过。环境变量路径不要出现中文和括号有人装到C:\Program Files (x86)\JavaTomcat脚本解析时就会出问题统一装到D:\dev这种干净目录更省心。这是java基础里最不起眼、但提交前最致命的一环。5.2 MySQL 8驱动类名与时区参数连接数据库直接抛异常现象项目启动报Public Key Retrieval is not allowed或者控制台出现The server time zone value ... is unrecognized。原因MySQL 8的驱动类名不再是com.mysql.jdbc.Driver而是com.mysql.cj.jdbc.Driver同时MySQL 8连接需要明确时区否则驱动无法解析默认时区。allowPublicKeyRetrieval在MySQL 8使用caching_sha2_password认证时必须设置。解决application.yml里改成spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/car_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSL在本地开发建议加false避免一堆SSL握手日志serverTimezone用Asia/Shanghai而不是GMT%2B8后者写进yml还要转义容易踩坑。数据库建库时就写DEFAULT CHARSETutf8mb4连接URL再加characterEncodingutf8车辆备注里的中文才能正常显示。5.3 中文乱码页面、请求、数据库三层都检查一遍现象页面显示稦车数据库里存的也是乱码或者查询条件输入中文查不到数据。原因JSP页面编码、服务器接收请求编码、数据库连接编码、表字符集四个环节只要有一处不是UTF-8整条链路就乱。最常见的两个独立变量是JSP没写contentType和POST请求没设置request编码。解决JSP页面第一行统一加% page contentTypetext/html;charsetUTF-8 languagejava %Spring Boot里已经通过server.servlet.encoding.force: true强制请求和响应编码。如果是传统ServletTomcat项目在web.xml里配一个CharacterEncodingFilterfilter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter排查顺序先看数据库表字符集再看连接URL最后看页面声明。三层统一后乱码基本消失。数据库工具里能看到正常中文不代表程序里正常程序用的字符集由连接URL决定这一段保命经验值得记住。5.4 删除车辆被外键拦住物理外键的代价现象删除一个没有关联订单的车辆控制台却抛Cannot delete or update a parent row: a foreign key constraint fails。原因建表时给vehicle_apply加了物理外键FOREIGN KEY。即使相关申请已被驳回只要外键索引还在删除车辆就会触发约束。解决两个方向选其一。一是把物理外键改成程序控制删除前先查该车辆是否存在关联申请存在就提示该车辆存在关联申请不能删除二是删除前手写SQL把关联表的vehicle_id置空。我推荐前者因为能顺便把车辆在申请单里以历史快照方式留存这个概念讲出来。如果一定要保留物理外键可以把车辆表改成逻辑删除增加deleted字段删除操作变成UPDATE查询默认过滤这个设计在答辩里很加分。5.5 JSP访问404或样式全丢WEB-INF目录和contextPath的坑现象一部署后访问首页正常点击某个按钮提交表单跳转时404控制台没有报错。现象二登录页正常显示但完全没有CSS样式浏览器F12里一堆js/css请求404。原因404多半是页面放在WEB-INF外且Controller跳转路径写错或者链接里没带contextPath跳转到了/login而不是/car/login。样式全丢则是资源路径用了相对路径css/style.css页面URL变深一级后相对路径就不对了。解决JSP统一放WEB-INF/jsp下Controller返回视图名时只写逻辑名例如employee/apply/add前缀后缀由Spring Boot配置补齐。资源引用统一用${pageContext.request.contextPath}/static/css/style.css。登录成功后重定向写response.sendRedirect(request.getContextPath() /employee/apply/my)所有重定向都带contextPath。排查顺序是先看地址栏URL对不对再看F12里失败的资源URL基本一眼能定位。6. 让源码变成能答辩的作品演示数据、README和三个必答点代码能跑只是及格线。老师拿到源码第一件事是跑起来第二件事是看你能不能讲清楚。把下面三件事做完普通项目也会显得像正式作品。先造演示数据。至少准备5个用户、5辆车、3个司机申请单要覆盖所有状态待审批、已通过、已驳回、已完成、已取消。审批记录要对得上申请单派车和归还记录要完整。演示账号可以参考这张表账号密码角色能演示什么adminadmin123ADMIN车辆管理、派车、统计approverapprover123APPROVER审批通过/驳回zhangsan123456EMPLOYEE提交申请、取消造数据时最容易被忽视的是时间。所有申请时间落在答辩前一周内统计报表里的近7天用车情况才有效按月度统计也才有柱状图可看。README不用写长篇论文式背景环境要求JDK8、Maven 3.6、MySQL 8、初始化步骤建库、执行sql脚本、改数据库密码、演示账号、技术栈和包结构够了。再附两张图一张ER图、一张申请单状态流转图答辩时直接指着讲。三个必答点提前练熟一是申请单状态如何流转把0到6的枚举含义背下来二是为什么用MyBatis不用JPA答SQL在手边能讲清查询走了哪张表三是权限怎么控制分两层说拦截器按URL前缀拦模块Service层按数据归属拦操作。这套流程理清楚之后java面试八股文里常问的事务失效、状态机设计也都有了实际例子可以佐证。我做这类Java web大作业的源码辅导时见过太多次功能做完了但讲不清楚的案例。后来养成一个习惯代码写完先不看控制台而是模拟老师拿到项目从README开始一步步走把每个按钮点开看看有没有死链。很多翻车在提交前半小时就能发现缺的往往就是这次以老师的视角验收的预演。希望帮到你。本文还有配套的精品资源点击获取
返回列表