简介这份资源是一套面向高校Java课程实验设计的完整源码方案围绕C/S架构的小型档案管理系统展开适合正在完成面向对象与多线程综合实验的学生及需要Socket通信实战案例的开发者参考。系统涵盖客户端与服务器端两个子系统涉及用户登录与权限分级、档案属性入库、文件上传下载、条件查询以及用户信息增删改查等核心功能服务器端采用多线程响应多客户端请求并配合MySQL完成数据持久化。压缩包共47个文件约1.37MB以14个java源文件与15个class编译文件为主体另含sql建库脚本、jar数据库驱动、readme说明及实验指导文档目录按client与server分模块组织结构清晰。目前已有875人学习下载。读者可据此获得一套可直接运行的实验参考实现理解Socket通信、多线程处理与数据库操作的整合思路并对照实验要求快速搭建与调试自己的档案管理系统。1. 从一份“能跑起来”的 Java 实验设计说起很多同学做 Java 实验设计时最怕的不是写代码而是“写完了跑不起来”。尤其是档案管理这类题目表面看只是增删改查真动手才发现数据库怎么建、Socket 怎么通信、客户端和服务端怎么分工、文件上传怎么存每一步都能卡住半天。这份 Java 实验设计实现的小型档案管理系统就是冲着“能跑通、能演示、能交作业”去的。它用 Java 写核心业务用 SQL 做数据持久化用 Socket 做客户端与服务端通信把档案录入、查询、修改、删除、借阅记录这些功能串成了一条完整链路。适合正在做课程设计、实验报告或者想找一个 Java 网络编程加数据库练手项目的开发者。下面我按实际拆项目的顺序把结构、跑法和改法讲清楚。2. 项目定位与技术选型为什么是 Java SQL Socket2.1 这个系统到底解决什么问题档案管理系统的核心诉求很朴素把纸质档案的信息电子化让管理员能快速录入、检索、修改和统计。这份实验设计没有一上来就堆 Spring Boot 或前端框架而是用最原始的 Java SE 加 Socket 通信把“网络编程”和“数据库操作”两个实验重点都覆盖了。服务端负责监听端口、处理客户端请求、操作数据库客户端负责界面展示和发送指令。两边通过自定义协议传输数据比如用特定分隔符拼接字段或者直接传序列化对象。这种设计的好处是依赖少、结构清晰适合实验环境。你不需要配 Tomcat也不需要懂前端构建只要 JDK 和 MySQL 就能跑。常见做法是服务端启动后先初始化数据库连接池然后循环 accept 客户端连接每个连接开一个线程处理。客户端启动后连上服务端通过控制台或 Swing 界面输入操作。对于实验设计来说这种“看得见摸得着”的通信过程比直接调 REST API 更能体现网络编程的理解。2.2 技术栈拆解与版本建议项目涉及的技术点可以拆成四块Java 基础语法与面向对象、JDBC 数据库操作、Socket 网络通信、多线程并发处理。数据库用 MySQL 5.7 或 8.0 都行驱动包用 mysql-connector-java。JDK 建议 1.8 以上因为要用到 Lambda 表达式和 Stream 简化集合操作。开发工具用 IDEA 或 Eclipse 都可以实验报告里通常要求贴关键代码和运行截图。技术模块具体选型在项目中的角色编程语言Java SE 8业务逻辑、网络通信、界面数据库MySQL 5.7/8.0存储档案、用户、借阅记录通信方式Socket TCP客户端与服务端数据交换并发处理多线程每个客户端连接独立线程界面Swing 或控制台用户交互入口构建工具无或 Maven依赖管理可选注意如果实验环境不能装 MySQL可以换成 SQLite只需改 JDBC URL 和驱动类名SQL 语法基本不用动。2.3 目录结构长什么样一份典型的实验设计源码包目录不会太复杂但该有的分层都有。下面是我拆过的类似项目里最常见的结构ArchiveSystem/ ├── src/ │ ├── com/archive/server/ │ │ ├── ServerMain.java # 服务端启动入口 │ │ ├── ClientHandler.java # 每个客户端连接的处理线程 │ │ └── ArchiveService.java # 业务逻辑层 │ ├── com/archive/client/ │ │ ├── ClientMain.java # 客户端启动入口 │ │ └── ArchiveView.java # Swing 界面或控制台菜单 │ ├── com/archive/dao/ │ │ ├── ArchiveDao.java # 档案表 CRUD │ │ ├── UserDao.java # 用户表操作 │ │ └── DBUtil.java # 数据库连接工具 │ ├── com/archive/model/ │ │ ├── Archive.java # 档案实体类 │ │ ├── User.java # 用户实体类 │ │ └── BorrowRecord.java # 借阅记录实体类 │ └── com/archive/common/ │ └── Protocol.java # 通信协议常量与工具方法 ├── sql/ │ └── init.sql # 建库建表脚本 ├── lib/ │ └── mysql-connector-java.jar # 数据库驱动 └── README.md这个结构把服务端、客户端、数据访问、实体模型、公共协议分开了。改代码时定位很快要加字段就改 model 和 dao要改通信格式就动 Protocol 和 ClientHandler。实验报告里画架构图直接按这个分层画就行。3. 核心模块拆解从数据库到 Socket 的完整链路3.1 数据库设计与建表脚本档案管理系统的数据库一般至少三张表档案表、用户表、借阅记录表。档案表存档案编号、名称、类别、存放位置、状态等用户表存管理员账号密码借阅记录表存谁在什么时候借了哪个档案、什么时候还。建表时注意主键、外键和索引档案编号通常加唯一索引避免重复录入。-- 档案表 CREATE TABLE archive ( id INT PRIMARY KEY AUTO_INCREMENT, archive_no VARCHAR(32) NOT NULL UNIQUE COMMENT 档案编号, name VARCHAR(128) NOT NULL COMMENT 档案名称, category VARCHAR(64) COMMENT 档案类别, location VARCHAR(128) COMMENT 存放位置, status TINYINT DEFAULT 1 COMMENT 1在库 0借出, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(16) DEFAULT admin ); -- 借阅记录表 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, archive_id INT NOT NULL, user_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME, FOREIGN KEY (archive_id) REFERENCES archive(id), FOREIGN KEY (user_id) REFERENCES user(id) );建表脚本放在 sql/init.sql 里服务端启动时可以选择自动执行也可以手动在 MySQL 客户端跑一遍。字段类型和长度按实际需求调比如档案名称如果可能很长就把 VARCHAR(128) 加大。status 字段用 TINYINT 表示在库和借出两种状态查询时直接 where status 1 就能筛出可借档案。3.2 数据库连接工具类 DBUtilJDBC 连接如果每次操作都 DriverManager.getConnection代码重复且性能差。常见做法是写一个 DBUtil用静态代码块加载驱动提供获取连接和关闭资源的方法。实验设计里不一定要上连接池但至少把 URL、用户名、密码抽成常量方便换环境。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/archive_db?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(数据库驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭忽略异常 try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }这里的 URL 带了 useSSLfalse 和 serverTimezoneUTC是 MySQL 8.0 驱动常见的坑不加可能报时区错误。用户名密码按自己环境改。close 方法把三个资源一起关调用时不用写一堆 try-catch。如果实验要求用 PreparedStatement把 Statement 换成 PreparedStatement 即可关闭逻辑一样。3.3 档案 DAO 层增删改查的具体实现DAO 层是业务逻辑和数据库之间的桥梁。以档案查询为例支持按编号、名称、类别模糊查。下面这段代码展示了插入和查询两个核心方法。public class ArchiveDao { public boolean insert(Archive archive) { String sql INSERT INTO archive(archive_no, name, category, location, status) VALUES(?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, archive.getArchiveNo()); ps.setString(2, archive.getName()); ps.setString(3, archive.getCategory()); ps.setString(4, archive.getLocation()); ps.setInt(5, archive.getStatus()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public ListArchive search(String keyword) { ListArchive list new ArrayList(); String sql SELECT * FROM archive WHERE archive_no LIKE ? OR name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); ResultSet rs ps.executeQuery(); while (rs.next()) { Archive a new Archive(); a.setId(rs.getInt(id)); a.setArchiveNo(rs.getString(archive_no)); a.setName(rs.getString(name)); a.setCategory(rs.getString(category)); a.setLocation(rs.getString(location)); a.setStatus(rs.getInt(status)); list.add(a); } } catch (SQLException e) { e.printStackTrace(); } return list; } }insert 方法用 PreparedStatement 防止 SQL 注入参数按占位符顺序设置。search 方法用 LIKE 做模糊匹配keyword 前后加 %。try-with-resources 自动关闭 Connection 和 PreparedStatement比手动 close 更安全。如果查询结果为空返回空列表而不是 null调用方不用做额外判空。3.4 Socket 通信协议设计客户端和服务端之间怎么传数据是实验设计里最能体现设计能力的地方。简单做法是定义一套文本协议比如每条消息以换行结束字段之间用竖线分隔。第一段是命令字后面跟参数。服务端解析命令字后分发到对应处理方法。public class Protocol { public static final String CMD_LOGIN LOGIN; public static final String CMD_ADD ADD; public static final String CMD_SEARCH SEARCH; public static final String CMD_DELETE DELETE; public static final String SEPARATOR \\|; // 拼接消息CMD|参数1|参数2 public static String build(String cmd, String... params) { StringBuilder sb new StringBuilder(cmd); for (String p : params) { sb.append(|).append(p); } return sb.toString(); } // 解析消息 public static String[] parse(String line) { return line.split(SEPARATOR); } }客户端发送ADD|A001|人事档案|人事类|A区3排|1服务端收到后按竖线切分第一段是 ADD后面依次是档案编号、名称、类别、位置、状态。这种协议简单直观调试时直接打印字符串就能看懂。缺点是字段里不能出现竖线如果档案名称可能包含特殊字符可以换成 JSON 或 Base64 编码。实验设计里用竖线分隔足够报告里也好写。3.5 服务端多线程处理客户端连接服务端主线程只负责 accept每来一个客户端就开一个新线程处理。这样多个客户端可以同时操作不会互相阻塞。下面是一个简化的服务端入口和处理器。public class ServerMain { public static void main(String[] args) throws IOException { ServerSocket serverSocket new ServerSocket(8888); System.out.println(服务端启动监听 8888 端口); while (true) { Socket socket serverSocket.accept(); new Thread(new ClientHandler(socket)).start(); } } } public class ClientHandler implements Runnable { private Socket socket; private ArchiveService service new ArchiveService(); public ClientHandler(Socket socket) { this.socket socket; } Override public void run() { try (BufferedReader in new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter out new PrintWriter(socket.getOutputStream(), true)) { String line; while ((line in.readLine()) ! null) { String[] parts Protocol.parse(line); String result service.handle(parts); out.println(result); } } catch (IOException e) { e.printStackTrace(); } } }ServerSocket 监听 8888 端口accept 返回后立刻交给新线程。ClientHandler 里用 BufferedReader 按行读PrintWriter 按行写自动 flush。service.handle 根据命令字调用不同 DAO 方法返回结果字符串。客户端收到结果后展示。这里要注意 socket 关闭和线程退出客户端断开后 readLine 返回 null循环结束线程自然终止。4. 跑起来环境准备、启动顺序与常见报错4.1 环境准备清单跑这个项目之前先把环境对齐。JDK 装 1.8 或以上命令行 java -version 能输出就行。MySQL 装好并启动用 Navicat 或命令行建一个 archive_db 数据库。把 mysql-connector-java 的 jar 包放到 lib 目录IDEA 里右键 Add as Library。如果项目用 Mavenpom.xml 里加对应依赖即可。检查项验证方式常见问题JDKjava -version版本低于 1.8 编译报错MySQLmysql -u root -p 能登录服务未启动或密码错误驱动包lib 目录下有 jar忘记 Add as Library端口8888 未被占用被其他程序占用导致启动失败数据库archive_db 存在未执行 init.sql4.2 启动顺序与验证先执行 sql/init.sql 建表再启动服务端 ServerMain控制台输出“服务端启动监听 8888 端口”。然后启动客户端 ClientMain输入服务端 IP本机就填 127.0.0.1和端口 8888。登录后尝试新增一条档案再查询看是否能查到。如果服务端控制台打印了收到的命令和返回结果说明通信链路通了。# 编译如果不用 IDE javac -d out -cp lib/mysql-connector-java.jar src/com/archive/**/*.java # 启动服务端 java -cp out:lib/mysql-connector-java.jar com.archive.server.ServerMain # 启动客户端 java -cp out:lib/mysql-connector-java.jar com.archive.client.ClientMainWindows 下 classpath 分隔符用分号Linux 和 Mac 用冒号。如果报 ClassNotFoundException: com.mysql.cj.jdbc.Driver检查驱动包是否在 classpath 里。如果报 Access denied for user检查 DBUtil 里的用户名密码。4.3 常见报错与排查现象一服务端启动报端口被占用。原因可能是 8888 端口已被其他程序监听。解决方法是换端口比如改成 9999同时改客户端连接端口。用 netstat -ano | findstr 8888 可以查占用进程。现象二客户端连不上服务端。先确认服务端是否已启动再确认 IP 和端口是否填对。如果服务端和客户端不在同一台机器要确保防火墙放行对应端口。实验环境通常本机跑填 127.0.0.1 最稳。现象三中文乱码。Socket 传输时如果两端编码不一致中文会变问号。常见做法是在 BufferedReader 和 PrintWriter 构造时指定 UTF-8或者用 OutputStreamWriter 包一层并指定字符集。数据库连接 URL 里也可以加 characterEncodingutf8。现象四查询结果为空但数据库里明明有数据。检查 SQL 里的 LIKE 拼接是否正确以及是否用了事务未提交。如果插入后没 commit其他连接查不到。JDBC 默认自动提交但如果你手动开了事务记得 commit。现象五多客户端同时操作时数据错乱。比如两个客户端同时借同一本档案。原因是业务层没有加锁或状态检查。解决方式是在借阅方法里先查状态再用 update ... where status 1 的原子操作更新根据受影响行数判断是否借阅成功。5. 二次开发与改造从实验设计到能拿得出手的项目5.1 把控制台换成 Swing 界面实验设计如果只交控制台演示效果一般。改成 Swing 界面工作量不大但观感提升明显。核心思路是把客户端原来的 Scanner 输入换成文本框和按钮把服务端返回的结果展示到 JTable 或 JTextArea。通信层不用动只改客户端展示层。// 简化示例点击查询按钮后发送命令并展示结果 searchBtn.addActionListener(e - { String keyword keywordField.getText().trim(); String cmd Protocol.build(Protocol.CMD_SEARCH, keyword); out.println(cmd); // 发送到服务端 String response in.readLine(); // 读取返回 // 假设返回格式编号,名称,类别;编号,名称,类别 String[] rows response.split(;); DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); for (String row : rows) { model.addRow(row.split(,)); } });这里的关键是客户端和服务端的协议要保持一致。如果服务端返回的是竖线分隔客户端就按竖线切如果改成 JSON两边都要改。建议在 Protocol 里加一个 formatResult 方法统一返回格式避免两边各写各的。5.2 加一个简单的权限控制现在的用户表只有用户名和密码可以加一个 role 字段区分管理员和普通用户。登录成功后服务端把角色返回给客户端客户端根据角色决定是否显示删除按钮。服务端在处理删除命令时也要再校验一次角色防止客户端绕过界面直接发命令。// 服务端处理删除时校验角色 if (Protocol.CMD_DELETE.equals(cmd)) { if (!admin.equals(currentUser.getRole())) { return ERROR|无权限删除; } // 执行删除 }这种“界面隐藏 服务端校验”的双重控制是权限设计的基本习惯。实验报告里可以作为一个扩展点写。5.3 用 Maven 管理依赖和打包如果想让项目更容易分发可以改成 Maven 结构把 mysql-connector-java 写进 pom.xml用 mvn package 打成可执行 jar。这样别人拿到 jar 包配好数据库就能跑不用手动配 classpath。dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency打包时用 maven-shade-plugin 或 maven-assembly-plugin 把依赖一起打进去生成一个 fat jar。启动命令变成 java -jar archive-system.jar对实验验收来说更省事。5.4 一个我踩过的坑别在循环里开连接早期写 DAO 时我在 for 循环里每次调用 DBUtil.getConnection()查一百条数据就开一百次连接跑得慢还容易把数据库连接数占满。后来改成一次连接、批量查询或者用连接池问题才解决。从那以后我每次写数据库操作都强制走一遍“连接是否复用”的检查宁可多写几行代码也不在循环里反复开连接。希望这个习惯能帮到你。本文还有配套的精品资源点击获取