Java档案管理系统源码拆解:基于Socket与JDBC的C/S架构实战

简介:这份基于Java的小型档案管理系统完整源码,面向高校软件工程、计算机等专业的课程实验、实训与课设,是一套综合运用C/S架构、Socket通信、多线程与关系数据库的典型项目。系统包含独立的客户端与服务器端,用户登录后按系统管理人员、档案录入人员、档案浏览人员三类角色区分权限;支持档案文件信息录入、文件上传、条件查询、下载、删除,以及用户基本信息和密码的查询与修改;服务器端采用多线程处理机制,可同时响应多个客户端的数据请求,并对应提供SQL建表脚本与MySQL驱动,便于直接配置运行。压缩包共47个文件,其中含14个Java源文件、15个class编译文件、2个SQL脚本、1个jar依赖包、docx实验指导书、txt说明及工程配置文件等,整体仅1.37MB,目录结构清晰,适合导入IDE对照学习。已有875人学习下载,特别适合需要完成Java实验设计或理解Socket多线程文件传输、用户权限控制等要点的开发者,既能作为课设模板,也能扩展为毕业设计或面试项目。

1. 一项看似简单的需求,为什么值得拆这套代码

接手某实验室的档案整理工作时,我面对的是几十个年份的纸质档案,和一张转存了三轮的 Excel 台账。调一份三年前的验收单,翻表格的时间比翻柜子还长。这套 Java 实验设计里的小型档案管理系统,解决的正是这个问题:它把档案的录入、查询、修改、删除完整地走了一遍,用 SQL 做持久化,用 Socket 打通客户端与服务端,是一份能把网络编程和 JDBC 同时看懂的 java 源码。适合刚学完 Java SE 的在校生、要交实验设计报告的人,以及想快速拆懂 C/S 架构的开发者。

2. 系统架构与技术选型:一份不依赖框架的 C/S 骨架

2.1 三层架构:客户端、服务端与数据库的链路关系

拿到源码后第一件事不是跑起来,而是先看清“界面 → 网络 → 数据库”这条链路是怎么打通的。这套档案管理系统的结构非常典型,整个工程被分成四个包:com.archive.client负责 Swing 界面和用户交互,com.archive.server负责监听端口和分发请求,com.archive.dao封装所有 JDBC 数据操作,com.archive.common放协议常量、消息对象和工具类。

数据流是这样的:客户端采集用户输入 → 组装成一条文本报文 → 通过 Socket 发送到服务端 → 服务端按命令字解析 → 调用 DAO 层执行 SQL → 把结果逐层封装返回 → 客户端解析响应并刷新界面。整个过程没有引入 Spring、Netty 或 ORM 框架,连数据库连接都是最原始的DriverManager。

这种选型在实验设计里是刻意的。治理需求能撑起一个完整的网络通信加数据库操作链路,同时又不把关键细节藏进框架黑匣子。Spring 全家桶确实省事,但实验考核点通常落在语言基础和网络编程上,用纯 Java SE 反而能看清每一行代码在干什么。如果你已经有了 Spring Boot 的经验,再看这套代码会觉得效率上很原始,但学习价值恰恰在这里。

2.2 功能模块与角色权限:管理员与档案员的差异

小型档案管理系统虽然叫“小型”,但功能点并不少。登录认证、档案新增、档案查询(精确和模糊)、档案修改、档案删除、档案借阅登记、操作日志,这些模块全都具备。每个功能对应一个命令字,客户端发命令,服务端执行,不存在页面跳转式的路由。

用户体系按角色分为管理员和档案员两级,权限差异落在命令分发阶段。管理员可以执行全部命令,档案员只能执行登录、查询和借阅登记。权限判断不放在界面层,而是放在服务端做——这一点很像生产环境的做法,客户端只能决定“发什么命令”,不能决定“能不能执行”。

功能点管理员档案员
登录 / 修改密码支持支持
档案录入 / 修改 / 删除支持不支持
档案查询支持支持
借阅登记 / 归还支持支持
操作日志查看支持不支持

数据库方面只用了两张核心表和一张借阅记录表。archive_user存用户信息,archive_info存档案台账,archive_borrow存借阅流水。我没有在一开始就把表设计得过于复杂,因为实验项目最关键的是把链路跑通,表结构冗余了反而增加排查成本。

2.3 通信协议设计:先定协议,再写代码

Socket 通信最忌讳的是客户端和服务端各说各话。这套源码里把所有命令约定写在了common包的常量类中,客户端和服务端共用同一份协议定义,避免出现“客户端发了 A 格式、服务端按 B 格式解析”的尴尬。

命令格式采用竖线分隔:命令字|参数1|参数2#请求ID。#后面的请求 ID 由客户端生成,用来把响应和请求配对,服务端处理完原样带回。响应格式固定为:响应码|消息|数据#请求ID。响应码用OK和ERR区分成功失败,消息是给人看的描述,数据段是给客户端解析的业务内容。

public class Protocol { // 命令字定义 public static final String CMD_LOGIN = "LOGIN"; public static final String CMD_ADD_ARCHIVE = "ADD_ARCHIVE"; public static final String CMD_QUERY_ARCHIVE = "QUERY_ARCHIVE"; public static final String CMD_UPDATE_ARCHIVE = "UPDATE_ARCHIVE"; public static final String CMD_DELETE_ARCHIVE = "DELETE_ARCHIVE"; // 分隔符 public static final String SEP_CMD = "\\|"; public static final String SEP_TAIL = "#"; // 响应码 public static final String RESP_OK = "OK"; public static final String RESP_ERR = "ERR"; }

这段代码的逻辑很直白:把命令字和分隔符统一成常量,任何地方需要拼报文或拆报文都引用这里的定义。SEP_CMD写成"\\|"是因为后面要配合String.split()使用,竖线在正则里是特殊字符,必须转义。SEP_TAIL是请求 ID 的分隔符,单独拆出来是为了让解析逻辑不需要关心请求 ID 里的内容格式。

这种自定义文本协议在生产系统中很常见,实验项目用它最合适:调试时能看到明文的报文,出错时一眼就能定位是客户端拼错了还是服务端拆错了。代价是报文体积略大、需要自己做序列化解析,但小型系统完全够用。

3. 核心代码拆解:一次档案查询从点击到返回的完整链路

3.1 客户端消息构建:把按钮事件变成一条报文

客户端的核心工作不是画界面,而是把用户操作翻译成服务端认识的报文。以查询功能为例:用户在文本框输入关键字,点击“查询”按钮,监听器里做三件事——构建报文、发送请求、解析响应并刷新表格。

// 查询按钮监听器 searchBtn.addActionListener(e -> { String keyword = keywordField.getText().trim(); if (keyword.isEmpty()) { JOptionPane.showMessageDialog(this, "请输入查询关键字"); return; } // 构建报文:QUERY_ARCHIVE|关键字#请求ID String reqId = String.valueOf(System.currentTimeMillis()); String request = "QUERY_ARCHIVE|" + keyword + "#" + reqId; String response = sendAndReceive(request); // 解析响应:OK|消息|数据#请求ID if (response.startsWith("OK|")) { List<ArchiveVO> list = parseArchives(response); refreshTable(list); } else { JOptionPane.showMessageDialog(this, "查询失败: " + response); } });

sender()方法里做的事情比较固定:建立连接、设置超时、写报文、读响应、关闭连接。超时设置是关键参数,这里给的是 5000 毫秒,意味着服务端如果 5 秒内没返回,客户端会抛出SocketTimeoutException,不会无限等下去。

需要说明的是,在 Swing 界面里直接做网络请求会阻塞事件线程,拖动画面的问题在数据量大时非常明显。这套源码的做法简单直接——同步阻塞,适合实验场景;想要更顺滑的体验,把网络请求丢进SwingWorker的后台线程即可,属于后续改造点。

3.2 服务端线程池与命令分发:请求进来后发生了什么

服务端的核心是一个监听循环加一个命令分发器。ServerSocket监听 8899 端口,每接受一个连接就交给线程池处理,不在main线程里做任何业务操作。

// ArchiveServer.java ServerSocket serverSocket = new ServerSocket(8899); ExecutorService pool = Executors.newFixedThreadPool(8); System.out.println("档案服务端已启动,端口: 8899"); while (true) { Socket socket = serverSocket.accept(); pool.execute(new ClientHandler(socket)); }

客户端连接被线程池接走之后,ClientHandler里先读取一行报文,然后调用Dispatcher.dispatch()分发。分发器的实现是典型的switch-case按命令字路由,每个命令对应一个处理逻辑。

// Dispatcher.java public static String dispatch(String request, UserVO currentUser) { // 按 # 拆出请求 ID,按 | 拆出命令和参数 String[] tailParts = request.split("#"); String reqId = tailParts.length > 1 ? tailParts[1] : ""; String[] parts = tailParts[0].split("\\|"); String cmd = parts[0]; switch (cmd) { case Protocol.CMD_QUERY_ARCHIVE: String keyword = parts.length > 1 ? parts[1] : ""; return archiveDao.queryByKeyword(keyword, reqId); case Protocol.CMD_ADD_ARCHIVE: return handleAddArchive(parts, currentUser, reqId); default: return "ERR|未知命令#" + reqId; } }

这里有两个参数值得留意。第一个是线程池大小 8:实验项目这个数量足够,生产环境需要根据机器的 CPU 核数和服务端瓶颈重新评估。第二个是currentUser——它从哪来?每个ClientHandler处理登录请求后,会把用户对象放到一个ThreadLocal或者会话 Map 里,后续命令都带着身份走。权限判断也发生在这里:如果当前用户是档案员,handleAddArchive会直接返回“无权限”响应。

调度这块最怕的是解析顺序搞反。报文格式是“命令|参数#请求ID”,一定先按#拆出请求 ID,再按|拆命令,顺序反了就会把请求 ID 当成参数的一部分,导致命令被篡改。

3.3 JDBC 数据访问:PreparedStatement 的查询与参数绑定

DAO 层最值得讲的是参数绑定。查询档案时用户输入的关键字要拼进 SQL 的 LIKE 子句,如果直接用字符串拼接,遇到单引号就会炸掉,也就是典型的 SQL 注入。这套源码的 DAO 层全部使用PreparedStatement,一条查询的完整写法如下:

// ArchiveDao.java public List<ArchiveVO> queryByKeyword(String keyword) { List<ArchiveVO> list = new ArrayList<>(); String sql = "SELECT id, archive_no, title, category, location, archive_date, remark " + "FROM archive_info " + "WHERE title LIKE ? OR archive_no LIKE ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String likePattern = "%" + keyword + "%"; ps.setString(1, likePattern); ps.setString(2, likePattern); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { ArchiveVO vo = new ArchiveVO(); vo.setId(rs.getInt("id")); vo.setArchiveNo(rs.getString("archive_no")); vo.setTitle(rs.getString("title")); vo.setCategory(rs.getString("category")); vo.setLocation(rs.getString("location")); vo.setArchiveDate(rs.getDate("archive_date")); vo.setRemark(rs.getString("remark")); list.add(vo); } } } catch (SQLException e) { e.printStackTrace(); } return list; }

逻辑说明:SQL 里用两个?占位,setString(1, likePattern)把“%关键字%”绑定到第一个占位符,第二个LIKE条件绑定同一个值。参数绑定后,即使关键字里带了'或;,也只会被当作普通字符串处理,不会被解析成 SQL 语句的一部分,这就是它比字符串拼接安全的原因。

参数方面值得注意的还有两点。第一是try-with-resources的写法:Connection、PreparedStatement、ResultSet都实现了AutoCloseable,代码块结束后自动关闭,不用手动写finally去释放资源。第二是rs.next()结果映射时的空值问题——如果remark字段允许为 NULL,rs.getString("remark")返回的是null,放进 VO 后在界面上显示为空白,不会报错,但如果你在别处直接调用remark.length()就会空指针。生产级的 DAO 层通常会对可空字段做默认值处理,这套源码交给界面层去容忍 null,属于简化处理。

4. 编译、配置与运行:把源码变成能点击的系统

4.1 项目目录结构:源码、驱动、脚本分别放在哪里

拿到源码包后先看目录,一个结构规整的 Java 实验项目通常长这样:

archive-manager/ ├─ src/ │ ├─ com/archive/client/ // Swing 界面与客户端交互 │ ├─ com/archive/server/ // 服务端监听与命令分发 │ ├─ com/archive/common/ // 协议常量、工具类 │ ├─ com/archive/dao/ // JDBC 数据访问层 │ └─ com/archive/vo/ // 值对象(价值对象) ├─ lib/ │ └─ mysql-connector-java-8.0.30.jar ├─ conf/ │ └─ db.properties ├─ sql/ │ └─ init.sql ├─ bin/ │ ├─ start-server.bat / start-server.sh │ └─ start-client.bat / start-client.sh └─ README.md

src目录按包名分层,lib存放 MySQL 驱动依赖。这里容易遇到一个坑:有些人为了方便会把驱动 jar 复制进src目录,IDE 里一编译就报“找不到类”。正确做法是让驱动 jar 留在lib中,通过构建路径或命令行-cp引用。

bin下的启动脚本做的是同一件事:先把src下所有.java文件编译进out目录,再带上conf和lib内容启动指定的主类。conf/db.properties是数据库连接配置,也是跑起来之前必须改的地方。

4.2 数据库初始化与配置项:改三处就能连上自己的库

数据库初始化脚本写在sql/init.sql里。建表语句和插入默认管理员的语句都包含在内,连库名都帮你定好了:archive_db。

CREATE DATABASE IF NOT EXISTS archive_db DEFAULT CHARACTER SET utf8mb4; USE archive_db; CREATE TABLE archive_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(10) NOT NULL ); CREATE TABLE archive_info ( id INT PRIMARY KEY AUTO_INCREMENT, archive_no VARCHAR(50) NOT NULL, title VARCHAR(200) NOT NULL, category VARCHAR(50), location VARCHAR(100), archive_date DATE, remark VARCHAR(500) ); INSERT INTO archive_user (username, password, role) VALUES ('admin', 'admin123', 'ADMIN');

配置路径上只需动三个参数。打开conf/db.properties,按本机环境改数据库地址、用户名和密码:

jdbc.url=jdbc:mysql://localhost:3306/archive_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai jdbc.user=root jdbc.password=你的数据库密码
配置键示例值作用
jdbc.urljdbc:mysql://localhost:3306/archive_db连接地址,含数据库名
jdbc.userroot数据库用户名
jdbc.password你的数据库密码对应密码

两个容易被忽略的 URL 参数必须说明。characterEncoding=UTF-8保证从数据库读出的中文字符不乱码,serverTimezone=Asia/Shanghai解决 MySQL 8.x 与 JDBC 驱动之间的时区报错。这两项任何一个缺失,跑起来之后要么中文变成问号,要么直接抛The server time zone value异常。

4.3 启动步骤与验证路径:先服务端后客户端

命令行的编译和启动建议直接看bin目录下的脚本,手动执行的写法也在这里列一下:

# 编译全部源码 javac -encoding UTF-8 -cp "lib/*" -d out src/com/archive/**/*.java # 启动服务端 java -cp "out;lib/*;conf" com.archive.server.ArchiveServer # 新开终端,启动客户端 java -cp "out;lib/*;conf" com.archive.client.ArchiveClient

在 Windows 下 classpath 分隔符是分号,Linux / macOS 下是冒号,脚本已分别处理。成功启动后,服务端控制台会打印监听信息,客户端会弹出登录窗口。先用admin / admin123登录,录入一条档案后按标题关键字查询,如果能查到并展示在表格中,说明整条链路已经打通。修改存放位置和删除功能可以用另一条档案再做一遍,顺便看服务端日志里每个命令的打印时间。

提示:无论用 IDE 还是命令行,都先把数据库脚本执行干净再启动服务端,否则第一次连数据库就会因为表不存在而报错。

5. 避坑排查:Socket 与 JDBC 最容易踩的五个坑

5.1 中文乱码:界面上一片问号

现象:档案标题里的中文在客户端表格中显示成???,服务端日志里的中文也是乱码。

原因:两端编码不一致。客户端发送报文时用getBytes()默认平台编码(Windows 下是 GBK),服务端用new InputStreamReader(socket.getInputStream())按平台编码读取,两边一旦错位,中文字符就全部变成问号。

解决:两端强制指定 UTF-8,发送和读取都带编码:

// 发送端 OutputStream os = socket.getOutputStream(); os.write((request + "\n").getBytes(StandardCharsets.UTF_8)); // 接收端 BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));

顺带检查编译时是否加了-encoding UTF-8参数,IDE 里则确认工程编码是 UTF-8。编码问题是最磨人的,因为它不一定每次都报错,往往是数据量大了才爆发,排查时优先看两端编码。

5.2 服务端“卡死”:粘包半包把请求拆坏了

现象:客户端偶尔发两条命令,服务端只收到一条拼在一起的内容;或者某条命令很短但服务端一直读不到结束符,界面就像卡死一样。

原因:TCP 是流协议,没有消息边界,两条命令可能被一次读到(粘包),也可能一条命令被拆成两段到达(半包)。用readLine()读行时,如果服务端收到的字节里没有换行符,就会一直等下去。

解决:在协议里加长度前缀。发送前先计算报文总长度的字节数,把长度写在报文最前面,接收端先读固定长度头部,再根据长度读完整消息体。这也是生产环境中主流协议的基本思路。

// 发送端:8 字节长度头 + 报文 byte[] body = request.getBytes(StandardCharsets.UTF_8); String header = String.format("%08d", body.length); os.write((header + new String(body, StandardCharsets.UTF_8) + "\n").getBytes(StandardCharsets.UTF_8)); // 接收端:先读 8 字节长度头,再读指定长度的消息体 char[] lenBuf = new char[8]; reader.read(lenBuf, 0, 8); int bodyLen = Integer.parseInt(new String(lenBuf)); char[] bodyBuf = new char[bodyLen]; reader.read(bodyBuf, 0, bodyLen);

自定义文本协议在简单场景下够用,但传输层不可控因素太多。如果你在改造中引入更复杂的报文结构,直接换成 JSON 序列化加长度头,能省掉大量手工拆包逻辑。

5.3 档案名里的单引号把 SQL 炸了

现象:查询或录入包含'的档案名时,程序抛SQLSyntaxErrorException,服务端日志里能看到完整的 SQL 语句。

原因:SQL 拼接了用户输入。WHERE title LIKE '%' + keyword + '%'遇到关键字O'Brien时,SQL 变成WHERE title LIKE '%O'Brien%',单引号提前结束了字符串,后面的内容就成了 SQL 语法的一部分。不只是报错,这也是 SQL 注入的入口。

解决:全部使用PreparedStatement参数绑定,让数据库驱动对参数做转义。代理层的setString()在发送前会正确处理特殊字符,不再走字符串拼接这条路。

5.4 端口占用:重启服务端就报 BindException

现象:第二次启动服务端时报java.net.BindException: Address already in use,服务端起不来。

原因:上一次运行的服务端进程没有被完全关闭,8899 端口还被它占着。IDE 停止运行时可能只杀掉了主线程,Socket 资源没有完全释放,或者之前关闭的窗口只是关界面没退进程。

解决:找到占用端口的进程,结束掉它再重启。Windows 命令是netstat -ano | findstr 8899,Linux 命令是lsof -i:8899,拿到 PID 后通过任务管理器或kill -9结束。如果端口不方便换进程,也可以在ArchiveServer里把端口改成 8890,但要记得同步改客户端的连接端口,两处不一致是最常见的翻车点。

5.5 驱动找不到:ClassNotFoundException

现象:服务端启动到Class.forName("com.mysql.cj.jdbc.Driver")时抛出ClassNotFoundException。

原因:MySQL 驱动 jar 没有进入运行时 classpath。有些人源包目录里没有lib文件夹,或者 IDE 里没有把 jar 添加到依赖,导致DriverManager找不到驱动类。

解决:把mysql-connector-java-8.0.30.jar放到lib目录,并确保启动命令带上了-cp "out;lib/*;conf"。注意lib/*通配符在部分老版本 JDK 中不识别,可以改成把 jar 文件的全路径写进去。检查方式很简单:启动前看服务端日志有没有加载驱动成功的输出,没看到就先确认 classpath。

6. 二次开发:让档案系统长出属于自己的接口

6.1 用 Command 接口统一扩展点

这套源码的分发逻辑是switch-case按命令字路由,功能一多,Dispatcher会越来越臃肿。改造第一步是引入命令模式:定义一个Command接口,把每个命令封装成独立对象,再让分发器通过一个Map查找命令处理器。

public interface Command { String execute(String[] args, String reqId, UserVO user); } // Dispatcher 内部维护命令注册表 private static final Map<String, Command> COMMANDS = new HashMap<>(); static { COMMANDS.put("QUERY_ARCHIVE", new QueryArchiveCommand()); COMMANDS.put("ADD_ARCHIVE", new AddArchiveCommand()); } // 新增功能只需注册一个新命令 COMMANDS.put("STATS_ARCHIVE", new StatsArchiveCommand());

业务逻辑做的大改动是:新增功能时不再改Dispatcher主方法,只要实现Command接口并在注册表加一行映射。代码结构更清晰,测试每个命令也可以写成独立单元测试。这套改造思路在小型项目里性价比很高,十分钟就能完成,后续扩展却方便很多。

6.2 验证二次开发最简单的方式

改完任何网络相关逻辑,先用一段脚本模拟大量请求,确认服务端稳定了再去做界面测试。我用的是循环发送查询命令,观察服务端日志的异常数来判断是否改动引入问题。

for i in $(seq 1 100); do echo "QUERY_ARCHIVE|档案$i#100$i" | nc localhost 8899 done

每个命令的请求 ID 不同,服务端日志中如果大量出现ERR或超时,说明服务端吞吐量下降或协议解析出错,这时候回头查代码,不要急着点界面。

从那以后我每次做完 Socket 通信模块,都会先用这种脚本压一遍请求,再打开界面做功能测试。这让我避开了好几次“界面没问题、多请求就崩”的尴尬。如果你要在这个系统上做自己的实验设计改造,建议也把这条习惯带上——先压接口,再过界面,希望帮到你。

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

相关新闻

用Simulink极点配置优化Buck变换器动态响应实操指南

用Simulink极点配置优化Buck变换器动态响应实操指南

如果你也遇到过这样的场面——Buck变换器负载突然加重&#xff0c;输出电压“咣”地凹下去一大块&#xff0c;然后带着几个明显的振荡周期才慢悠悠回到稳态——那你一定明白动态响应优化这件事有多扎心。这篇文章就是我从踩坑到跑通的一份完整实操记录&#xff1a;用Simulink搭…

2026/10/21 8:28:52 阅读更多 →
96张数据安全治理架构图:从模板到可汇报方案的实战指南

96张数据安全治理架构图:从模板到可汇报方案的实战指南

简介&#xff1a;这份可编辑PPT资料面向数据安全治理从业者、企业安全合规人员及信息安全学习者&#xff0c;系统梳理了数据安全治理与数据安全管理体系的整体架构。内容围绕安全体系、IT安全管理体系、组织体系、安全运行体系、访问控制、安全监控与审计、应用安全、数据安全管…

2026/10/21 8:28:52 阅读更多 →
6D位姿估计实践:DenseFusion逐像素融合的Python实现与工程避坑指南

6D位姿估计实践:DenseFusion逐像素融合的Python实现与工程避坑指南

简介&#xff1a;这是一份Python实现的DenseFusion六自由度物体姿态估计项目包&#xff0c;面向计算机视觉研究者和机器人抓取、增强现实等应用开发人员。核心方法通过融合彩色图像与深度图像&#xff0c;对物体位置和三轴旋转进行像素级稠密特征估算&#xff0c;从而在堆叠或遮…

2026/10/21 8:28:52 阅读更多 →

最新新闻

artcraft:拆解创意编程工具的分层架构与模块化设计

artcraft:拆解创意编程工具的分层架构与模块化设计

1. 从“artcraft”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“artcraft”这个标题&#xff0c;我脑子里蹦出来的第一反应是“艺术”加“手艺”的组合词。直觉告诉我&#xff0c;这不是一个纯技术框架的名字&#xff0c;也不是某个具体产品的品牌名&#xff0c;它…

2026/10/21 9:18:06 阅读更多 →
木头、树脂与金属线:一套从草图到作品的手作实验

木头、树脂与金属线:一套从草图到作品的手作实验

很多人问我&#xff0c;为什么我做的那些小物件&#xff0c;明明材料也就是木头、树脂、金属线这些东西&#xff0c;看起来却总比市面上买的“工艺品”多了一层说不清的味道。其实答案不复杂&#xff1a;因为我做的不是“产品”&#xff0c;而是把“艺术”和“craft”揉在一起的…

2026/10/21 9:18:06 阅读更多 →
开放式代码审查实践:从流程设计到团队协作的完整指南

开放式代码审查实践:从流程设计到团队协作的完整指南

代码审查这件事&#xff0c;我做了不少年头了。从最早一个人盯着 diff 看半天&#xff0c;到后来把审查流程嵌进团队的日常开发节奏&#xff0c;中间踩过的坑比写过的 bug 还多。最近几个月我一直在琢磨一套叫 open-code-review 的实践方案——说白了&#xff0c;就是把代码审查…

2026/10/21 9:18:06 阅读更多 →
artcraft:把艺术灵感化为可复制手工创作的完整方法论

artcraft:把艺术灵感化为可复制手工创作的完整方法论

artcraft 这个词&#xff0c;最早只是我工作台记事本上随手写下的一个代号&#xff0c;把 art 和 craft 硬拼在一起&#xff0c;也没什么特别含义。后来它慢慢长成一个完整的创作体系——用数字工具做设计&#xff0c;用双手完成实物&#xff0c;再把整个过程整理成能反复使用的…

2026/10/21 9:18:06 阅读更多 →
Ceph RBD删除后空间未释放?trash清理与对象回收实战指南

Ceph RBD删除后空间未释放?trash清理与对象回收实战指南

处理这种问题&#xff0c;我的第一反应是&#xff1a;先别急着扩容&#xff0c;也别怀疑删除操作失败。ceph df显示的空间没变化&#xff0c;十有八九是对象没被真正清掉&#xff0c;或者删除流程还没走完。标题里提到的“删除rbd的数据”&#xff0c;大概率是通过rbd rm或rbd …

2026/10/21 9:18:06 阅读更多 →
艺术院校计算机基础课教改:分层教学与案例驱动实践

艺术院校计算机基础课教改:分层教学与案例驱动实践

简介&#xff1a;这份资源是面向艺术类院校计算机基础课教师与教学研究者的教学探讨文档&#xff0c;围绕艺术院校学生文化基础薄弱、偏重感性思维、喜欢动手却抵触理论等特点&#xff0c;分析计算机基础课程在专业设置、教学目标与应用方式上的特殊性&#xff0c;并针对学生水…

2026/10/21 9:17:00 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/20 9:50:45 阅读更多 →