资讯动态

JSP+Servlet+JavaBean实现车险模拟系统:保费计算与理赔状态机

发布时间:2026/9/12 6:46:59 来源:尧图企业网站定制
简介这份资源面向计算机及相关专业毕业生是基于 JSP 的车险模拟系统毕业设计完整源码覆盖保险报价、保费计算、理赔处理等核心业务项目采用 MVC 架构结合 Servlet、JavaBean 和数据库设计包含用户管理、表单校验、权限控制与安全防护等模块适合课程设计或毕业设计参考及二次开发。 压缩包共 950 个文件约 22.48MB主要包含 87 个 JSP 页面、21 个 Java 源文件、21 个 class 文件、70 个 jar 依赖库、20 个 XML 配置以及 HTML、CSS、JavaScript 等前端静态资源附带 SQL 数据库脚本和 MyHibernate/Spring 配置文件目录结构层次清楚便于按模块查阅和学习。目前已有 102 人参与浏览学习。 借助这套源码读者可以快速搭建可运行环境理解车险业务系统中报价、投保、理赔等流程的设计与实现思路掌握 JSP 动态页面开发、MVC 分层架构、数据库建模、事务处理和安全防护等关键技能。同时资源还覆盖了测试、部署与性能优化等实践要点是一份兼具教学参考与工程实战价值的毕业设计参考资料。1. 从毕业设计标题出发的系统定位“基于JSP的车险模拟系统”这类题目在毕业设计里出镜率不低但多数实现只是把增删改查搬进网页答辩时老师一句“商业险保费里的费率浮动表放在哪里算的”就答不上来。真正的车险模拟系统核心不是“录数据”而是要用代码模拟出保险业务里的两套关键规则保费计算规则和理赔状态推进规则。前者是精算模型的简化版后者是业务流程的状态机。本文按 JSP Servlet JavaBean 的经典三层结构把一张保单从创建、报价到出险理赔的完整链路讲清楚附带可复用的建表语句、JDBC接入代码和页面实现思路给做毕设的同学一条能跑通、能解释、能抗提问的实现路径也给想快速上手 JSP 全栈开发的读者一份可参考的落地样板。2. 技术选型与MVC分层JSP Servlet JavaBean的职责边界2.1 为什么这个题目不必硬上 Spring 框架毕设选型的原则是“够用且能自证”。车险模拟系统的业务量不大用户并发极低核心是业务逻辑的表达上 Spring Boot 反而会让老师把提问重点移到“自动配置、IOC 原理”等框架机制上偏离车险业务本身。JSP Servlet JavaBean 是 Java Web 课程里的标准答案三个部件刚好对应 MVC 三层。层次组件职责对应包名/目录展示层JSP表单收集、数据回显、页面跳转WebContent/jsp/控制层Servlet参数接收、调用业务层、转发重定向src/controller/业务层JavaBean保费计算、理赔状态机、数据校验src/service/数据层JavaBean JDBC数据库访问、结果集封装src/dao/这里没有给数据层单独造术语JavaBean 既可以做业务对象也可以承载 DAO 职责毕设里不引入 MyBatis 也完全说得通。关键是让每个类的名字能直接看出它属于哪一层包名清晰web.xml 里配好 Servlet 路径代码质量这一项的答辩分就能拿住。2.2 项目目录结构与关键配置一个能直接导入 Eclipse 或 IDEA配置 Tomcat 运行的项目结构长这样CarInsuranceSimulator/ ├── src/ │ ├── model/ // 实体Bean: Vehicle, Policy, ClaimRecord │ ├── dao/ // 数据访问: VehicleDao, PolicyDao, ClaimDao │ ├── service/ // 业务逻辑: PremiumCalculator, ClaimService │ ├── controller/ // Servlet: LoginServlet, QuoteServlet, ClaimServlet │ └── util/ // 工具类: DBUtil, DateUtil ├── WebContent/ │ ├── jsp/ // 页面: login.jsp, quote.jsp, policyList.jsp │ ├── css/ // 样式文件 │ ├── static/ │ └── WEB-INF/ │ └── web.xml // Servlet 3.0 配置WebContent 目录下放的是 Tomcat 直接对外暴露的静态资源和 JSP 页面WEB-INF 下的 web.xml 是容器启动时加载的主配置。页面文件通常不直接放进 WEB-INF 里因为放在 WEB-INF 下只能通过 forward 访问虽能防止直接 URL 访问页面但开发调试时反而麻烦。毕设项目更建议 JSP 放 jsp 目录下用 Filter 做访问控制这样页面改完刷新就能看到效果。2.3 web.xml 中 Servlet 映射与初始化参数的写法web.xml 是 JSP 项目的脊柱Servlet 3.0 支持注解方式但毕业设计里把映射写进 web.xml 更利于答辩展示配置逻辑。以报价请求为例?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 display-nameCarInsuranceSimulator/display-name !-- 编码过滤器统一请求与响应编码 -- filter filter-nameEncodingFilter/filter-name filter-classutil.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- 登录拦截过滤器 -- filter filter-nameLoginFilter/filter-name filter-classutil.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/jsp/*/url-pattern /filter-mapping !-- 报价Servlet -- servlet servlet-nameQuoteServlet/servlet-name servlet-classcontroller.QuoteServlet/servlet-class load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameQuoteServlet/servlet-name url-pattern/quote/url-pattern /servlet-mapping welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list /web-app这段配置里有三个细节值得答辩时展开。第一是 filter-mapping 的 url-pattern 用了/jsp/*表示只拦截页面请求不做 API 级权限控制简单项目这样写足够。第二是 load-on-startup 为 1 时Servlet 在 Tomcat 启动时就完成初始化避免首次访问时编译 Servlet 带来的延迟。第三是编码过滤器必须放在所有过滤器最前面否则后面读到的请求参数还是 ISO-8859-1 编码中文直接乱码。乱码问题在 JSP 项目中占排障时间的三成以上把编码过滤器写在最上面是第一个经验点。请求流转到这里已经清晰浏览器发起/quote请求web.xml 将 URL 交给 QuoteServletServlet 调用 service 层计算保费结果放到 request 域并 forward 到 quoteResult.jspJSP 通过 EL 表达式取出数据渲染页面。三层之间的调用边界明确答“一次请求经历了什么”时按这个链路说就是满分回答。3. 数据库设计与JDBC封装车险核心数据的表结构与连接管理3.1 保单、车辆、理赔三张核心表的字段设计车险系统绕不开一张保单的完整生命周期先有车辆信息然后投保生成保单出险后创建理赔单。数据库设计围绕这三个对象展开字段要为保费计算和理赔金额计算服务而不是简单堆字段。CREATE DATABASE IF NOT EXISTS car_insurance DEFAULT CHARACTER SET utf8mb4; USE car_insurance; -- 车辆信息表 CREATE TABLE vehicle ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) NOT NULL COMMENT 车牌号, owner_name VARCHAR(50) NOT NULL COMMENT 车主姓名, owner_phone VARCHAR(20) COMMENT 联系电话, vehicle_type TINYINT NOT NULL COMMENT 1-家庭自用 2-营运货车 3-营运客车, purchase_price DECIMAL(10,2) NOT NULL COMMENT 新车购置价(元), reg_date DATE NOT NULL COMMENT 首次登记日期, seats INT DEFAULT 5 COMMENT 核定载客数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT车辆档案表; -- 保单表 CREATE TABLE insurance_policy ( id INT PRIMARY KEY AUTO_INCREMENT, policy_no VARCHAR(32) NOT NULL UNIQUE COMMENT 保单号, vehicle_id INT NOT NULL COMMENT 关联车辆ID, compulsory_premium DECIMAL(10,2) NOT NULL COMMENT 交强险保费, commercial_premium DECIMAL(10,2) NOT NULL COMMENT 商业险保费, total_premium DECIMAL(10,2) NOT NULL COMMENT 总保费, start_date DATE NOT NULL COMMENT 起保日期, end_date DATE NOT NULL COMMENT 终保日期, status TINYINT DEFAULT 1 COMMENT 1-有效 2-已过期 3-已退保, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_policy_vehicle FOREIGN KEY (vehicle_id) REFERENCES vehicle(id) ) ENGINEInnoDB COMMENT保单表; -- 理赔记录表 CREATE TABLE claim_record ( id INT PRIMARY KEY AUTO_INCREMENT, claim_no VARCHAR(32) NOT NULL UNIQUE COMMENT 报案号, policy_id INT NOT NULL COMMENT 关联保单ID, accident_date DATE NOT NULL COMMENT 出险日期, accident_desc VARCHAR(255) COMMENT 事故描述, responsibility_ratio DECIMAL(4,2) DEFAULT 1.00 COMMENT 责任比例(0~1), loss_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT 定损金额, deduct_ratio DECIMAL(4,2) DEFAULT 0.20 COMMENT 免赔率(0~1), payable_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT 最终赔付金额, status TINYINT DEFAULT 1 COMMENT 1-报案 2-定损 3-待核赔 4-已赔付, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_claim_policy FOREIGN KEY (policy_id) REFERENCES insurance_policy(id) ) ENGINEInnoDB COMMENT理赔记录表;三个表之间形成“车辆 1 对多 保单保单 1 对多 理赔单”的级联关系。责任比例 responsibility_ratio 和免赔率 deduct_ratio 分开存放是业务上的关键区分责任比例决定保险公司赔不赔免赔率决定赔多少打折扣。两列都保留后期统计赔付率时才能分别按责任和免赔维度过滤。varchar 类型统一用 utf8mb4 字符集避免生僻汉字或特殊符号写入时报 Incorrect string value 错误。3.2 JDBC 连接管理从 DriverManager 到连接池的演进JSP 毕设直接用 DriverManager 写死连接是扣分项每次请求新建连接的开销虽然小但体现不出工程素养。用commons-dbcp2连接池把数据库连接复用起来代码量多一二十行但能写出“数据库资源为什么不能反复创建”的答案。package util; import org.apache.commons.dbcp2.BasicDataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBUtil { private static final BasicDataSource dataSource new BasicDataSource(); static { dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/car_insurance ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxTotal(20); dataSource.setMaxIdle(10); dataSource.setMinIdle(2); dataSource.setMaxWaitMillis(3000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(ResultSet rs, PreparedStatement ps, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }静态代码块在类加载时执行一次数据源全局共享。setMaxWaitMillis 设为 3000 毫秒连接池耗尽时请求最多等 3 秒就抛异常避免线程无限阻塞。关闭资源时按 ResultSet → PreparedStatement → Connection 的倒序释放连接池的 close 方法归还连接而不是真正关闭这是和 DriverManager 模型最大的区别。3.3 PreparedStatement 预编译防 SQL 注入的基本姿势JSP 项目最容易暴露的安全问题就是字符串拼接 SQL。车险系统的保单查询条件多报价输入项杂Statement 拼接的查询条件很容易被构造出恒真条件。PreparedStatement 用占位符?绑定参数数据库层面先预编译 SQL 骨架再传入参数用户输入中的单引号会按普通字符处理没有机会改变 SQL 语义。package dao; import util.DBUtil; import model.Vehicle; import java.sql.*; import java.util.ArrayList; import java.util.List; public class VehicleDao { public ListVehicle queryByOwnerName(String keyword) { // 注意?号位置参数可以通过setString绑定不允许改变SQL骨架 String sql SELECT id, plate_no, owner_name, vehicle_type, purchase_price, reg_date FROM vehicle WHERE owner_name LIKE ?; ListVehicle list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Vehicle v new Vehicle(); v.setId(rs.getInt(id)); v.setPlateNo(rs.getString(plate_no)); v.setOwnerName(rs.getString(owner_name)); v.setVehicleType(rs.getInt(vehicle_type)); v.setPurchasePrice(rs.getBigDecimal(purchase_price)); v.setRegDate(rs.getDate(reg_date).toLocalDate()); list.add(v); } } } catch (SQLException e) { e.printStackTrace(); } return list; } public int insertVehicle(Vehicle v) { String sql INSERT INTO vehicle (plate_no, owner_name, owner_phone, vehicle_type, purchase_price, reg_date, seats) VALUES (?,?,?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, v.getPlateNo()); ps.setString(2, v.getOwnerName()); ps.setString(3, v.getOwnerPhone()); ps.setInt(4, v.getVehicleType()); ps.setBigDecimal(5, v.getPurchasePrice()); ps.setDate(6, Date.valueOf(v.getRegDate())); ps.setInt(7, v.getSeats()); int rows ps.executeUpdate(); if (rows 0) { try (ResultSet keys ps.getGeneratedKeys()) { if (keys.next()) { v.setId(keys.getInt(1)); } } } return rows; } catch (SQLException e) { e.printStackTrace(); return 0; } } }try-with-resources 写法让连接只用声明一次省去 finally 里逐个判断非空再关闭的模板代码。通配符查询中 LIKE 的%也绑定在参数里拼接拼进参数值不影响 SQL 层级。getGeneratedKeys 拿到自增主键解决“插入车辆后立刻用来关联保单”的场景。这种写法放在 DAO 层每个方法里重复出现虽然啰嗦但答辩时能明确回答“如何查出一条数据、如何取回自增 ID”。4. 业务核心落地保费计算、保单状态与理赔流程实现4.1 车险保费计算规则的精简模型与 Java 实现真实车险的费率精算涉及大量历史赔付数据和行业系数毕设只需要保留业务表达逻辑交强险按车辆种类和座位数取固定基准值商业险按新车购置价 × 基础费率 × 使用年限折扣。核心代码的职责是“把规则写成可解释的方法”而不是算出行业级精度。package service; import model.Vehicle; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.Period; public class PremiumCalculator { // 基础商业险费率明显低于实际精算费率此处仅为模拟 private static final BigDecimal COMMERCIAL_BASE_RATE new BigDecimal(0.0120); // 交强险基准保费1-家庭自用按950元起营运货车按1800元起 public BigDecimal calcCompulsoryPremium(Vehicle v) { if (v.getVehicleType() 1) { return new BigDecimal(950.00); } else if (v.getVehicleType() 2) { return new BigDecimal(1800.00); } else { return new BigDecimal(2200.00); } } public BigDecimal calcCommercialPremium(Vehicle v) { BigDecimal baseAmount v.getPurchasePrice() .multiply(COMMERCIAL_BASE_RATE); // 使用年限折扣每年折5%上限折到60% int ageYears Period.between(v.getRegDate(), LocalDate.now()).getYears(); BigDecimal discount BigDecimal.ONE.subtract( new BigDecimal(ageYears).multiply(new BigDecimal(0.05))); if (discount.compareTo(new BigDecimal(0.60)) 0) { discount new BigDecimal(0.60); } return baseAmount.multiply(discount) .setScale(2, RoundingMode.HALF_UP); } public BigDecimal calcTotalPremium(Vehicle v) { return calcCompulsoryPremium(v).add(calcCommercialPremium(v)); } }BigDecimal 是金额计算的强制选择double 浮点误差在金额上会引发“保费多了几分钱”的尴尬问题。setScale(2, RoundingMode.HALF_UP) 是四舍五入到分的标准写法。使用年限 discount 的封底 0.60 防止旧车保费无限向下折扣模拟了精算模型中的最低折扣系数。答辩时可以把这些系数改成从数据库配置表读取这样“参数配置化”的回答就能接上了。4.2 理赔流程的状态推进实现理赔是车险系统里最有动态感的模块报案 → 定损 → 核赔 → 赔付。每个状态改变都要更新赔付金额相关字段而且系统要保证状态只能向后推进。这里的实现策略是 ClaimService 接收前端传递的“当前状态 操作类型”内部校验后再执行更新。package service; import model.ClaimRecord; import java.math.BigDecimal; import java.math.RoundingMode; public class ClaimService { // 状态推进规则1报案 - 2定损 - 3核赔 - 4已赔付 public int applyNextStatus(ClaimRecord claim, String action) { int current claim.getStatus(); if (report.equals(action)) { // 定损后要计算赔付金额定损额 × 责任比例 × (1-免赔率) BigDecimal loss claim.getLossAmount(); BigDecimal ratio claim.getResponsibilityRatio(); BigDecimal deduct claim.getDeductRatio(); BigDecimal payable loss.multiply(ratio) .multiply(BigDecimal.ONE.subtract(deduct)) .setScale(2, RoundingMode.HALF_UP); claim.setPayableAmount(payable); return 2; } else if (survey.equals(action)) { return 3; } else if (approve.equals(action)) { return 4; } // 非预期操作则保持原状态 return current; } public boolean canTransit(int fromStatus, String action) { if (fromStatus 1 report.equals(action)) return true; if (fromStatus 2 survey.equals(action)) return true; if (fromStatus 3 approve.equals(action)) return true; return false; } }定损节点的计算是重点上一步定损操作录入 loss_amount、responsibility_ratio、deduct_ratio审核时用公式计算出 payable_amount。公式体现了保险公司理赔的核心逻辑——赔付金额并不等于定损金额要按责任比例打折再扣掉免赔部分。canTransit 方法把状态机校验和业务逻辑分开防止前端越过中间状态直接提交到已赔付。4.3 Servlet 接收报价参数与 JSP 回显的完整链路上面两层业务逻辑最终要通过 Servlet 暴露给页面。报价表单提交时Servlet 负责收集车辆参数、调用计算器、返回结果页面。package controller; import model.Vehicle; import service.PremiumCalculator; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDate; public class QuoteServlet extends HttpServlet { private PremiumCalculator calculator new PremiumCalculator(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 收取前端传的车辆参数并封装成Vehicle对象 Vehicle v new Vehicle(); v.setPlateNo(req.getParameter(plateNo)); v.setOwnerName(req.getParameter(ownerName)); v.setVehicleType(Integer.parseInt(req.getParameter(vehicleType))); v.setPurchasePrice(new BigDecimal(req.getParameter(purchasePrice))); v.setRegDate(LocalDate.parse(req.getParameter(regDate))); v.setSeats(Integer.parseInt(req.getParameter(seats))); // 调用业务层计算结果 BigDecimal compulsory calculator.calcCompulsoryPremium(v); BigDecimal commercial calculator.calcCommercialPremium(v); BigDecimal total compulsory.add(commercial); // 结果放入request作用域forward到结果页回显 req.setAttribute(vehicle, v); req.setAttribute(compulsory, compulsory); req.setAttribute(commercial, commercial); req.setAttribute(total, total); req.getRequestDispatcher(/jsp/quoteResult.jsp).forward(req, resp); } }Servlet 里只做参数接收和页面跳转不放置任何计算逻辑。req.getAttribute 之后用 forward 跳转地址栏不变刷新页面会导致重复提交这是毕设里可以主动提的一个已知缺点然后引出用 PRG 模式解决的方案。对应 JSP 页面的结果展示区用 EL 表达式取值% page contentTypetext/html;charsetUTF-8 languagejava % html body h3报价结果/h3 p车牌号${vehicle.plateNo}/p p车辆类型 ${vehicle.vehicleType 1 ? 家庭自用 : 营运车辆} /p p交强险保费${compulsory} 元/p p商业险保费${commercial} 元/p pstrong总保费${total} 元/strong/p a hrefjsp/quote.jsp返回重新报价/a /body /htmlJSP 中尽量不写 Java 脚本片段只用 EL 表达式 ${} 取值三元运算符做显示层的简单分支。这样页面看起来干净答“表现层和数据层如何解耦”时可以直接拿这页举例。5. 权限控制与页面细节登录拦截、防重复提交与数据校验5.1 用 Filter 实现统一登录拦截车险系统里保单和理赔数据属于业务内部数据不做登录限制说不过去。Filter 拦截所有 jsp 请求Session 里没有登录标记就重定向到登录页。package util; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 登录页面本身不用拦截否则会死循环跳转 if (uri.endsWith(/login.jsp) || uri.contains(/login)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); Object loginUser (session ! null) ? session.getAttribute(loginUser) : null; if (loginUser null) { // 未登录则重定向到登录页附带来源URL便于登录后回跳 response.sendRedirect(request.getContextPath() /login.jsp ?redirect request.getRequestURI()); return; } chain.doFilter(req, resp); } }getSession(false) 是这里的关键细节false 表示如果没有会话则返回 null避免为每个未登录请求都创建一个无意义的 HttpSession 对象。放行登录页的逻辑不能漏否则过滤器把登录页自己也拦截了形成重定向死循环。sendRedirect 之后要加 return防止 filter 链继续往下走导致页面输出异常。5.2 表单重复提交的两种拦截方案报价和理赔提交页面如果直接 forward用户按 F5 就会重新走一遍 doPost产生两条保单记录。常见的处理是 PRG 模式即 Post-Redirect-Get。也就是 Servlet 写库成功之后用 sendRedirect 跳转到结果展示 servlet而不是 forward。另一种做法是在表单里放一个隐藏 token% String token java.util.UUID.randomUUID().toString(); session.setAttribute(formToken, token); % form action/quote methodpost input typehidden nameformToken value% token %/ !-- 车辆信息字段 -- /form// 在QuoteServlet的doPost开头校验token HttpSession session req.getSession(); String sessionToken (String) session.getAttribute(formToken); String requestToken req.getParameter(formToken); if (sessionToken null || !sessionToken.equals(requestToken)) { resp.sendError(400, 重复提交); return; } session.removeAttribute(formToken); // 一次有效防止绕过该方案的核心是一次性 token 校验提交成功后立即删除 session 中的 formToken刷新页面时参数里的 token 已经与 session 中的不匹配请求被拒绝。这种实现放在 JSP 里用脚本片段取 UUID虽然略有些违背“页面不写 Java”的原则但作为防重提交手段在毕设中可接受答辩时主动提出该设计即可。5.3 JSP 层面四类参数校验的取舍服务端校验才是安全防线前端 JS 校验只是用户体验优化。对车险系统而言重点检查四类数据。校验对象验证规则服务端实现位置车牌号正则匹配 省份简称 字母数字组合VehicleService.validatePlate购置价 0 且不超过 500 万元PremiumCalculator 前置检查首次登记日期不晚于当前日期日期解析时比较 LocalDate.now()出险日期不得早于保单起保日ClaimService.checkAccidentDate// 参考实现片段金额和日期的组合校验 public boolean validateQuoteParams(Vehicle v) { if (v.getPurchasePrice().compareTo(BigDecimal.ZERO) 0) { return false; // 金额不能为0或负数 } if (v.getRegDate().isAfter(LocalDate.now())) { return false; // 首次登记日期不能在未来 } if (v.getSeats() 1 || v.getSeats() 20) { return false; // 座位数合理范围 } return true; }金额比较必须用 compareTo 而不是 equals因为 BigDecimal 的 equals 会连小数点后的精度一起比较0.00 和 0 在 equals 下不相等而 compareTo 只比数值大小。JSP 页面端可以再加一层 JS 拦截但服务端校验才是真墙这个顺序反了就是安全漏洞。6. 答辩演示准备与代码提交前的检查清单6.1 给演示数据库注入一组有业务含义的测试数据空库演示是最扣分的场景。演示前在 MySQL 中手工执行几条 INSERT数据要覆盖不同状态一辆家庭自用车、一辆营运货车各对应一份保单其中一辆车有一笔理赔流程停在定损状态另一笔走完赔付闭环。运行时的保费计算结果与手工 Excel 预演结果提前对过即可现场回答问题。-- 家庭自用车示例购置价150000元2020年登记约5年车龄 INSERT INTO vehicle (plate_no, owner_name, owner_phone, vehicle_type, purchase_price, reg_date, seats) VALUES (京A12345, 张三, 13800138000, 1, 150000.00, 2020-03-15, 5); -- 为该车生成一张有效保单保单号预生成以展示唯一约束 INSERT INTO insurance_policy (policy_no, vehicle_id, compulsory_premium, commercial_premium, total_premium, start_date, end_date, status) SELECT P20240010001, id, 950.00, 1260.00, 2210.00, 2024-01-01, 2024-12-31, 1 FROM vehicle WHERE plate_no 京A12345;注意保单号使用业务编码规则“P 年份 6位序号”这个格式在真实保险系统里具有实际意义提前在数据库层面加 UNIQUE 约束后就具备了防重能力比程序里手动判断再插入更可靠。6.2 源码检查答辩前需要过一遍的七个点代码提交时不要只贴整个 src 目录。检查这几个具体位置每一项都可能成为答辩提问切入点web.xml 中是否统一配置了 UTF-8 编码过滤器JSP 页面头部的 pageEncoding 是否一致。DAO 中是否还有 Statement 拼接字符串的残留写法全部替换为 PreparedStatement。连接是否在 finally 或 try-with-resources 中正确关闭无法关闭的连接会造成连接池耗尽。Session 中存储的对象是否实现 Serializable未实现的 session 持久化时会报 NotSerializableException。JSP 页面是否还在用 % % 脚本片段输出大量数据尽量用 EL 替换。理赔状态推进时是否有字段级校验比如定损金额未填时不能进入核赔。数据库配置文件中的账号密码是否硬编码在 DBUtil 中可改为外部 properties 文件读取。6.3 命令行快速验证项目可运行状态如果在 Linux 服务器上演示或做环境迁移不用启动 Eclipse 也能快速验证项目是否可跑。提前将项目打成 war 包部署到 Tomcat然后依次检查三个健康信号# 1. 项目部署后确认目录解压成功 ls -la /usr/local/tomcat/webapps/car_insurance/ # 2. 确认数据库连接能建立MySQL Container场景下 mysql -h127.0.0.1 -uroot -p123456 -e use car_insurance; show tables; # 3. 用curl模拟一次登录请求观察Cookie与响应头 curl -i -X POST http://localhost:8080/car_insurance/login \ -d usernameadminpassword123456 -c cookies.txtcurl -c 参数将服务器下发的 JSESSIONID 写入本地文件后续访问受保护页面时带上-b cookies.txt就能验证 Filter 拦截逻辑是否正确放行已登录用户。这三个命令跑通之后项目对于你而言才真正可控不再依赖 IDE 内置浏览器确认效果。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价