news 2026/9/4 8:36:44

原生JavaWeb银行账目系统:Servlet+JDBC实现资金安全转账

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
原生JavaWeb银行账目系统:Servlet+JDBC实现资金安全转账

简介:本资源是一套面向计算机专业本科生毕业设计及JavaWeb初学者实战训练的银行帐目管理系统,聚焦银行账户全生命周期管理与ATM业务协同场景,解决毕设选题难、项目调试繁、功能完整性不足等典型痛点。压缩包共3个文件(1.24MB),含核心项目源码(ZIP)、数据库建表与初始化脚本(SQL)、详细部署与功能说明文档(TXT),覆盖从环境搭建(JDK+Eclipse+Tomcat)、MySQL配置到角色权限验证的完整链路。已有2299人学习下载,说明其在教学实践中的高复用性与稳定性。用户可直接导入运行,获得包含双角色(用户/管理员)权限体系、六大功能模块(开户销户、存取款、挂失办卡、ATM维护、多维查询统计等)的可演示系统,并附带JSP+Servlet+JDBC三层架构实现细节与典型SQL语句范例,便于理解银行类业务逻辑与Web层交互设计。

1. 这不是“又一个毕设模板”,而是一套能真正跑起来的银行账目管理逻辑

你搜“JavaWeb 银行帐目管理系统 毕设”,页面刷出来几十个同名项目,点开一看:首页是蓝色渐变背景+“欢迎使用”大字,登录页用的是默认 Bootstrap 表单,转账功能点一下弹个 alert(“转账成功”)——然后就没然后了。这不是系统,这是幻灯片。我带过六届毕设指导,每年至少帮 30+ 个学生从“能编译通过”推进到“能经得起老师现场提问”,也亲手重写过 17 个被答辩组当场叫停的“假系统”。今天这篇,就是把那个被压缩包里尘封三年、连数据库字段都没加索引的“银行帐目管理系统”,真正掰开揉碎,还原成一个有资金流向、有操作留痕、有异常拦截、能跑在 Tomcat 8.5+ 上、MySQL 5.7 实际环境里的最小可行系统(MVP)。它不炫技,不用 Spring Boot 自动装配糊弄人,就用最朴素的 Servlet + JSP + JDBC + MySQL 堆出来,但每一行代码都对应真实银行业务逻辑:比如“同一账户不能在 1 秒内发起两笔出账”,比如“转账金额必须是 0.01 元的整数倍”,比如“日终对账时,所有未确认流水必须人工干预”。关键词里反复出现的“项目源码”和“数据库脚本”,不是打包下载后解压就能运行的玩具,而是你能在 IDEA 里逐行调试、能改表结构加字段、能看慢查询日志优化 SQL 的生产级起点。适合两类人:一类是正在开题、怕选题太虚被导师打回的本科生,另一类是想补全 JavaWeb 全链路实操能力的转行者——它不教你“Spring 是什么”,它只告诉你“当用户点击‘提交转账’按钮,从 HTTP 请求进来到数据库 commit 成功,中间这 23 个关键节点,哪个环节漏了校验,钱就真没了”。

2. 为什么坚持用原生 JavaWeb 而不是 Spring Boot?——不是守旧,是看清边界

2.1 毕设场景下的技术选型真相

很多同学一上来就问:“老师,我能用 Spring Boot 吗?”我的回答永远是:“能,但你要先回答三个问题:第一,你能手写一个不依赖 @Autowired 的 JDBC 连接池吗?第二,你能解释清楚 DispatcherServlet 怎么把 /transfer 请求路由到 TransferServlet,而不是靠 @RequestMapping 注解蒙混过关吗?第三,当线上转账接口响应时间突然从 80ms 涨到 1200ms,你是打开 Actuator 看指标,还是抓取 Tomcat access log 定位到某条 SQL 执行了 1100ms?”——这三个问题,90% 的 Spring Boot 使用者答不全。毕设不是技术选型大赛,它是对你“是否真正理解 Web 应用数据流动”的压力测试。用原生 JavaWeb,不是因为 Spring Boot 不好,而是因为它太好——好到掩盖了 Servlet 生命周期、HTTP 协议状态码、JDBC 事务隔离级别这些底层契约。而银行账目系统,恰恰是这些契约失效就会出事的典型场景。比如,你用 @Transactional(propagation = Propagation.REQUIRED) 包裹转账方法,看起来天衣无缝;但如果你没手动控制 Connection 的 setAutoCommit(false),没在 catch 块里显式 rollback,没处理好 Connection 关闭时机,那这笔钱可能只扣了 A 账户,B 账户根本没入账,还找不到日志。原生写法逼你直面这些细节。

2.2 技术栈锁定:Tomcat 8.5 + MySQL 5.7 + JDK 8u291

这个组合不是拍脑袋定的,而是来自高校实验室的真实约束。我们系机房服务器统一安装的是 CentOS 7.6 + Tomcat 8.5.31 + MySQL 5.7.28,JDK 固定为 8u291(因部分老版 Oracle JDBC 驱动不兼容 JDK 11)。这意味着你的项目必须在这些版本上 100% 通过部署验证,不能写“建议使用 JDK 11+”。具体到每个组件:

  • Tomcat 8.5:Servlet 3.1 规范,支持异步 Servlet(虽然本系统没用,但为后续扩展留接口),JNDI 数据源配置稳定,比 Tomcat 9 对 Windows Server 兼容性更好;
  • MySQL 5.7:重点在于sql_mode必须包含STRICT_TRANS_TABLES,否则INSERT INTO account (balance) VALUES ('abc')会静默转成 0,而银行系统绝不允许这种“友好转换”;
  • JDK 8u291:关键点在于java.time包的LocalDateTime与 MySQLDATETIME字段映射——必须用PreparedStatement.setObject(?, LocalDateTime.now(), JDBCType.TIMESTAMP),不能用setTimestamp(),否则时区偏移会错乱。

提示:很多开源毕设项目在web.xml里写<welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list>,这在 Tomcat 8.5 下没问题;但如果你用 Tomcat 10,javax.servlet包名已改成jakarta.servlet,整个项目直接编译失败。选型不是越新越好,而是越稳越准。

2.3 数据库设计:三张表撑起核心账务,拒绝过度设计

网上很多“银行系统”动辄 20+ 张表,账户表、客户表、机构表、渠道表、产品表……毕设阶段,这纯属给自己挖坑。我们只用三张表实现闭环:

表名字段(精简版)关键约束业务含义
accountidBIGINT PK,account_noVARCHAR(19) UNIQUE NOT NULL,balanceDECIMAL(18,2) DEFAULT 0.00 CHECK (balance >= 0),statusTINYINT DEFAULT 1 COMMENT '0=冻结,1=正常'主键索引、account_no 唯一索引、balance 非负检查核心账户实体,account_no 严格按 19 位银行卡号规则生成(如 622848*********1234)
transaction_logidBIGINT PK,from_accountVARCHAR(19),to_accountVARCHAR(19),amountDECIMAL(18,2) NOT NULL CHECK (amount > 0),statusTINYINT DEFAULT 0 COMMENT '0=待处理,1=成功,2=失败,3=冲正',create_timeDATETIME DEFAULT CURRENT_TIMESTAMP,update_timeDATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPfrom_account/to_account 联合索引、status 索引所有资金变动日志,status 控制状态机流转,绝不允许直接 UPDATE balance 字段
useridBIGINT PK,usernameVARCHAR(20) UNIQUE NOT NULL,passwordVARCHAR(64) NOT NULL COMMENT 'BCrypt 加密',roleENUM('admin','operator') DEFAULT 'operator'username 唯一索引系统用户,密码必须 BCrypt 加密(不是 MD5!),role 控制菜单权限

为什么不用account_id外键关联?因为account_no就是业务主键,且银行系统中账户号是全局唯一标识,比自增 ID 更可靠。为什么transaction_log不设外键?因为高并发下外键约束会成为性能瓶颈,且account_no字段本身已做业务校验。这些取舍,不是偷懒,是基于真实场景的权衡。

3. 核心业务逻辑拆解:转账不是“A 减 B 加”,而是状态机驱动的资金流

3.1 转账流程的四个硬性状态节点

网上教程教转账,永远是“查 A 余额→判断够不够→A 减→B 加→commit”。这在银行系统里是致命错误。真实流程是:

  1. 预占(Pre-occupy):插入一条status=0的交易日志,amount冻结,from_account余额暂不扣减;
  2. 校验(Validation):检查 A 账户状态是否为 1(正常)、余额是否 ≥ amount、当日累计出账是否超限(需额外daily_out_limit字段)、是否触发反洗钱规则(如单笔超 5 万需人工审核);
  3. 执行(Execution):A 账户balance = balance - amount,B 账户balance = balance + amount,更新交易日志status=1
  4. 对账(Reconciliation):每小时跑一次定时任务,扫描status=0超过 5 分钟的记录,自动置为status=2(失败),并触发短信通知。

这个状态机设计,让系统具备“可中断、可追溯、可补偿”能力。比如网络超时导致第 3 步失败,第 4 步会自动清理脏数据;比如审计需要查某笔资金去向,直接查transaction_log表即可,无需关联多张表。

3.2 关键代码片段:如何用原生 JDBC 实现原子性转账

// TransferService.java public boolean transfer(String fromAccountNo, String toAccountNo, BigDecimal amount) { Connection conn = null; PreparedStatement psLog = null; PreparedStatement psFrom = null; PreparedStatement psTo = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // 关键!关闭自动提交 // 1. 插入预占日志(status=0) String sqlLog = "INSERT INTO transaction_log (from_account, to_account, amount, status) VALUES (?, ?, ?, 0)"; psLog = conn.prepareStatement(sqlLog, Statement.RETURN_GENERATED_KEYS); psLog.setString(1, fromAccountNo); psLog.setString(2, toAccountNo); psLog.setBigDecimal(3, amount); psLog.executeUpdate(); // 获取生成的日志 ID,用于后续更新 ResultSet rs = psLog.getGeneratedKeys(); long logId = 0; if (rs.next()) { logId = rs.getLong(1); } // 2. 查询并锁定 A 账户(FOR UPDATE) String sqlFrom = "SELECT balance, status FROM account WHERE account_no = ? FOR UPDATE"; psFrom = conn.prepareStatement(sqlFrom); psFrom.setString(1, fromAccountNo); ResultSet rsFrom = psFrom.executeQuery(); if (!rsFrom.next()) { throw new BusinessException("转出账户不存在"); } if (rsFrom.getInt("status") != 1) { throw new BusinessException("转出账户已冻结"); } BigDecimal fromBalance = rsFrom.getBigDecimal("balance"); if (fromBalance.compareTo(amount) < 0) { throw new BusinessException("余额不足"); } // 3. 更新 A 账户余额 String sqlUpdateFrom = "UPDATE account SET balance = balance - ? WHERE account_no = ?"; psFrom = conn.prepareStatement(sqlUpdateFrom); psFrom.setBigDecimal(1, amount); psFrom.setString(2, fromAccountNo); int updateFrom = psFrom.executeUpdate(); if (updateFrom != 1) { throw new BusinessException("转出账户更新失败"); } // 4. 更新 B 账户余额(同样需查是否存在) String sqlTo = "SELECT id FROM account WHERE account_no = ?"; psTo = conn.prepareStatement(sqlTo); psTo.setString(1, toAccountNo); if (!psTo.executeQuery().next()) { throw new BusinessException("转入账户不存在"); } String sqlUpdateTo = "UPDATE account SET balance = balance + ? WHERE account_no = ?"; psTo = conn.prepareStatement(sqlUpdateTo); psTo.setBigDecimal(1, amount); psTo.setString(2, toAccountNo); int updateTo = psTo.executeUpdate(); if (updateTo != 1) { throw new BusinessException("转入账户更新失败"); } // 5. 更新日志状态为成功 String sqlUpdateLog = "UPDATE transaction_log SET status = 1 WHERE id = ?"; psLog = conn.prepareStatement(sqlUpdateLog); psLog.setLong(1, logId); psLog.executeUpdate(); conn.commit(); // 所有操作成功,提交事务 return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 任何异常,回滚全部 } catch (SQLException ex) { log.error("事务回滚失败", ex); } } throw new BusinessException("转账失败:" + e.getMessage()); } finally { // 关闭资源(此处省略,实际需用 try-with-resources) } }

注意:FOR UPDATE是核心。没有它,两个并发请求同时读到 A 账户余额 10000,各自扣减 5000,最终余额变成 0 而不是 5000。这就是经典的“丢失更新”问题。很多毕设项目用SELECT ... WHERE balance >= ?判断,再UPDATE ... SET balance = balance - ?,看似正确,但在高并发下依然会出错——因为 SELECT 和 UPDATE 是两条语句,中间有时间窗口。

3.3 登录与权限控制:不是“if(role=='admin')”,而是 URL 拦截器

很多项目把权限校验写在每个 Servlet 的doPost方法开头,导致重复代码泛滥。我们用Filter实现统一拦截:

// AuthFilter.java public class AuthFilter implements Filter { private static final Set<String> ADMIN_URLS = Set.of("/admin/userManage.jsp", "/admin/accountAudit.jsp"); private static final Set<String> OPERATOR_URLS = Set.of("/operator/transfer.jsp", "/operator/balanceQuery.jsp"); @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(".css") || uri.endsWith(".js") || uri.contains("/login")) { chain.doFilter(req, resp); return; } // 检查 session 中的 user 对象 User user = (User) request.getSession().getAttribute("user"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp?error=not_login"); return; } // 根据角色和 URL 判断权限 if ("admin".equals(user.getRole())) { if (ADMIN_URLS.stream().anyMatch(uri::contains)) { chain.doFilter(req, resp); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, "无权限访问"); } } else if ("operator".equals(user.getRole())) { if (OPERATOR_URLS.stream().anyMatch(uri::contains)) { chain.doFilter(req, resp); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, "无权限访问"); } } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, "未知角色"); } } }

web.xml中注册:

<filter> <filter-name>AuthFilter</filter-name> <filter-class>com.bank.filter.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

这样,所有 URL 访问都经过同一道门,权限逻辑集中维护,新增页面只需往ADMIN_URLSOPERATOR_URLS里加路径,不用改每个 Servlet。

4. 从 IDEA 到可运行 WAR 包:手把手部署全流程

4.1 IDEA 项目结构标准化(避免“新建项目→复制粘贴”陷阱)

很多同学在 IDEA 里建个普通 Java 项目,把 JSP 文件扔进src/main/java,结果部署时报ClassNotFoundException。正确结构必须是 Maven 标准 Web 项目:

bank-system/ ├── pom.xml # Maven 依赖声明 ├── src/ │ ├── main/ │ │ ├── java/ # Java 类(com.bank.servlet, com.bank.service...) │ │ ├── resources/ # db.properties, log4j2.xml │ │ └── webapp/ # Web 根目录 │ │ ├── WEB-INF/ │ │ │ ├── web.xml # 核心配置 │ │ │ └── lib/ # 手动放 jar(如 mysql-connector-java-5.1.47.jar) │ │ ├── login.jsp │ │ ├── operator/ │ │ │ └── transfer.jsp │ │ └── admin/ │ │ └── userManage.jsp │ └── test/ # 单元测试 └── target/ # Maven 编译输出

关键点:webapp目录必须是 Module Settings → Web → Web Resource Directory 的指定路径,否则 IDEA 不识别为 Web 项目,无法配置 Tomcat。

4.2 数据库脚本执行:不只是source xxx.sql

提供的bank_db.sql脚本包含三部分:

  1. 建库与字符集

    CREATE DATABASE IF NOT EXISTS bank_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE bank_system;
  2. 建表与索引(含关键注释):

    -- 账户表:account_no 必须唯一,balance 非负检查 CREATE TABLE account ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_no VARCHAR(19) NOT NULL UNIQUE, balance DECIMAL(18,2) NOT NULL DEFAULT 0.00 CHECK (balance >= 0), status TINYINT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 为高频查询字段建索引 CREATE INDEX idx_account_status ON account(status); CREATE INDEX idx_account_no ON account(account_no);
  3. 初始化数据(含真实业务数据):

    -- 插入两个测试账户,余额非零,模拟真实场景 INSERT INTO account (account_no, balance, status) VALUES ('6228480000000000001', 10000.00, 1), ('6228480000000000002', 5000.00, 1); -- 插入管理员用户(密码已 BCrypt 加密:$2a$10$...) INSERT INTO user (username, password, role) VALUES ('admin', '$2a$10$N9qo8uLOickgx2ZMRZoMy.eI0z6X4mYzQbOvVhWfJFqHdKkUxKvJ.', 'admin'), ('operator', '$2a$10$N9qo8uLOickgx2ZMRZoMy.eI0z6X4mYzQbOvVhWfJFqHdKkUxKvJ.', 'operator');

提示:BCrypt 密码不能手动生成。用在线工具或 Java 代码生成:

String encoded = new BCryptPasswordEncoder().encode("123456"); System.out.println(encoded); // 输出加密串

直接写死在 SQL 里,避免部署时还要手动改密码。

4.3 Tomcat 配置:JNDI 数据源替代硬编码

db.properties里写jdbc.url=jdbc:mysql://localhost:3306/bank_system是危险的。正式部署必须用 JNDI:

  1. Tomcat/conf/context.xml中添加:

    <Resource name="jdbc/BankDB" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/bank_system?useSSL=false&amp;serverTimezone=Asia/Shanghai" username="root" password="your_password" maxActive="20" minIdle="5" maxWait="10000"/>
  2. 在项目WEB-INF/web.xml中声明资源引用:

    <resource-ref> <description>Bank Database</description> <res-ref-name>jdbc/BankDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
  3. 在 Java 代码中获取:

    Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/BankDB"); Connection conn = ds.getConnection();

这样,数据库连接参数完全脱离代码,运维人员只需改 Tomcat 配置,无需重新编译 WAR 包。

4.4 WAR 包生成与部署:三步验证法

  1. 本地验证:在 IDEA 中配置 Tomcat Server,选择bank-system为 Deployment,Application context 设为/bank,启动后访问http://localhost:8080/bank/login.jsp
  2. 导出 WAR:右键项目 →ExportWAR file,保存为bank.war
  3. 服务器部署:将bank.war复制到Tomcat/webapps/目录,Tomcat 自动解压;检查Tomcat/logs/catalina.out是否有INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive日志;访问http://服务器IP:8080/bank/login.jsp

常见失败点:MySQL 驱动 jar 包没放进WEB-INF/lib/,导致ClassNotFoundExceptionweb.xmlservlet-mappingurl-pattern写成/transfer而不是/transfer(少斜杠),导致 404;JSP 中 EL 表达式${user.username}不生效,是因为web.xmlweb-app版本低于 2.4,需声明:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">

5. 毕设答辩高频问题与避坑指南:从“能跑”到“能讲”

5.1 导师必问的五个灵魂问题及应答逻辑

问题错误回答(踩坑)正确回答(体现深度)应答要点
“转账怎么保证不丢钱?”“用了事务,commit 就成功了。”“通过四步状态机:预占日志→业务校验→双账户更新→对账补偿。即使 JVM 崩溃,未完成的预占日志会被定时任务标记为失败,并触发人工干预流程。”强调状态机设计,而非单纯事务
“并发转账怎么处理?”“加了 synchronized,不会并发。”“在 SQL 层用SELECT ... FOR UPDATE锁定账户行,结合数据库 MVCC 机制,避免应用层锁带来的性能瓶颈。”指出数据库级锁,而非 Java 锁
“密码怎么存的?”“MD5 加密,很安全。”“使用 BCrypt 加盐哈希,迭代次数 10,密码长度限制 20 字符,防止彩虹表攻击。BCrypt 的 salt 存在 hash 字符串中,无需单独建表存储。”明确算法、参数、原理
“如果数据库挂了怎么办?”“那就没法用了。”“当前是单点部署,但架构上预留了扩展点:1)transaction_log表可同步到 Elasticsearch,提供日志检索;2)账户余额可缓存到 Redis,设置过期时间,降级为只读查询;3)未来可接入消息队列,实现最终一致性。”展示架构演进思维,而非回避问题
“你这个系统和真实银行系统差在哪?”“功能差不多。”“真实银行系统有:1)七层防火墙和 WAF 规则;2)硬件加密机(HSM)处理密钥;3)两地三中心容灾;4)每笔交易需符合《金融行业信息系统安全等级保护基本要求》三级标准。本系统聚焦业务逻辑验证,安全边界由学校实验环境保障。”坦诚差距,突出教育目标

5.2 答辩演示技巧:让老师看到“你做了什么”,而非“代码写了多少”

  • 演示前 5 分钟:不要从登录页开始。直接打开transaction_log表,展示几条status=1的成功记录,再手动插入一条status=0的预占记录,然后刷新页面,演示“转账中”状态;
  • 关键截图:准备三张图:1)account表中两个账户初始余额;2)转账后account表余额变化;3)transaction_log表中对应日志的status从 0 变 1 的过程;
  • 代码聚焦:不展示web.xml全文,只圈出<filter-mapping>部分,说明“权限控制在这里统一实现”;不展示整个TransferService,只放大conn.setAutoCommit(false)FOR UPDATE两行,说“这两行决定了钱能不能真正转过去”。

5.3 源码交付清单:让老师一眼看出“这不是抄的”

一份合格的毕设源码包,必须包含以下文件,缺一不可:

文件路径作用检查点
/README.md项目介绍、环境要求、启动步骤是否有JDK 8u291,Tomcat 8.5,MySQL 5.7明确版本
/doc/database_design.pngER 图(用 draw.io 导出 PNG)是否只有account,transaction_log,user三张表,无冗余
/src/main/resources/db.properties数据库配置(仅用于开发)是否有driverClassName=com.mysql.jdbc.Driver(不是 8.0 的cj
/src/main/webapp/WEB-INF/web.xmlServlet 配置是否有filterservlet-mapping完整定义
/target/bank.war可部署包是否能直接丢进 Tomcat 运行
/sql/bank_db.sql数据库脚本是否含CREATE DATABASE,CREATE TABLE,INSERT INIT DATA三部分

实操心得:我见过太多学生答辩时被问“你的数据库脚本在哪?”,翻遍压缩包找不到xxx.sql文件,最后只能现场用 Navicat 导出,结果导出的脚本含AUTO_INCREMENT=12345,老师问“为什么不是 1?”,当场卡壳。脚本必须手写,且AUTO_INCREMENT去掉,用INSERT初始化数据。

6. 后续可扩展方向:从毕设到真实项目的跃迁路径

这个系统不是终点,而是起点。如果你真想把它变成可用的工具,下一步可以这样走:

6.1 安全加固:从“能用”到“合规”

  • SQL 注入防护:当前用PreparedStatement已防住,但需补充OWASP ESAPI库,对用户输入做encoder.canonicalize()处理,防止 Unicode 编码绕过;
  • XSS 防护:JSP 中所有${}输出,必须用<c:out value="${xxx}" />StringEscapeUtils.escapeHtml4()包装;
  • CSRF 防护:在转账表单中加入input type="hidden" name="csrf_token" value="${sessionScope.csrfToken}",后端校验 token 有效性。

6.2 性能优化:从“单机”到“可伸缩”

  • 连接池升级:将 Tomcat JNDI 的org.apache.tomcat.jdbc.pool.DataSourceFactory替换为HikariCP,配置maximumPoolSize=20,connectionTimeout=30000
  • 查询优化:为transaction_log表的create_time字段建索引,支持“近 7 天流水查询”;
  • 缓存引入:用 Ehcache 缓存account表的热点账户(如account_no622848开头的),设置 TTL=300 秒,降低数据库压力。

6.3 功能增强:从“基础”到“完整”

  • 日志审计:增加audit_log表,记录所有敏感操作(登录、转账、用户修改),字段含operator_ip,user_agent,action_time
  • 短信通知:集成阿里云 SMS SDK,在transfer成功后发送“【银行】您尾号0001账户支出5000.00元,余额5000.00元”;
  • Excel 导出:用 Apache POI 实现“账户流水导出”,支持按日期范围筛选,导出.xlsx文件。

这些扩展,每一个都能成为你简历上的亮点。但记住:毕设的核心价值,不在于功能多炫,而在于你能否清晰说出“为什么选这个方案,它解决了什么问题,边界在哪里”。当你能对着transaction_log表,指着status字段说“这个 0/1/2/3 的状态机,就是资金安全的生命线”,你就已经超越了 90% 的毕设同学。

我在实际带毕设时发现,那些答辩表现最好的学生,往往不是代码写得最多的,而是能把一个FOR UPDATE语句背后的并发控制原理,用食堂打饭排队的例子讲清楚的人。真正的技术深度,不在堆砌框架,而在理解契约——HTTP 协议的契约、数据库事务的契约、金融业务的契约。这个银行账目管理系统,就是一张契约的具象化。它不完美,但它真实;它不庞大,但它完整;它不前沿,但它扎实。如果你正为毕设发愁,不妨就从这三张表、一个状态机、一次真实的转账开始。钱不会骗人,代码不会说谎,而你,终将在这份扎实里,找到自己的技术坐标。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 8:35:56

电感选型与维修核心指南:深度解析L值与Q值

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 8:35:30

基于YOLOv8的车流检测系统:从模型训练到多端部署的工程实践

简介&#xff1a;这是一套基于YOLOv8实现的多端车流检测系统完整工程&#xff0c;面向计算机、人工智能、自动化等专业的在校学生、教师及初级开发者&#xff0c;适用于课程设计、毕业设计、项目立项演示或智能交通方向的实践学习。资源包共396个文件&#xff0c;含150个Python…

作者头像 李华
网站建设 2026/9/4 8:31:43

电子元器件采购全攻略:从需求分析到验货避坑的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 8:30:28

核电站延寿工程评估与设备老化管理:从安全论证到数据驱动分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 8:29:19

收藏!AI批量挖洞实战教程:小白也能月入过万的秘诀与内卷陷阱解析

收藏&#xff01;AI批量挖洞实战教程&#xff1a;小白也能月入过万的秘诀与内卷陷阱解析 本文深入分析了当前AI批量挖洞在SRC圈子中的广泛应用及其影响。AI通过自动化扫描、降低入门门槛、提高效率等优势&#xff0c;使得新人能快速实现变现&#xff0c;但也引发了“人工过时”…

作者头像 李华
网站建设 2026/9/4 8:27:07

从零开始手把手设计定焦工业镜头:Zemax实战与像差优化全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华