Java原生Socket+JDBC档案管理系统(课程设计实战) 简介这是一份面向Java初学者与课程设计学生的C/S架构档案管理系统实战项目聚焦面向对象编程、Socket网络通信与多线程服务器开发等核心技能训练适用于高校《Java程序设计》《网络编程》等课程实验及综合实训。资源包含47个文件涵盖14个Java源码含client/server双端逻辑、15个编译后class文件、2个SQL建表脚本支撑用户与档案元数据管理、8个说明类txt文档含实验指导书与readme以及项目配置文件和MySQL驱动jar包整体压缩包仅1.37MB结构清晰、开箱即用。已有873人学习下载提供完整可运行的客户端-服务器协同流程支持三级权限登录管理员/录入员/浏览员、基于Socket的多线程服务端、数据库持久化用户与档案元信息、文件上传/下载/查询/删除等全功能模块代码注释充分目录分层明确client/src、server/src、archive_index等便于理解C/S交互机制与系统分层设计思想。1. 用纯 Java Socket JDBC 实现一个可运行、可调试、可扩展的档案管理系统不是 Demo是能塞进课程设计答辩现场的真实项目你手头正赶着《Java 程序设计》或《软件工程综合实验》的结课作业老师要求“独立完成一个带数据库交互和网络通信的小型系统”但网上搜到的全是“学生信息管理系统Swing 版”——界面丑、没网络层、SQL 写死在代码里、连基本增删改查都缺事务控制。这个「Java 实验设计-实现一个小型档案管理系统」源码包就是专为这种场景打磨出来的它不依赖 Spring Boot、不套 Maven 模板、不走 REST API 路线而是用最扎实的 Java 原生技术栈——java.net.Socket实现客户端/服务端通信java.sql.*封装 JDBC 连接池与 DAO 层配合PreparedStatement防注入、手动事务控制、文件路径安全校验所有逻辑可单步调试、可断点追踪、可替换数据库驱动MySQL/SQLite/H2 全兼容。适合大二下到大三上的学生复现答辩也适合刚转 Java 的工程师补全“从零搭服务端”的肌肉记忆。它解决的不是“能不能跑”而是“为什么这么写才不算交差”。2. 项目结构与核心模块拆解看清三层架构如何用原生 Java 落地2.1 目录结构与文件职责拒绝“src 下一坨乱麻”项目采用清晰的分层目录结构所有类按职责归位无冗余包、无未使用类src/ ├── client/ # 客户端模块Swing GUI Socket 客户端 │ ├── ArchiveClient.java # 主启动类含登录、主界面、事件监听 │ └── ui/ # Swing 组件封装避免逻辑混入 UI │ ├── LoginFrame.java │ └── MainFrame.java ├── server/ # 服务端模块Socket Server 业务调度 │ ├── ArchiveServer.java # 主服务入口监听端口、分发请求 │ └── handler/ # 请求处理器每个命令对应一个 Handler │ ├── AddArchiveHandler.java │ ├── QueryArchiveHandler.java │ └── DeleteArchiveHandler.java ├── common/ # 公共模块DTO、协议、工具类 │ ├── protocol/ # 自定义通信协议文本协议非 JSON/HTTP │ │ ├── Command.java # 命令枚举ADD/QUERY/DELETE/LOGIN │ │ └── Message.java # 消息封装type, data, timestamp │ ├── model/ # 档案实体类POJO无 Lombok字段全 public final 或 getter/setter │ │ └── Archive.java │ └── util/ # 工具类DBUtil、FileUtil、StringUtil ├── db/ # 数据库初始化与配置 │ ├── init.sql # 建表语句含 id、title、category、file_path、create_time 字段 │ └── db.properties # 数据库连接参数driver、url、user、password └── resources/ # 静态资源图标、日志配置 log4j2.xml提示common.protocol.Message是关键枢纽——所有网络传输都序列化为Message对象type字段决定服务端调用哪个Handlerdata字段是 JSON 格式字符串如{title:张三档案,category:人事}避免了 Java 序列化兼容性问题也方便后续升级为 HTTP 接口。2.2 通信协议设计为什么不用 HTTPSocket 文本协议怎么防粘包项目采用自定义文本协议而非 HTTP原因很实际课程设计要求体现“网络编程能力”而 HTTP 客户端如HttpURLConnection太黑盒无法考察 TCP 连接管理、流读写控制、粘包处理等底层能力。协议格式如下[COMMAND]\n [JSON_DATA]\n [END]\n例如添加档案请求ADD {title:李四档案,category:教学,file_path:/data/archives/li_si.pdf} END服务端ArchiveServer中关键读取逻辑带粘包防护// server/ArchiveServer.java BufferedReader reader new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8)); StringBuilder buffer new StringBuilder(); String line; while ((line reader.readLine()) ! null) { if (END.equals(line.trim())) { break; // 收到结束标记停止读取 } buffer.append(line).append(\n); } String fullMessage buffer.toString().trim(); if (fullMessage.startsWith(ADD\n) || fullMessage.startsWith(QUERY\n)) { // 解析 command 和 data String[] parts fullMessage.split(\n, 2); String commandStr parts[0].trim(); String jsonData parts.length 1 ? parts[1].trim() : {}; // 后续交给对应 Handler 处理 }参数说明readLine()自动处理\n或\r\n行尾避免手动解析字节流buffer.append(line).append(\n)保证 JSON 结构完整split(\n, 2)限制只切一次防止 JSON 内部换行干扰。这是课程设计中“能讲清楚原理”的硬核细节。2.3 数据库访问层JDBC 连接池 手动事务 防 SQL 注入项目未用 HikariCP 或 Druid而是手写轻量级连接池DBConnectionPool仅 87 行代码但支持最大连接数、空闲超时、连接验证足够课程设计使用// common/util/DBUtil.java public class DBConnectionPool { private static final int MAX_CONNECTIONS 5; private static final long IDLE_TIMEOUT_MS 300_000L; // 5分钟 private static final ListConnection connections new ArrayList(); private static final ListLong lastUsed new ArrayList(); public static synchronized Connection getConnection() throws SQLException { for (int i 0; i connections.size(); i) { Connection conn connections.get(i); if (conn ! null !conn.isClosed() System.currentTimeMillis() - lastUsed.get(i) IDLE_TIMEOUT_MS) { lastUsed.set(i, System.currentTimeMillis()); return conn; } } // 创建新连接 Connection newConn DriverManager.getConnection( PropertiesUtil.getDbUrl(), PropertiesUtil.getDbUser(), PropertiesUtil.getDbPassword() ); connections.add(newConn); lastUsed.add(System.currentTimeMillis()); return newConn; } }DAO 层全部使用PreparedStatement杜绝字符串拼接// server/handler/AddArchiveHandler.java public void handle(Message message) throws SQLException { String sql INSERT INTO archive (title, category, file_path, create_time) VALUES (?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { conn.setAutoCommit(false); // 开启事务 JSONObject data new JSONObject(message.getData()); ps.setString(1, data.optString(title, )); ps.setString(2, data.optString(category, )); ps.setString(3, sanitizeFilePath(data.optString(file_path, ))); // 文件路径白名单校验 ps.setTimestamp(4, new Timestamp(System.currentTimeMillis())); int affected ps.executeUpdate(); if (affected 0) throw new SQLException(Insert failed); conn.commit(); // 提交事务 } catch (Exception e) { conn.rollback(); // 回滚 throw e; } }逻辑说明sanitizeFilePath()会检查路径是否以/data/archives/开头且不含../防止目录穿越conn.setAutoCommit(false)commit()/rollback()显式控制事务边界比 SpringTransactional更易理解、更易调试。3. 客户端与服务端启动实操从零跑通一步一验证3.1 环境准备与依赖确认JDK 8、MySQL 5.7、无需额外 Jar项目仅依赖 JDK 原生类库java.net,java.sql,javax.swing无第三方框架。唯一外部依赖是 MySQL JDBC 驱动需手动下载mysql-connector-java-8.0.33.jar并加入 classpathWindows将 jar 放入lib/目录编译时加-cp .;lib/mysql-connector-java-8.0.33.jarLinux/macOSjavac -cp .:lib/mysql-connector-java-8.0.33.jar src/server/ArchiveServer.java注意若用 SQLite 替代 MySQL只需修改db.properties中的driver和url并把mysql-connector-java-*.jar换成sqlite-jdbc-3.42.0.0.jar其余代码零修改。3.2 数据库初始化执行 init.sql检查表结构与索引运行前必须初始化数据库。db/init.sql内容如下已适配 MySQL 5.7CREATE DATABASE IF NOT EXISTS archive_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE archive_db; CREATE TABLE IF NOT EXISTS archive ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, category VARCHAR(100) NOT NULL, file_path VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category), INDEX idx_title (title) ); -- 插入测试数据可选 INSERT INTO archive (title, category, file_path) VALUES (王五档案, 人事, /data/archives/wang_wu.docx), (课程大纲V2, 教学, /data/archives/curriculum_v2.pdf);执行方式MySQL 命令行mysql -u root -p db/init.sql验证要点执行后检查archive表是否存在、字段类型是否正确特别是file_path为VARCHAR(500)足够存绝对路径、索引是否生效SHOW INDEX FROM archive;。这是后续查询性能的基础别跳过。3.3 服务端启动与日志观察端口占用、连接数、异常捕获服务端启动命令Linux/macOScd src javac -cp .:../lib/mysql-connector-java-8.0.33.jar server/ArchiveServer.java \ java -cp .:../lib/mysql-connector-java-8.0.33.jar server.ArchiveServer启动成功标志控制台输出Archive Server started on port 8080日志文件logs/server.log中出现INFO [main] ArchiveServer: Server listening on /0.0.0.0:8080关键日志字段说明日志级别字段示例含义INFOClient connected: /127.0.0.1:56789新客户端接入记录 IP 和端口DEBUGHandling ADD command with data: {title:测试档案...}协议解析成功进入业务逻辑ERRORSQLException in AddArchiveHandler: Duplicate entry xxx for key PRIMARY数据库异常含具体错误码技巧启动时加 JVM 参数-Dlog4j2.debugtrue可查看 Log4j2 加载过程排查日志不输出问题。3.4 客户端登录与功能验证从登录框到真实增删改查客户端启动命令cd src javac -cp .:../lib/mysql-connector-java-8.0.33.jar client/ArchiveClient.java \ java -cp .:../lib/mysql-connector-java-8.0.33.jar client.ArchiveClient标准验证流程启动客户端 → 弹出LoginFrame→ 输入账号admin/ 密码123456硬编码课程设计允许登录成功 → 主界面MainFrame加载左侧树形菜单显示人事、教学分类点击人事→ 右侧表格显示王五档案来自 init.sql 测试数据点击新增按钮 → 弹出对话框 → 输入标题张三档案、分类人事、路径/data/archives/zhang_san.pdf→ 点击确定切换到数据库命令行执行SELECT * FROM archive WHERE title张三档案;→ 确认记录存在且id自增正确血泪经验第一次运行常卡在“登录无响应”大概率是服务端没起来或端口被占。先netstat -an | grep 8080Linux/macOS或netstat -ano | findstr :8080Windows确认端口状态再看服务端日志是否有BindException。4. 避坑指南五个真实踩过的雷省下你三天调试时间4.1 现象客户端点击“新增”后界面卡死服务端无日志输出原因客户端ArchiveClient中sendRequest()方法未设置 Socket 写超时当服务端因数据库连接失败 hang 住时客户端outputStream.write()无限阻塞。解决在client/ArchiveClient.java的sendRequest()中添加超时设置socket.setSoTimeout(5000); // 发送超时 5 秒 socket.getOutputStream().write(requestBytes); socket.getOutputStream().flush();同时服务端ArchiveServer的clientSocket.setSoTimeout(10000)也要匹配避免单边超时导致状态不一致。4.2 现象中文档案标题存入数据库后变成???但控制台打印正常原因MySQL 连接 URL 缺少useUnicodetruecharacterEncodingutf8mb4参数且数据库建表时未指定CHARACTER SET utf8mb4。解决修改db/db.propertiesurljdbc:mysql://localhost:3306/archive_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai并确保init.sql中CREATE DATABASE语句含CHARACTER SET utf8mb4已提供。4.3 现象多次快速点击“删除”按钮导致同一条记录被删两次抛出SQLException: No row found原因客户端未做按钮禁用button.setEnabled(false)服务端DeleteArchiveHandler未对重复请求做幂等校验。解决客户端增加防抖逻辑MainFrame.java中deleteButton.addActionListener(e - { deleteButton.setEnabled(false); // 禁用按钮 try { // ... 执行删除逻辑 } finally { SwingUtilities.invokeLater(() - deleteButton.setEnabled(true)); // 恢复按钮 } });服务端补充SELECT COUNT(*)验证存在性再执行DELETE。4.4 现象服务端启动时报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因JDK 9 默认模块化mysql-connector-java-8.x驱动类名从com.mysql.jdbc.Driver变为com.mysql.cj.jdbc.Driver但db.properties中driver配置未更新。解决检查db/db.properties确保为drivercom.mysql.cj.jdbc.Driver若用 MySQL 5.1.x 旧版驱动则改为com.mysql.jdbc.Driver但强烈建议升级驱动。4.5 现象Swing 界面在高 DPI 屏幕如 MacBook Retina上文字模糊、控件错位原因JDK 8 默认未启用高 DPI 支持Swing 渲染缩放失效。解决启动客户端时加 JVM 参数java -Dsun.java2d.uiScale1.5 -cp .:../lib/mysql-connector-java-8.0.33.jar client.ArchiveClient1.5表示 150% 缩放可根据屏幕实际 DPI 调整Windows 用1.25Linux KDE 用1.0。5. 进阶改造三个可立即落地的增强点让项目从“及格”变“优秀”5.1 支持多客户端并发给服务端加上线程池与连接隔离当前服务端ArchiveServer是单线程 accept所有请求串行处理不符合“小型系统”定位。改造为固定线程池每个客户端连接独占一个线程// server/ArchiveServer.java private static final ExecutorService threadPool Executors.newFixedThreadPool(10); public static void main(String[] args) { try (ServerSocket serverSocket new ServerSocket(PORT)) { System.out.println(Archive Server started on port PORT); while (!Thread.currentThread().isInterrupted()) { Socket clientSocket serverSocket.accept(); // 每个连接提交到线程池 threadPool.submit(new ClientHandler(clientSocket)); } } catch (IOException e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } // 新增内部类 ClientHandler static class ClientHandler implements Runnable { private final Socket socket; ClientHandler(Socket socket) { this.socket socket; } Override public void run() { try { // 原有 handleClient 逻辑移至此处 handleClient(socket); } catch (Exception e) { System.err.println(Error handling client: e.getMessage()); } finally { try { socket.close(); } catch (IOException ignored) {} } } }验证方法用ab -n 100 -c 10 http://localhost:8080/需先改造成 HTTP或写 Python 脚本并发发起 10 个 Socket 连接观察服务端日志是否出现ClientHandler-1、ClientHandler-2等线程名且响应时间稳定在 200ms 内。5.2 档案文件上传用 Socket 流式传输替代路径字符串当前file_path是客户端传来的字符串存在安全隐患如传/etc/passwd。真实场景应支持文件上传。改造思路客户端选择文件后先发UPLOAD命令 文件元信息{filename:report.pdf,size:102400}服务端校验通过后返回READY客户端再发二进制流服务端接收流保存到./uploads/目录生成唯一文件名UUID再插入数据库关键代码服务端接收流// server/handler/UploadArchiveHandler.java DataInputStream dis new DataInputStream(clientSocket.getInputStream()); String filename dis.readUTF(); // 先读文件名 long fileSize dis.readLong(); // 再读大小 File uploadDir new File(./uploads/); if (!uploadDir.exists()) uploadDir.mkdirs(); File targetFile new File(uploadDir, UUID.randomUUID().toString() _ filename); try (FileOutputStream fos new FileOutputStream(targetFile)) { byte[] buffer new byte[8192]; long totalRead 0; while (totalRead fileSize) { int read dis.read(buffer, 0, (int) Math.min(buffer.length, fileSize - totalRead)); if (read -1) break; fos.write(buffer, 0, read); totalRead read; } } // 插入数据库file_path 存 targetFile.getAbsolutePath()参数说明readUTF()读取 UTF-8 编码的文件名自动处理长度readLong()读取文件大小用于流控Math.min(buffer.length, fileSize - totalRead)防止最后一次读取越界。这是 Java Socket 文件传输的标准写法。5.3 权限分级从单账号admin到角色-权限模型当前登录凭证硬编码无权限控制。可快速引入 RBAC基于角色的访问控制数据库新增role表id,name和user_role关联表Archive表加owner_id字段关联用户客户端登录后服务端返回role字段UI 根据角色动态隐藏/禁用按钮如人事专员只能删自己上传的档案权限校验伪代码QueryArchiveHandler.java// 查询时加 owner 过滤 if (staff.equals(userRole)) { sql AND owner_id ?; ps.setLong(paramIndex, userId); }落地技巧不必重写整个认证模块。只需在LoginHandler中登录成功后查SELECT r.name FROM user u JOIN user_role ur ON u.idur.user_id JOIN role r ON ur.role_idr.id WHERE u.username?将role存入Session对象自定义UserSession类后续所有 Handler 从Session取角色判断。从动手到验证2 小时内可完成。从那以后我每次带学生做 Java 课程设计都会先让他们跑通这个档案系统再带着他们一起改这三处——不是为了炫技而是让他们亲手摸到“并发”、“文件传输”、“权限控制”这三个词背后的字节、线程、SQL。它们不是八股文里的名词解释而是threadPool.submit()之后日志里跳出来的线程名是dis.readUTF()读到的文件名是WHERE owner_id ?多加的那一个问号。希望帮到你。本文还有配套的精品资源点击获取