简介:这是一份基于Java开发的轻量级校园卡管理系统源码包,面向Java初学者与课程设计学生,聚焦校园场景下的饭卡充值、消费记录与账户管理等核心功能,适合作为Java SE综合实践项目或毕业设计参考。资源共30个文件,含7个Java源文件(含主程序与数据库操作类)、8个编译后class文件、10张界面截图(展示登录、充值、消费等关键页面),以及Eclipse工程配置文件(.project、.classpath)和说明文档,整体压缩包仅703KB,结构清晰,开箱即用。已有1845人学习下载,提供完整可运行的桌面应用方案:包含Swing图形界面实现、MySQL数据库连接示例(JDBC)、基础事务处理逻辑及MD5密码加密模块,代码注释充分,目录组织规范,便于理解MVC分层思想与Java桌面应用开发全流程。
1. 用 Java + Eclipse 搭建轻量级校园卡管理系统:不是 Demo,是能跑通充值、消费、挂失的真实业务闭环
你手头有一份标着“Java 校园卡管理系统”的代码,打开发现是 Eclipse 工程结构、带src和WebContent目录、依赖servlet-api.jar、数据库用的是 HSQLDB 或 MySQL —— 这不是教学玩具,而是典型的学生一卡通系统最小可行原型(MVP)。它不依赖 Spring Boot 自动装配,不走微服务架构,但完整覆盖了学生卡核心流程:发卡登记、余额查询、食堂消费扣款、自助机挂失、管理员后台审核。这类项目在高校信息中心、实训课程、毕业设计中高频出现,特点是业务逻辑清晰、数据模型扁平、IO 压力低、部署环境受限(常要求 Tomcat 7/8 + JDK 8)。如果你正面对一个需要快速交付、无云资源、仅靠本地 Eclipse 调试、且要经得起教师现场演示的 Java Web 项目,本篇就聚焦如何从零复现这个系统——不绕开web.xml配置,不跳过JDBCUtils手写连接池,不假设你已装好 Maven 插件。所有操作基于 Eclipse Oxygen/2020-06 及 JDK 1.8 环境验证,命令和路径全部可复制粘贴。
2. 用 Eclipse 创建标准 Java Web 工程并配置 Tomcat 运行时环境
2.1 新建 Dynamic Web Project 并指定兼容性版本
Eclipse 中新建项目必须严格匹配运行容器能力。校园卡系统普遍部署在老旧服务器上,Tomcat 版本多为 7.0.x 或 8.5.x,对应 Servlet 规范为 3.0 或 3.1。因此不能选择 “Dynamic Web Module Version” 为 4.0+,否则web.xml中<url-pattern>写法或注解扫描会失败。
提示:若新建时未勾选 “Generate web.xml deployment descriptor”,后续无法通过右键项目 →Java EE Tools→Generate Deployment Descriptor补全,必须手动创建
WebContent/WEB-INF/web.xml文件并确保其根节点为<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_3_1.xsd" version="3.1">。
2.1.1 具体操作步骤
File → New → Dynamic Web Project- Project name 输入
campus-card-system(避免中文和空格) - Target runtime 选择已配置的 Tomcat v7.0 Server(若未配置,先点击New Runtime→ 下载并指向 Tomcat 解压目录)
- Dynamic web module version 选3.0(关键!Spring Boot 项目常用 4.0,但本系统需兼容老 Tomcat)
- Configuration 选Default Configuration for Apache Tomcat v7.0
- Finish 后检查
WebContent/WEB-INF/web.xml是否自动生成;若缺失,手动创建并粘贴标准头声明
2.2 添加 JDBC 驱动与 Servlet API 依赖
校园卡系统数据库通常为 MySQL 5.7 或 HSQLDB(嵌入式,免安装),Eclipse 中不使用 Maven 时,依赖需手动放入WebContent/WEB-INF/lib/。
- MySQL 驱动:下载
mysql-connector-java-5.1.47.jar(兼容 JDK 1.8,新版 8.x 需改驱动类名为com.mysql.cj.jdbc.Driver) - Servlet API:Tomcat 自带
servlet-api.jar,不可重复添加,否则启动报java.lang.ClassCastException: org.apache.catalina.connector.RequestFacade cannot be cast to javax.servlet.http.HttpServletRequest。正确做法是:右键项目 →Properties → Java Build Path → Libraries → Add Library → Server Runtime → Apache Tomcat v7.0。
2.2.1 验证依赖是否生效
在src/cn/edu/campus/dao/BaseDao.java中写测试代码:
package cn.edu.campus.dao; import java.sql.Connection; import java.sql.DriverManager; public class BaseDao { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); // 注意:5.1.x 版本用此驱动类名 Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/campus_card?useUnicode=true&characterEncoding=UTF-8", "root", "123456"); System.out.println("JDBC 连接成功:" + conn.isValid(1)); } }注意:运行此测试类前,必须右键 →Run As → Java Application,而非Run on Server。若报
ClassNotFoundException,说明mysql-connector-java-5.1.47.jar未正确加入 Build Path:右键 JAR 文件 →Build Path → Add to Build Path。
2.3 配置 Tomcat 启动参数与上下文路径
默认 Eclipse 启动 Tomcat 时,项目访问路径为http://localhost:8080/campus-card-system/,但校园卡自助终端常硬编码/card/路径。需修改部署描述符:
右键项目 →Properties → Web Deployment Assembly→ 选中src和WebContent→ Edit → 将 Deploy Path 改为/(使根路径直接映射项目);同时在Servers视图中双击 Tomcat 服务器 →Modules标签页 → 选中项目 → Edit → Context root 改为/card。
重启后访问http://localhost:8080/card/login.jsp即可进入登录页。
3. 实现校园卡核心业务逻辑:发卡、消费、挂失的三层架构落地
3.1 数据库表设计与初始化脚本
校园卡系统数据模型极简,核心三张表即可支撑全流程:
student(学生基本信息):id(PK),name,class_id,phonecard(卡片主表):card_id(PK),student_id(FK),balance(DECIMAL),status(ENUM: 'normal','lost','frozen')transaction_log(交易流水):id(PK),card_id,type(ENUM: 'recharge','consume','loss_report'),amount,create_time
3.1.1 MySQL 初始化 SQL(执行前确保数据库campus_card已存在)
CREATE DATABASE IF NOT EXISTS campus_card CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE campus_card; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL, class_id VARCHAR(20), phone VARCHAR(11) ); CREATE TABLE card ( card_id VARCHAR(20) PRIMARY KEY, student_id INT NOT NULL, balance DECIMAL(10,2) DEFAULT 0.00, status ENUM('normal','lost','frozen') DEFAULT 'normal', FOREIGN KEY (student_id) REFERENCES student(id) ); CREATE TABLE transaction_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, card_id VARCHAR(20) NOT NULL, type ENUM('recharge','consume','loss_report') NOT NULL, amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );提示:
card_id设为VARCHAR(20)是因实际校园卡号常含字母前缀(如CARD20230001),避免INT类型溢出或补零问题;status使用ENUM而非TINYINT,便于 SQL 查询语义清晰,如WHERE status='lost'比WHERE status=2更易维护。
3.2 DAO 层:手写 JDBC 连接池与事务控制
不依赖 HikariCP 或 Druid,用BasicDataSource(Apache Commons DBCP)实现轻量连接池。在src/cn/edu/campus/utils/JDBCUtils.java中:
package cn.edu.campus.utils; import org.apache.commons.dbcp.BasicDataSource; public class JDBCUtils { private static BasicDataSource dataSource; static { dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/campus_card?useUnicode=true&characterEncoding=UTF-8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxActive(20); // 最大活跃连接 dataSource.setMaxIdle(10); // 最大空闲连接 dataSource.setMinIdle(5); // 最小空闲连接 dataSource.setRemoveAbandonedOnBorrow(true); dataSource.setRemoveAbandonedOnMaintenance(true); dataSource.setRemoveAbandonedTimeout(60); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs != null) try { rs.close(); } catch (SQLException e) {} if (ps != null) try { ps.close(); } catch (SQLException e) {} if (conn != null) try { conn.close(); } catch (SQLException e) {} } }参数说明:
setRemoveAbandonedOnBorrow启用连接泄露检测,当借出连接超时未归还(removeAbandonedTimeout=60秒),自动回收该连接并打印警告日志,防止 Tomcat 启动后因连接未关闭导致Cannot create PoolableConnectionFactory错误。
3.3 Service 层:消费扣款的原子性保障
食堂消费需保证“余额足够 → 扣款 → 记录流水”三步不可分割。在src/cn/edu/campus/service/CardService.java中:
package cn.edu.campus.service; import cn.edu.campus.dao.CardDao; import cn.edu.campus.entity.Card; import cn.edu.campus.utils.JDBCUtils; import java.sql.Connection; import java.sql.SQLException; public class CardService { private CardDao cardDao = new CardDao(); public boolean consume(String cardId, double amount) { Connection conn = null; try { conn = JDBCUtils.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 查询当前余额 Card card = cardDao.findByCardId(cardId); if (card == null || card.getStatus().equals("lost") || card.getStatus().equals("frozen")) { throw new RuntimeException("卡片状态异常,无法消费"); } if (card.getBalance() < amount) { throw new RuntimeException("余额不足"); } // 2. 扣款 cardDao.updateBalance(cardId, -amount); // 3. 记录流水 cardDao.insertTransaction(cardId, "consume", amount); conn.commit(); // 提交事务 return true; } catch (Exception e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) {} } e.printStackTrace(); return false; } finally { JDBCUtils.close(conn, null, null); } } }关键点:
conn.setAutoCommit(false)必须在获取连接后立即设置,否则后续executeUpdate()默认自动提交,事务失效;rollback()在catch块中显式调用,确保异常时余额和流水状态一致。
4. JSP + Servlet 实现前后端交互:登录、消费、挂失页面链路
4.1 登录 Servlet:验证学号密码并跳转到学生主页
src/cn/edu/campus/servlet/LoginServlet.java处理表单 POST:
package cn.edu.campus.servlet; import cn.edu.campus.dao.StudentDao; import cn.edu.campus.entity.Student; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginServlet extends HttpServlet { private StudentDao studentDao = new StudentDao(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String studentId = req.getParameter("studentId"); String password = req.getParameter("password"); // 简单校验:生产环境应加密存储密码(BCrypt) Student student = studentDao.findByStudentId(studentId); if (student != null && password.equals(student.getPassword())) { HttpSession session = req.getSession(); session.setAttribute("student", student); resp.sendRedirect(req.getContextPath() + "/student/home.jsp"); } else { req.setAttribute("error", "学号或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } }注意:
req.getContextPath()返回/card,因此重定向路径为/card/student/home.jsp,与 Tomcat Context root 保持一致;session.setAttribute("student", student)将学生对象存入 Session,供后续 JSP 页面通过${sessionScope.student.name}获取。
4.2 消费页面:表单提交到 ConsumeServlet
WebContent/student/consume.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>校园卡消费</title></head> <body> <h2>消费金额(元)</h2> <form action="${pageContext.request.contextPath}/ConsumeServlet" method="post"> <input type="hidden" name="cardId" value="${sessionScope.student.cardId}"> <input type="number" name="amount" step="0.01" min="0.01" required> <button type="submit">确认消费</button> </form> <% if (request.getAttribute("result") != null) { %> <p style="color:red">${request.getAttribute("result")}</p> <% } %> </body> </html>对应ConsumeServlet:
// ... 导入声明 public class ConsumeServlet extends HttpServlet { private CardService cardService = new CardService(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cardId = req.getParameter("cardId"); double amount = Double.parseDouble(req.getParameter("amount")); if (cardService.consume(cardId, amount)) { req.setAttribute("result", "消费成功,当前余额:" + getBalance(cardId)); } else { req.setAttribute("result", "消费失败,请重试"); } req.getRequestDispatcher("/student/consume.jsp").forward(req, resp); } private String getBalance(String cardId) { // 此处应调用 CardDao 查询,为简洁省略 return "99.50"; } }参数说明:
step="0.01"确保输入框支持小数点后两位;min="0.01"防止输入 0 元;隐藏域cardId从 Session 中取出,避免前端篡改卡号。
4.3 挂失功能:状态更新与通知记录
挂失操作需更新card.status并插入transaction_log:
// CardDao.java 中新增方法 public void reportLoss(String cardId) throws SQLException { String sql = "UPDATE card SET status = 'lost' WHERE card_id = ?"; update(sql, cardId); sql = "INSERT INTO transaction_log(card_id, type, amount) VALUES (?, 'loss_report', 0)"; update(sql, cardId); }LossReportServlet调用此方法,并重定向至提示页:
// LossReportServlet.java @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cardId = req.getParameter("cardId"); try { cardDao.reportLoss(cardId); req.setAttribute("message", "挂失成功,卡片已冻结"); } catch (Exception e) { req.setAttribute("message", "挂失失败:" + e.getMessage()); } req.getRequestDispatcher("/student/loss_result.jsp").forward(req, resp); }5. 排查 Eclipse 常见运行时错误:从 “找不到主类” 到 JSP 编译失败
5.1 “找不到或无法加载主类 org.apache.catalina.startup.Bootstrap”
此错误绝非项目代码问题,而是 Tomcat 启动入口类路径缺失。根本原因是 Eclipse 的 Server Runtime 配置未正确关联 Tomcat 的bin/bootstrap.jar和bin/tomcat-juli.jar。
5.1.1 修复步骤
- 关闭 Eclipse
- 删除工作空间下
.metadata/.plugins/org.eclipse.wst.server.core/tmp0/(tmp0 对应 Tomcat 实例编号) - 打开
Window → Preferences → Server → Runtime Environments - 选中 Apache Tomcat v7.0 → Edit →Next→ 确保 “JRE” 选择 JDK 1.8(非 JRE)→Finish
- 右键 Servers 视图中的 Tomcat →Clean...→ 勾选Clean all projects
- 重新启动 Tomcat
提示:若仍报错,检查 Tomcat 安装目录
bin/下是否存在bootstrap.jar;某些精简版 Tomcat 包可能缺失该文件,需从官网下载完整 zip 包替换。
5.2 JSP 编译失败:“Unable to compile class for JSP”
Eclipse 默认将 JSP 编译为 Java 文件存于work/Catalina/localhost/[context]/,若该目录权限不足或磁盘满,编译失败。
5.2.1 定位与清理
- 查看 Tomcat 控制台输出,找到类似
org.apache.jasper.JasperException: Unable to compile class for JSP的堆栈 - 进入
Servers/Tomcat v7.0 Server at localhost-config/→ 打开server.xml→ 找到<Engine name="Catalina" defaultHost="localhost">下的<Host>标签,确认appBase值(默认webapps) - 清理编译缓存:删除
workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/card/全部内容 - 重启 Tomcat,首次访问 JSP 时会重新编译
5.3 中文乱码终极解决方案:三处强制 UTF-8
校园卡系统涉及姓名、班级等中文字段,乱码常出现在三处:
| 位置 | 配置方式 | 说明 |
|---|---|---|
| JSP 页面 | <%@ page contentType="text/html;charset=UTF-8" %> | 必须放在每页首行,且charset=UTF-8不可省略 |
| Tomcat 请求编码 | Servers/Tomcat v7.0 → Overview → Open launch configuration → Arguments → VM arguments添加-Dfile.encoding=UTF-8 | 影响request.getParameter()解码 |
| MySQL 连接 URL | jdbc:mysql://localhost:3306/campus_card?useUnicode=true&characterEncoding=UTF-8 | useUnicode=true启用 Unicode,characterEncoding=UTF-8指定编码 |
验证方法:在
LoginServlet中打印System.out.println("学号:" + studentId);,若控制台显示乱码,则 VM 参数未生效;若浏览器显示乱码而控制台正常,则 JSP 页面未声明 charset。
5.4 数据库连接超时:调整 DBCP 连接池参数
当系统空闲 8 小时后首次访问报Communications link failure,是 MySQL 默认wait_timeout=28800(8 小时)断开空闲连接所致。
5.4.1 两种修复方案
方案一(推荐):在 JDBC URL 中添加自动重连
dataSource.setUrl("jdbc:mysql://localhost:3306/campus_card?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=3");方案二:修改 MySQL 配置
编辑my.cnf(Linux)或my.ini(Windows),在[mysqld]下添加:
wait_timeout = 288000 # 80 小时 interactive_timeout = 288000然后重启 MySQL 服务。
参数说明:
autoReconnect=true启用自动重连,failOverReadOnly=false避免重连后连接变为只读,maxReconnects=3限制重试次数防死循环。
6. 优化校园卡系统的响应速度:JSP 缓存、SQL 索引与连接复用技巧
6.1 JSP 页面启用缓冲与静态资源分离
Eclipse 默认 JSP 编译不启用缓冲,导致每次请求都重新解析。在web.xml中添加:
<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <buffer>8kb</buffer> <trim-blank-lines>true</trim-blank-lines> </jsp-property-group> </jsp-config>同时,将 CSS/JS/图片移出WebContent/,放入WebContent/static/目录,并在 JSP 中引用:
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/main.css"> <script src="${pageContext.request.contextPath}/static/js/util.js"></script>效果:
buffer减少输出流 flush 次数;trim-blank-lines删除 JSP 中多余空行,减小响应体积;静态资源分离后,浏览器可长期缓存,降低 Tomcat IO 压力。
6.2 为高频查询字段添加数据库索引
校园卡系统最慢操作通常是按卡号查余额、按学号查卡片。在 MySQL 中执行:
-- 为 card 表 card_id 字段加唯一索引(主键已存在,此步可省略) ALTER TABLE card ADD UNIQUE INDEX idx_card_id (card_id); -- 为 student 表 student_id 加索引(假设 student.id 是主键,但 card.student_id 未索引) ALTER TABLE card ADD INDEX idx_student_id (student_id); -- 为 transaction_log 表 card_id 和 create_time 加联合索引(查某卡最近 10 笔流水) ALTER TABLE transaction_log ADD INDEX idx_card_time (card_id, create_time DESC);验证索引生效:执行
EXPLAIN SELECT * FROM transaction_log WHERE card_id='CARD20230001' ORDER BY create_time DESC LIMIT 10;,若type为ref且key显示idx_card_time,则索引命中。
6.3 复用 PreparedStatement 避免 SQL 解析开销
在CardDao.java中,将updateBalance方法从字符串拼接改为预编译:
// 旧写法(每次执行都解析 SQL) String sql = "UPDATE card SET balance = balance - " + amount + " WHERE card_id = '" + cardId + "'"; // 新写法(PreparedStatement 复用执行计划) private static final String SQL_UPDATE_BALANCE = "UPDATE card SET balance = balance + ? WHERE card_id = ?"; // ... ps = conn.prepareStatement(SQL_UPDATE_BALANCE); ps.setDouble(1, -amount); // 扣款传负数 ps.setString(2, cardId); ps.executeUpdate();性能对比:对同一 SQL 模板,PreparedStatement 在数据库端只编译一次执行计划,后续调用直接绑定参数执行,比 Statement 快 3~5 倍;且彻底杜绝 SQL 注入风险。
6.4 使用 Eclipse MAT 分析内存泄漏:定位未关闭的 ResultSet
当 Tomcat 运行数小时后OutOfMemoryError: GC overhead limit exceeded,大概率是 DAO 层未关闭ResultSet。用 Eclipse Memory Analyzer Tool(MAT)分析 heap dump:
- 在
Servers视图右键 Tomcat →Debug(非 Run) - 访问几次消费页面后,右键 Tomcat →Heap Dump
- 打开生成的
heap_dump.hprof→Leak Suspects Report - 若发现大量
com.mysql.jdbc.JDBC4ResultSet实例,说明ResultSet.close()未调用 - 修改
JDBCUtils.close()方法,确保rs.close()在 finally 块中执行
关键技巧:在
close()方法开头添加日志System.out.println("Closing ResultSet: " + rs);,若日志不输出,证明rs为 null 或未创建,需检查 DAO 中executeQuery()后是否遗漏赋值。
本文还有配套的精品资源,点击获取