news 2026/10/7 13:07:26

Java Socket多线程银行排号系统源码解析与Swing GUI避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Socket多线程银行排号系统源码解析与Swing GUI避坑指南

简介:一套完整的银行排号系统设计与实现项目,基于Java Socket完成客户端与服务器端的网络通信,并利用Java GUI构建人机交互界面,数据存取搭配Oracle数据库,功能覆盖取号、叫号、窗口调度与排队状态查看等典型应用场景。项目主要面向计算机专业高校学生、课程设计或毕业设计需求者,以及希望将Java网络编程知识落地的进阶开发者。压缩包整体大小约292.61MB,内含项目全套源码与完整设计文档,源码均经过测试校正,可百分百运行;文档对系统架构、模块划分和关键实现进行了说明,便于读者快速梳理排号逻辑与通信流程。目前已有475人浏览学习,适合作为相关项目设计的参考模板,也可用于学习Socket通信、多线程并发、GUI事件处理和数据库交互等实用技能,是理论与实践结合的高质量参考资源。

1. 拿到这份基于 Java + Socket + Java GUI 的银行排号系统源码,我最先确认的是它到底能不能跑

和不少课程设计卖家给的半成品不一样,这份银行排号系统压缩包里不是单个 Java 文件,而是一整套 C/S 结构的完整工程:客户端是 Java GUI 写的叫号界面,服务端用 Socket 监听请求,数据落进 Oracle。拆开后我发现,它的价值不只是「能交差」——如果你正在学 Java 网络编程,或者做毕业设计想选一个能讲清多线程、GUI 事件模型、数据库事务的项目,这套代码比空谈理论有用得多。

当时我先跑起来看了一圈,确认了它能解决的真实痛点:大厅取号、柜员叫号、窗口状态变更、等待列表刷新,这些业务闭环都在。下面不吹资源,按我排障的顺序,从架构、核心代码、GUI 客户端的线程处理,最后落到实际运行遇到的高频问题,全部说一遍。

2. 架构与数据库设计:为什么这套排号系统要选 C/S 和 Oracle

2.1 为什么用 Java Socket 做通信层而不是 RMI 或 HTTP

银行排号业务本质上是内网低并发的实时通信场景。取号终端、柜员叫号端和综合管理端,都需要实时知道当前排队号码状态。如果走 HTTP,要么加轮询,要么引入 WebSocket 或消息推送组件,这对一个课程设计或中小型实训项目来说复杂度偏高。RMI 虽然也是 Java 原生的,但需要额外维护 Registry 服务,对象序列化耦合严重,换一台机器跑容易出问题。

所以这套系统选择 Java Socket 是合理的。服务端建立一个 ServerSocket,客户端主动连上来之后保持长连接,所有指令通过自定义文本协议传输。这个方案的好处有三个:第一,不依赖第三方容器,部署简单;第二,可以很直观地展示 TCP 连接、多线程通信、流读写,和面试时被追问的「Socket 网络编程」知识直接对应;第三,服务端能精确控制每个客户端连接的状态,比如柜员在哪个窗口、当前叫到哪个号。

实际项目里,端口号一般会选一个 8000 到 9999 之间的空闲端口,比如 9001。如果和本机其他服务冲突,启动日志直接会报BindException,这个问题我在后面的避坑章节里详细说。

2.2 排号系统数据库表设计:号码状态与窗口业务的映射关系

Oracle 在这套系统里负责两件事:一是持久化所有排队记录,防止服务端重启后号码乱掉;二是记录叫号流水,方便后面统计业务量。

我从源码文档里整理出的最简表结构大概有 5 张核心表:

表名作用关键字段
T_QUEUE排队主表ID、BIZ_CODE、WINDOW_ID、STATUS、CREATE_TIME、CALL_TIME、FINISH_TIME
T_WINDOW窗口表WINDOW_ID、WINDOW_NAME、WORKER_NAME、STATUS
T_BIZ_TYPE业务类型表BIZ_TYPE_ID、TYPE_NAME、PREFIX_CHAR
T_CALL_LOG叫号流水表LOG_ID、QUEUE_ID、WINDOW_ID、ACTION、ACTION_TIME
T_OPERATOR操作员表OP_ID、OP_NAME、PASSWORD、ROLE

其中T_QUEUE.STATUS是最关键的状态字段。通常维护这几个值:

  • WAITING:已取号,等待叫号
  • CALLED:已被柜员叫号
  • FINISHED:业务办理完成
  • CANCELED:过号或取消

为什么状态用数字或字符串而不是直接删记录?因为叫号、完成、过号都要在日志里留痕。如果直接删除,统计就会失真。这也是设计上值得学习的一点:排队记录做追加,不做原地覆盖。

Oracle 里建主键或者生成号码时,我建议用序列SEQ_QUEUE_ID,尽管项目里也有人用MAX(ID)+1,但并发时容易重复,后面避坑章节会展开。

2.3 Socket 应用层报文协议:让两类客户端对接同一套服务端

客户端和服务端之间不能只传「一句话」就完事,因为取号请求、叫号请求、状态查询请求格式不同。源码里用的是一种带命令前缀的文本协议,我用伪代码还原一下:

命令格式:CMD,参数1,参数2,参数3\n CMD 取值:TAKE_NUMBER / CALL_NEXT / FINISH / QUERY_STATUS / HEARTBEAT

服务端用一个BufferedReader.readLine()读取客户端发过来的这一行,然后按逗号拆分,匹配命令字符串。这个设计很简单,比 Java 对象序列化更稳定,而且可以直接用 telnet 或者写一段 Socket 测试脚本手工验证,不用依赖整个 GUI 界面。

如果在真实项目里,我一般会加上一个协议版本号,比如改成V1,CMD,参数,这样以后服务端升级还能兼容旧客户端。不过作为演示项目,直接用CMD开头也够用。

3. 服务端核心实现:多线程 Socket 服务、叫号逻辑与 Oracle 落库

3.1 服务端启动入口与业务线程池

服务端的主流程是这样:启动时加载 JDBC 配置,创建线程池,然后进入 accept 循环,每接受到一个新 Socket 连接,就交个一个独立的业务线程去处理。这样能保证多个客户端同时取号时,每个客户端不会互相堵住。

下面是我还原的服务端核心代码骨架:

// BankServer.java import java.io.*; import java.net.*; import java.util.concurrent.*; public class BankServer { private static final int PORT = 9001; // 核心线程 8 个,最大线程 20 个,队列长度 200 private static final ExecutorService POOL = new ThreadPoolExecutor(8, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200)); public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(PORT); System.out.println("银行排号服务端已启动,端口:" + PORT); while (true) { Socket socket = server.accept(); POOL.execute(new ClientHandler(socket)); } } }

这段代码是整个服务端的入口。new ServerSocket(PORT)会在指定端口开启监听,accept()会阻塞等待客户端连接。ThreadPoolExecutor的参数值得说明一下:核心线程 8 个适用于一般的毕设和实训环境,如果你们学校机房有 30 个以上终端,建议把核心线程提高到 16 到 32,队列长度也同步调大。如果核心线程和最大线程都设成 1,那就相当于串行处理,第一个终端取号时第二个终端会一直等待。

实际运行中,BankServer.java应该常驻后台运行。很多新手直接在 IDE 里点一次运行,关掉控制台再点第二次,就会报端口被占用。这一点后面我会单独说。

3.2 取号与叫号逻辑:状态机是排号系统的核心

每一个ClientHandler负责处理一个客户端连接。客户端发来TAKE_NUMBER,业务类型之后,服务端要做三件事:生成不重复的排队号码、写入数据库、把号码返回给客户端。

取号的核心逻辑我用代码片段说明:

// ClientHandler.java 内部简化版 public void takeNumber(String bizType) throws Exception { Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS); conn.setAutoCommit(false); // 通过序列生成当日单号,避免并发重复 PreparedStatement ps = conn.prepareStatement( "SELECT SEQ_QUEUE_ID.NEXTVAL FROM DUAL"); ResultSet rs = ps.executeQuery(); long seqId = 0; if (rs.next()) { seqId = rs.getLong(1); } String prefix = getBizPrefix(bizType); String queueNo = String.format("%s%03d", prefix, seqId % 1000); PreparedStatement insert = conn.prepareStatement( "INSERT INTO T_QUEUE (ID, BIZ_CODE, QUEUE_NO, STATUS, CREATE_TIME) " + "VALUES (?, ?, ?, 'WAITING', SYSDATE)"); insert.setLong(1, seqId); insert.setString(2, bizType); insert.setString(3, queueNo); insert.executeUpdate(); conn.commit(); // 返回号码给客户端 out.write("TAKE_OK," + queueNo + "\n"); out.flush(); }

这里最关键的是不要用SELECT MAX(ID)+1来生成号。多线程同时取号时,两个线程都查到同一个最大 ID,然后都插入相同号码,直接主键冲突。用 Oracle 序列可以保证并发环境下的唯一性。seqId % 1000只是演示取三位数,如果一天业务量超过一千笔,可以改成% 10000取四位。

叫号逻辑稍微复杂一点。柜员点「呼叫下一个」时,服务端要把当前窗口关联到队列中最早WAITING的那条记录,并把状态改成CALLED,同时记录叫号时间。SQL 大致是这样:

SELECT ID FROM ( SELECT ID FROM T_QUEUE WHERE STATUS = 'WAITING' ORDER BY ID ASC ) WHERE ROWNUM = 1

拿到这个 ID 之后,再用UPDATE更新状态和窗口号。为什么不能直接用第一个语句就 update?因为在多线程叫号时,两个窗口可能同时抢到同一条等待记录。所以稳妥的做法是把「取下一个号」和「更新状态」放在同一个事务里,用SELECT ... FOR UPDATE SKIP LOCKED锁定记录。这个写法 Oracle 11g 以后都支持,是高并发下比较可控的取数方式。

3.3 JDBC 连接与事务:叫号时不能把失败写在客户脸上

服务端操作数据库的常见坑就是连接不释放和乱提交事务。我复查源码时发现,这个项目在ClientHandler里使用try-with-resources管理 Connection 和 Statement,这个习惯很好。

我建议库表操作遵循以下纪律:

  • 取号、叫号、完成这三个写操作都显式开启事务setAutoCommit(false)
  • 所有的查询、更新、插入完成之后手动commit()
  • 任何异常都要rollback(),避免出现「界面显示叫号成功,数据库里状态还是 WAITING」的情况
  • 关闭连接放到finally块,或者直接使用 Java 7 的 try-with-resources 语法

代码层面,最典型的写法是这样:

try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { conn.setAutoCommit(false); ps.executeUpdate(); conn.commit(); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} } throw e; }

很多新手只写conn.close()不写 rollback,一旦 update 执行一半报错,数据库状态就是错乱的。虽然本项目规模小,这个习惯仍然值得保留,因为后续扩展「过号」「预约」「VIP 优先」时,事务范围会越来越大。

4. Java GUI 客户端:Swing 界面与 Socket 通信多线程踩坑

4.1 用 Swing 而不是 AWT 搭界面

Java GUI 的课程设计中,AWT 是教科书常提的老技术,但真正做项目我更推荐 Swing。Swing 的组件更丰富、外观跨平台一致,而且JFrame、JButton、JTable这些组件用起来比 AWT 顺手得多。

这套排号系统的客户端起两个角色:大厅取号机用户和柜员操作系统。大厅取号机界面相对简单,就是几个大按钮和显示当前号码的面板;柜员端则需要显示等待列表、每个窗口状态、叫号操作按钮。

下面是一个简化版的取号界面创建代码:

// TakeNumberFrame.java 片段 JFrame frame = new JFrame("银行取号机"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 300); JPanel panel = new JPanel(new BorderLayout()); JLabel numberLabel = new JLabel("请点击按钮取号", SwingConstants.CENTER); numberLabel.setFont(new Font("宋体", Font.BOLD, 32)); JButton takeBtn = new JButton("取号"); panel.add(numberLabel, BorderLayout.CENTER); panel.add(takeBtn, BorderLayout.SOUTH); // 点击取号时,不能直接在这里写 Socket 阻塞逻辑 takeBtn.addActionListener(e -> { new TakeNumberWorker(numberLabel).execute(); });

注意takeBtn.addActionListener里的写法。ActionListener运行在 Swing 的 EDT(Event Dispatch Thread,事件派发线程)里,如果你在这里直接socket.getInputStream().read(),界面就会卡住。很多刚学 Java GUI 的人会在这里翻车:点完按钮界面转圈,窗口拖不动,最后只能强制杀进程。正确做法是使用SwingWorker或单独创建线程网络请求,拿到结果后再回到 EDT 更新界面。

4.2 读取 Socket 的线程必须和事件线程分离

客户端同时要维护一条到服务端的 TCP 连接,并持续读取服务端推送过来的状态变化,比如「当前叫到 A012,请到 3 号窗口」。这个读取动作绝不能放在事件线程里做。

我常用的做法是启动一个单独的SocketListener线程,在后台阻塞循环读服务端消息,然后用SwingUtilities.invokeLater把更新动作切回事件线程:

// SocketListener.java 片段 new Thread(() -> { try (Socket socket = new Socket(serverIp, port); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); PrintWriter out = new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true)) { while (true) { String line = in.readLine(); if (line == null) break; final String msg = line; SwingUtilities.invokeLater(() -> { // 更新界面上的当前叫号标签 numberLabel.setText(msg); }); } } catch (IOException e) { SwingUtilities.invokeLater(() -> numberLabel.setText("服务端连接断开")); } }).start();

invokeLater的本质,是让更新 UI 的代码在事件派发线程上排队执行,避免在后台线程直接改组件,引发线程安全问题。这里的网络流两边都指定了"UTF-8",这是避免中文乱码非常关键的一句话,源码里如果少写了这个参数,换个环境运行就可能出现A0??或者方框乱码。

4.3 客户端心跳与断线重连

服务端重启后,已经建立连接的老客户端不会自动感知断开。Socket 读取时readLine()会返回null,但这是阻塞状态下比较迟钝的反映。更好的方案是客户端周期性发送心跳包。

这里的心跳包不是必须的,但加上会让项目完整度更高:

// 每 5 秒发送一次心跳 Timer timer = new Timer(5000, e -> { if (out != null) { out.write("HEARTBEAT\n"); out.flush(); } }); timer.start();

HEARTBEAT不是业务请求,服务端收到后只需要回一个PONG或者直接忽略。它的意义有两个方面:一是维持连接不被中间网络设备意外回收,二是让服务端能清理已经失效的客户端连接。排号系统如果挂在大厅的触屏一体机上,断线重连必须能在不重启界面的情况下自动恢复,否则大堂经理得天天找人重启应用。

5. 排查避坑:端口冲突、Oracle 驱动与 GUI 卡死的高频原因

这一章是排号系统部署过程中最容易踩实的坑,我在复现源码时把几个典型问题都整理出来了。

5.1 服务端重复启动报端口被占用

现象:运行BankServer.main时报java.net.BindException: Address already in use: JVM_Bind。

原因:上一个服务端进程没有退出,常见情况是之前直接从 IDE 点红色停止,但后台线程没有完全释放,或者端口被其他程序占用。

解决:先确认端口占用进程,再结束进程或改端口。

# Windows 下查看 9001 端口占用 netstat -ano | findstr 9001 # 最后一列是 PID,结束进程 taskkill /PID 1234 /F

如果是在同一台机子上反复调试,我一般直接把端口改成 9101,让服务端和客户端读取同一个配置文件。

5.2 连接 Oracle 时报 ClassNotFoundException

现象:启动取号或叫号功能时,控制台出现java.lang.ClassNotFoundException: oracle.jdbc.OracleDriver。

原因:项目引用了ojdbc包,但运行时 classpath 里没有这个 jar。特别是用 Tomcat 或者轻量级 IDE 跑的时候,容易漏配。

解决:确认ojdbc8.jar在项目的 lib 目录中,并且在 IDE 里把它添加为 Library。命令行运行的话,这样写:

java -cp .;lib\ojdbc8.jar;lib\其他依赖.jar BankServer

Oracle 12c/19c 建议用ojdbc8而不是老的ojdbc6,对 JDK 8 以上兼容性更好。

5.3 GUI 点按钮后窗口卡死

现象:取号机界面上点「取号」按钮,按钮按下去没反应,整个窗口变成「未响应」状态。

原因:ActionListener里直接执行了 Socket 读写或Thread.sleep(),把 Swing 事件派发线程堵住了。事件线程一堵,所有按钮、标签都没法重绘,窗口就卡死了。

解决:用SwingWorker或独立线程做网络操作。以取号为例:

new SwingWorker<String, Void>() { @Override protected String doInBackground() throws Exception { // 这里执行 socket 发送与接收 return sendCommand("TAKE_NUMBER,A"); } @Override protected void done() { try { numberLabel.setText(get()); } catch (Exception e) { numberLabel.setText("取号失败"); } } }.execute();

doInBackground是在后台线程执行的,done会回到 EDT 执行,所有界面更新操作都必须放在done方法里,这样才符合 Swing 线程模型。

5.4 中文乱码或消息错位

现象:取号成功后客户端显示A001?或者?号,服务端日志里显示的中文业务名变成乱码。

原因:SocketInputStream和OutputStream两头编码不一致。Windows 控制台默认 GBK,代码里如果没指定编码,就会使用系统默认编码,传过去的中文字节串在不同机器上解析出不同字符。

解决:在 Socket 流创建时显式指定编码。读取用new InputStreamReader(socket.getInputStream(), "UTF-8"),写出用new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),两头统一成 UTF-8。数据库表字段也建议使用 NVARCHAR2 或 VARCHAR2 配合 AL32UTF8 字符集,避免 Oracle 客户端和服务端字符集转换产生问号。

5.5 并发取号时号码重复或插入报主键冲突

现象:两个取号终端同时按取号,一个拿到A001,另一个也拿到A001,数据库报ORA-00001: unique constraint violated。

原因:号码生成逻辑用了SELECT MAX(ID)+1,两个事务同时执行时查到同一个最大值。前面我写的 Oracle 序列NEXTVAL就是为规避这个问题,但许多课程设计源码为了简化会手写这个查询。

解决:把号码生成改成数据库序列或并发锁。如果不想新增序列,可以在服务端加一个单例的号码生成器:

private static synchronized String getNextQueueNo(String prefix) { // synchronized 保证并发环境下一个线程只生成一个号 String queueNo = prefix + String.format("%03d", ++counter); return queueNo; }

这是代码层面的兜底,能保证同一 JVM 内不重复。但最稳妥的方案仍是依赖 Oracle 序列。每次生成号码后立即插入,然后才释放锁。

6. 进阶:压测服务端、参数调优,以及把排号记录导出为报表

正常跑通这套系统只是第一步,我拆资源时还会做两件额外验证:第一是写一个多小时的压力测试,确认服务端在并发取号时不把号码发重;第二是把数据库里的排号流水导出成业务报表,让项目答辩时有数据可讲。

压测方法很简单,不需要引入 JMeter,写一个 Java Socket 客户端脚本模拟 50 个线程同时取号,看最终数据库里有没有重复号码:

// 压测入口,大家可以根据自己机器调整并发数 public static void main(String[] args) throws Exception { int threadCount = 50; CountDownLatch latch = new CountDownLatch(threadCount); Set<String> allQueueNos = ConcurrentHashMap.newKeySet(); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try (Socket socket = new Socket("127.0.0.1", 9001); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8")); PrintWriter out = new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true)) { out.write("TAKE_NUMBER,A\n"); out.flush(); String resp = in.readLine(); if (resp != null && resp.startsWith("TAKE_OK")) { String no = resp.split(",")[1]; allQueueNos.add(no); } } catch (IOException e) { e.printStackTrace(); } finally { latch.countDown(); } }).start(); } latch.await(); System.out.println("并发取号完成,不重复号码数:" + allQueueNos.size()); }

这个脚本的关键点,是每个线程都独立创建 Socket 连接,真实模拟大厅多台取号机并发请求。ConcurrentHashMap.newKeySet()用来收集所有返回的号码,最后检查总数是否等于 50。如果少号,就是服务端线程池队列溢出或数据库写入失败;如果出现重复号码,就是号码生成逻辑需要改。

事务之外,线程池参数也可以根据压测结果调整。我的参考经验是:

  • 单机版演示:核心线程 4,最大线程 8,队列 100
  • 机房 20 台终端:核心线程 16,最大线程 32,队列 500
  • 队列满了之后,新的取号请求会触发RejectedExecutionException,这时客户端应该提示「系统繁忙」

至于排号报表,可以从T_QUEUE表按天聚合,统计每类业务取号量、平均等待时间、平均办理时间。用 Oracle 的AVG和日期函数能直接查出:

SELECT TO_CHAR(CREATE_TIME, 'YYYY-MM-DD') AS BIZ_DATE, BIZ_CODE, COUNT(*) AS TOTAL_NUM, ROUND(AVG(CALL_TIME - CREATE_TIME) * 1440, 2) AS AVG_WAIT_MIN FROM T_QUEUE WHERE CREATE_TIME >= SYSDATE - 7 GROUP BY TO_CHAR(CREATE_TIME, 'YYYY-MM-DD'), BIZ_CODE ORDER BY BIZ_DATE, TOTAL_NUM DESC;

这句话可以放进配套文档里作为演示数据。答辩时能拿出一张按天、按业务类型统计的数据表,比单纯说「系统有叫号功能」得分高得多。

最后说一个我的个人习惯。每次拿到这种带文档和源码的项目,我都会先跑一遍压测再去看界面,因为界面能骗人,数据库里的号码不能骗人。以后做 Java Socket 项目,我也会强制走一遍这套流程:确认端口、确认编码、确认线程池、确认号码唯一、最后才连 GUI。这套银行排号系统已经把 C/S 项目的骨架完整搭好了,如果你正在准备课程设计或期末项目,从它的源码和文档入手会省掉不少查资料的弯路,希望帮到你。

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

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

数模混合芯片版图LVS验证全流程:从规则配置到错误定位

做数模混合芯片的版图&#xff0c;最磨人的环节之一就是LVS。画版图的时候觉得连线都对、参数都填对了&#xff0c;一到Calibre跑LVS&#xff0c;报出来的结果能把人看晕&#xff1a;几百个incorrect net、几十个soft connect、还有一堆property mismatch挤在一起。尤其当你面对…

作者头像 李华
网站建设 2026/10/7 13:06:54

claude-mem:给Claude大模型补上长期记忆的实战指南

最近在折腾AI工具链的时候&#xff0c;我盯上了一个叫 claude-mem 的小项目。它的目标很直接&#xff1a;给Claude这种“每次对话都从零开始”的大模型补上长期记忆。简单说&#xff0c;就是让Claude记得你上次聊了什么、你习惯用什么语言、你反复强调过哪些偏好&#xff0c;甚…

作者头像 李华
网站建设 2026/10/7 13:06:10

AI Agent工程落地:从并发治理到内容安全的全链路实践

我每天早上都会花二十分钟把当天散落在 Hacker News、开发者群聊、搜索引擎热词和几个行业社群里关于 AI 的信息过一遍&#xff0c;然后把真正有用的部分留下来&#xff0c;形成这份「2026.09.29」科技AI资讯日报。今天的热度明显集中在两个方向&#xff1a;一是 AI Agent 从“…

作者头像 李华
网站建设 2026/10/7 13:05:32

iQ-R PLC报警锁存FB设计:解决报警闪退与历史记录难题

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

作者头像 李华
网站建设 2026/10/7 13:05:32

AI Agent企业落地指南:从场景诊断到基础设施的完整路径

做企业服务咨询这几年&#xff0c;我几乎每周都会被同一个问题轰炸&#xff1a;“AI Agent到底能帮我们公司干点什么&#xff1f;是不是又是个概念&#xff1f;”问的人包括制造业的CIO、零售连锁的运营总监、金融科技团队的技术负责人&#xff0c;甚至还有几位刚把股价炒上天的…

作者头像 李华
网站建设 2026/10/7 13:05:13

嘉楠K230+MediaPipe实现低延迟边缘AI手势控制

1. 为什么这个方案值得认真对待&#xff1a;它不是玩具&#xff0c;而是能落地的边缘AI控制入口 嘉楠K230开发板MediaPipe做手势控制智能家居——看到这个标题&#xff0c;很多人第一反应是“又一个Demo级项目”&#xff0c;刷个视频点个赞就划走了。但我在去年下半年连续三个月…

作者头像 李华