news 2026/9/13 16:37:57

Java Swing进销存系统源码实战:库存流水与事务处理解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Swing进销存系统源码实战:库存流水与事务处理解析

简介:一份基于Java Swing的进销存管理系统完整源码包,面向计算机相关专业毕设、课程设计以及希望了解桌面管理信息系统开发的初学者。系统涵盖信息管理(客户、商品、供应商)、业务管理(进货单、销售单)、库存管理(库存盘点)、查询统计(客户、商品、供应商及销售、入库查询)和系统管理(操作管理)五大模块,为企业库存、销售与进货数据提供管理及分析支持;配套SQL Server 2000数据库文件(含.mdf/.ldf),可在MyEclipse中直接导入运行,适合需要兼容早期数据库环境的项目。资源共325个文件,压缩包仅4.47MB,主要包含169个class字节码、75个java源文件、56个png界面图、7个doc设计文档,以及jar依赖库、properties配置、SQL数据库文件等,结构便于按功能模块检索与对照学习。已有599人学习下载。借助完整源码与设计文档,读者可理清Swing界面层、DAO数据访问层的调用关系,掌握订单录入、库存盘点、条件查询等典型功能的实现方式,并可参考文档完成数据库表设计与扩展,适合作为实战练手、答辩演示及二次开发基础。

1. Java Swing 进销存管理系统源码:老项目里的高密度 Java 综合练习

个人云盘和技术分享群里,Java Swing 进销存管理系统源码(附相关设计文档)是老面孔。它不像 Spring Boot 上来就要面对一堆容器概念,也不像算法题那样脱离业务;解压 zip 后通常是一个 Java SE 工程、几份 SQL 脚本和 Word 版设计文档。把这套源码跑通,等于把 Java 基础语法、JDBC、Swing 布局、MySQL 事务和分层思想完整过了一遍,不少准备 java 面试题的人拿它当综合案例复盘。我拿到这类 zip 的习惯是:先找 ER 图,再找建表脚本,最后打开主窗体类运行。只要三步能对上,后面改功能、盘点、导报表都顺;对不上,多半要先查数据库连接或版本差异,否则连登录页都出不来。

2. 进销存的核心是库存流水,不是库存表当前值

2.1 进销存管理系统的四个基础角色

无论源码界面做得多炫,数据库表有多少张,进销存领域总绕不开四个基础角色:商品、往来单位、仓库和结算账户。商品是进货和销售的标的,往来单位同时承担客户与供应商两个身份,仓库记录货品放在哪里,结算账户处理应收应付。老设计文档常把客户和供应商拆成 customer、supplier 两张表,但从业务上完全可以合并成 partner + type 的结构。表结构不同,界面层代码的组织方式也不同,看源码前先用角色去对表,比硬背字段快。

角色核心字段Java 层常见对象
商品product_id, product_name, spec, purchase_price, sale_priceProduct
往来单位partner_id, partner_name, typePartner
仓库warehouse_id, warehouse_name, addressWarehouse
结算账户account_id, account_name, balanceAccount

这四个角色在界面上分属“基础数据”菜单,但每张业务单据都离不开它们。判断一套源码设计得是否清晰,就看业务单据表是直接存字段冗余,还是只存 id。早期进销存为了查询快,会在 order 表里同时存商品名称、单位、价格;这样商品改名后老单据也跟着变,审计对不上。设计文档如果强调“单据行冗余商品快照”,那对应的 Java 实体也要加对应的快照字段,别只看到 product_id。

2.2 从 ER 图和建表 SQL 拆出三张核心表

设计文档里的 ER 图会画出 product 与 stock 一对一,也有 purchase_main、purchase_detail 与 product 的多对多。落库时不一定建外键,但字段关联必须一致。我一般先重建这三张表,把库存流水独立出来,这样后续所有报损、退货、盘点都有地方还原。

-- 商品基础表:状态位让“停用商品”仍然可查历史 DROP TABLE IF EXISTS product; CREATE TABLE product ( product_id INT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(32) NULL, product_name VARCHAR(128) NOT NULL, spec VARCHAR(64) DEFAULT '', unit VARCHAR(16) DEFAULT '件', purchase_price DECIMAL(10,2) DEFAULT 0.00, sale_price DECIMAL(10,2) DEFAULT 0.00, status TINYINT DEFAULT 1, UNIQUE KEY uk_barcode (barcode) ); -- 库存表:只保存当前结存,不要在这里手工加加减减 DROP TABLE IF EXISTS stock; CREATE TABLE stock ( product_id INT PRIMARY KEY, warehouse_id INT NOT NULL DEFAULT 1, quantity INT NOT NULL DEFAULT 0, min_stock INT DEFAULT 0, max_stock INT DEFAULT 0 ); -- 库存流水表:每一笔进出都留痕,对账全看这张表 DROP TABLE IF EXISTS stock_flow; CREATE TABLE stock_flow ( flow_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, warehouse_id INT NOT NULL DEFAULT 1, ref_type TINYINT NOT NULL COMMENT '1-进货入库,2-销售出库,3-盘点调整', ref_no VARCHAR(32) NOT NULL, direction TINYINT NOT NULL COMMENT '1-入库,-1-出库', quantity INT NOT NULL, before_qty INT DEFAULT 0, after_qty INT DEFAULT 0, biz_date DATETIME DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(255), KEY idx_flow_biz_date (biz_date), KEY idx_flow_ref_no (ref_no) );

这段 SQL 的关键参数在两个地方。direction用 1 和 -1 而不是 in/out,查询时可以直接SUM(quantity * direction)算出累计变动量,省去 case when。before_qtyafter_qty虽然是冗余,但做库存审计时能很快看出某张单据把结存从多少改成了多少;如果源码里的流水表没有这两个字段,也可以先不补,等对账出问题再加。注意ref_no建议建索引,因为它会出现在每一条对账 SQL 的关联条件里。建完表后,用SHOW CREATE TABLE stock_flow和设计文档里的数据字典对一遍,字段注释不一致的地方多半就是文档滞后的信号。

2.3 库存表为什么不能直接按加减来维护

很多下载到的源码里,采购入库就是一句UPDATE stock SET quantity = quantity + ?,销售出库就是减。单看没问题,但一旦出现退货、订单作废、分仓调拨,就需要同时改好几张表,稍漏一行,期末盘点就对不上。更合理的流程是先保存单据主表和明细行,再向 stock_flow 插入一条流水,最后用事务更新 stock 表。界面上的“库存回滚”其实就是删掉流水再重新计算,前提是流水表里所有进出都有ref_nobiz_date

这里补充一个常见现象:源码的建表脚本里没有 stock_flow,只有 product 和 stock。这种系统做 demo 可以,用在真实门店会非常难受,因为无法回答“这批货是什么时候进的、当时多少钱”。所以从设计文档的 ER 图上先找“流水”或“日志”字样的表,如果能找到,说明作者考虑了账实一致;找不到,你后续二次开发的第一件事就是补流水表,再把原来的库存更新语句改成事务。

3. 在本地把 Java Swing 进销存管理系统源码跑起来的最小路径

解压 zip 后,先看目录结构。一个能直接跑的源码包通常包含 src、sql、lib、doc 和 README.txt 五个部分;lib 里放着 mysql-connector jar,sql 里是建库脚本,doc 就是标题里说的相关设计文档。如果 lib 是空的,说明作者默认你手动下载驱动,运行前需要先解决依赖,否则会在点击“查询”按钮的那一刻才抛出NoClassDefFoundError

3.1 先配 JDK 与 MySQL,版本差异明确列出

Swing 项目大多数出生在 JDK 8 时代,我建议本地直接装 JDK 8,而不是装最新版。高版本不是不能跑,但老源码中用-encoding UTF-8和某些内部 API 在高版本会被限制,排错成本反而高。装完后在命令行确认。

java -version javac -version mysql --version

正常情况下第一行会显示java version "1.8.0_...",第三行显示 MySQL 的版本号。如果 java 后面报javac not found,就是 Path 没有指向 JDK 的 bin 目录;这个问题在 windows 上搜“java 环境变量配置详细教程”就有一堆答案,核心是新建JAVA_HOME并把%JAVA_HOME%\bin加到 Path。MySQL 用 5.7 或 8.0 都行,但连接驱动类名不同,这一点在 3.2 里会再强调。

3.2 创建数据库并导入 sql 脚本

业务库命名常见为 jxc、shop、erp,先看 install 脚本里的CREATE DATABASE语句。如果不一致,直接把库名改成脚本里的名字,或者在建立库时保持统一。

# 新建业务库,utf8mb4 比 utf8 更稳 mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS jxc DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 导入脚本,路径按实际解压位置调整 mysql -uroot -p jxc < sql/jxc.sql

这里有个命令行习惯:-p后面不要直接跟密码,留在交互提示里输入,防止密码写进 shell 历史。导入完后登录 MySQL 执行USE jxc; SHOW TABLES;,如果表数量不等于文档中的 ER 图数量,先检查脚本是不是分段执行,特别是purchase_detail这种后来追加的表,可能在第二份 sql 文件里。

3.3 在 DBUtil 里确认四个连接点

大多数老源码用一个DBUtil.java类集中管理连接。打开它改四件事:driver、url、username、password。

// DBUtil.java 常见写法,改成你本地的实际值 public class DBUtil { private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/jxc?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

URL 里的useSSL=false是本地开发标准配置,避免 MySQL 8 的 SSL 证书警告;serverTimezone=Asia/Shanghai负责解决 CST 时区歧义;characterEncoding=utf8配合建库时的 utf8mb4,中文显示不乱码。特别提醒:如果你用 MySQL 8,驱动类必须是com.mysql.cj.jdbc.Driver;MySQL 5.7 的压缩包可能还是com.mysql.jdbc.Driver,直接切换版本即可,不用改 jar 包。

3.4 编译运行主窗体和启动异常对照表

在 IDE 里运行前,先把 lib 目录右键添加到项目依赖,再找到主类。主类名一般是LoginFrameMainFrame,包名从src/com/xxx路径反推。命令行方式也可以跑,尤其适合跨环境验证:

# 列出所有 java 文件并编译到 out 目录 javac -encoding UTF-8 -cp "lib/*" -d out $(find src -name "*.java") # Windows 下用分号分隔 classpath java -cp "out;lib/*" com.jxc.main.LoginFrame # Linux/macOS 下用冒号 java -cp "out:lib/*" com.jxc.main.LoginFrame

第一行里的find src -name "*.java"是 Linux/macOS 语法,Windows 可直接用 IDE 编译,不必纠结。如果启动时报错,对照下面这张表能快速缩小范围。

启动阶段报错原因定位处理方式
ClassNotFoundException: com.mysql.jdbc.Driver驱动 jar 没有加入 classpath把 mysql-connector jar 放 lib 并重新导入依赖
Access denied for user 'root'@'localhost'DBUtil 账号密码不对先在命令行登录 MySQL,确认 root 的密码和允许的 host
Unknown database 'jxc'建库不成功或库名不一致重新执行建库和导入,再用SHOW TABLES验证
Could not find or load main class主类全限定名写错看编译输出的包名,改成真实路径
按钮点击后中文问号编译时缺-encoding UTF-8加编码参数,并把窗体字体设置为宋体

这一章解决了“跑起来”,但运行只是第一步。真正难的是后续改需求,比如加一个采购员字段,很多人不知道该动 table、DAO 还是 Swing 界面,下一章就从设计文档的顺序入手。

4. 相关设计文档的正确打开方式:先读模块边界,再读 SQL

4.1 一张模块划分图框定源码包里的类

老项目的设计文档比 Spring Boot 的接口文档更有参考价值,因为它通常从模块图开始。最常见的进销存模块划分是:系统管理、基础数据、采购管理、销售管理、库存管理、报表查询。六个模块直接对应源码里的六个包名。

模块典型相关类对应数据表
系统管理UserDao, RoleDaosys_user, sys_role
基础数据ProductDao, PartnerDaoproduct, partner
采购管理PurchaseMainFrame, PurchaseDetailpurchase_main, purchase_detail
销售管理SaleMainFrame, SaleDetailsale_main, sale_detail
库存管理StockDao, StockFlowDaostock, stock_flow
报表查询ReportFrame, SaleReportDaosale_report_view

看源码时先用模块图定位:要改商品资料,就去基础数据包找 Product 相关类;要查库存流水,去库存管理包找 StockFlow。不要在MainFrame一个文件里从登录到处翻几百行事件监听,那是没有包结构的早期代码才做的事。如果文档里的模块图和源码包名差一级,以包名为准,但要在文档的第一页标注差异,方便后来人。

4.2 用 ER 图反查数据字典,逐列对齐

设计文档里的 ER 图通常只是粗粒度关系,数据字典才是逐字段描述。问题是很多 Word 数据字典没有更新,与 sql 脚本不一致。常见做法是用 SQL 直接查看真实表结构,再回到设计文档里标记。

-- 查看销售主表的字段、类型、注释 SHOW FULL COLUMNS FROM sale_main; -- 查看销售明细表上的索引,判断关联查询会不会全表扫描 SHOW INDEX FROM sale_detail;

SHOW FULL COLUMNS会输出字段名、类型、是否为空、默认值和注释,注释是设计文档里最重要的部分。遇到文档里叫price,代码里叫sale_price也不要慌,先确认 ER 图中是哪一张表,再以数据库脚本为基准生成 DAO 层代码,没有必要人肉手动改所有字段名。

4.3 用时序图理解 Swing 事件回调

很多 Swing 源码让人头晕,是因为不知道“按下保存按钮后,到底先调了谁”。设计文档的时序图会把这个过程画出来:用户点击按钮 -> 窗口监听器 -> 业务服务 -> DAO -> 数据库。落到代码就是下面这个顺序。

// 保存销售单的按钮事件,对应时序图里的两条消息 saleFrame.addSaveListener(e -> { saleService.createSale(conn, saleMain, saleDetailList); stockService.createOutFlow(conn, refNo, saleDetailList); });

同样一个操作,如果面试时用三步来描述:校验主表数据、插入明细、更新库存,会比直接背一段 SQL 清楚。我在二开时遇到看不懂的代码,会在这个方法上打断点,跑一次再画一个简化时序图;这个自画的过程比读十遍源码更有效。

4.4 把测试用例直接当成验收清单

设计文档最后几页通常有测试用例,常见的有“新增商品成功”“采购入库成功”“销售出库后库存减少”。文字看着简单,但它正好是完整的验收路径。我一般会按顺序手工跑一遍,并且在每步记录界面上的库存数,最后用 SQL 对总表,看有没有出现界面上成功但库里少流水的情况。这一步能同时检验源码和设计文档是否一致,也为后面的正式上线留一份底稿。

5. Java Swing 进销存源码里值得扣出复用的代码片段

5.1 用 AppContext 保存登录信息,而不是到处传 User

Swing 没有 Spring 容器,不能随手@Autowired UserService,所以登录信息往往被塞在各种不变量里。最常见的问题是一个面板要用当前操作员,就得从主窗口一层层传递,改了构造器还要改调用处。更简单的方式是定义一个全局上下文类,把用户信息放在静态变量里。

// 单机版桌面系统可接受,不需要线程级隔离 public class AppContext { private static User currentUser; private AppContext() {} public static void login(User user) { currentUser = user; } public static User getCurrentUser() { return currentUser; } public static void logout() { currentUser = null; } }

这个类的好处是保存单据时直接AppContext.getCurrentUser().getUserId(),不需要在销售单、入库单单据类里再加 userId 字段。要注意的是静态变量会一直存在到 JVM 退出,每次重新登录时一定先调logout()防止上一个用户残留。如果以后要改成 Web 版,这个类就不能用,需要换成 Session 或 ThreadLocal;但在 Swing 进销存源码这个规模下是合适的。

5.2 用 AbstractTableModel 封装 JTable,别直接操作 ResultSet

很多旧代码用ResultSet列名,循环model.addRow(new Object[]{...})填充 JTable;这在数据量小的时候看不出问题,一旦需要排序、过滤、修改单元格,就会把表格和业务逻辑混在一起。更稳的做法是自定义表格模型。

// 表格模型从 List<Product> 取数据,与 ResultSet 解耦 public class ProductTableModel extends AbstractTableModel { private final String[] columns = {"商品ID", "商品名称", "规格", "库存", "售价"}; private final List<Product> products = new ArrayList<>(); public void setProducts(List<Product> list) { products.clear(); products.addAll(list); fireTableDataChanged(); } @Override public int getRowCount() { return products.size(); } @Override public int getColumnCount() { return columns.length; } @Override public String getColumnName(int c) { return columns[c]; } @Override public Object getValueAt(int row, int col) { Product p = products.get(row); switch (col) { case 0: return p.getProductId(); case 1: return p.getProductName(); case 2: return p.getSpec(); case 3: return p.getStock(); case 4: return p.getSalePrice(); default: return null; } } }

使用时JTable table = new JTable(new ProductTableModel()),刷新数据就一行model.setProducts(productDao.queryAll())。关键点是fireTableDataChanged():告诉 JTable 整个数据集合变了,UI 会重新调用getValueAt,不需要table.revalidate()。如果列表里只有一行变化,可以用fireTableRowsUpdated(row, row)减少重绘范围。如果还想让“库存低于下限”的行显示红色,再单独实现TableCellRenderer,这与表格模型是两块独立职责。

5.3 销售开单是天然的事务边界,DAO 必须共用 Connection

销售开单最少涉及三件事:写销售单头、写销售单明细、减库存并记流水。任何一个步骤失败,都不能留下只有半张单。常见错误是每个 DAO 方法内部自己getConnection(),结果三个 DAO 各连各的库,事务根本没有生效。

// 正确的做法:Connection 只获取一次,向下传参 public void createSale(SaleMain main, List<SaleDetail> lines) throws SQLException { Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); // 先插入主表,回填自增主键 int mainId = saleMainDao.insert(conn, main); for (SaleDetail line : lines) { line.setMainId(mainId); saleDetailDao.insert(conn, line); // 记录库存流水,再更新库存结存 stockFlowDao.addFlow(conn, line.getProductId(), -line.getQuantity()); int ok = stockDao.changeStock(conn, line.getProductId(), -line.getQuantity()); if (ok == 0) { throw new SQLException("库存不足:" + line.getProductName()); } } conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn); } }

这个方法的参数顺序值得说明。DAO 方法全部接受 Connection 作为第一个参数,目的是让它们共用同一个事务;stockDao.changeStock的 SQL 最好写成UPDATE stock SET quantity = quantity + ? WHERE product_id = ? AND quantity + ? >= 0,这样在数据库层面也拒绝负库存。最后finally里把 autoCommit 恢复为 true,避免连接池复用下一条 SQL 继承false状态。

循环步骤必需写入的表失败时的后果
step 1sale_main无主单,明细无法归属
step 2sale_detail商品明细缺失,报表数量偏大
step 3stock_flow, stock库存流水断链,对账不平

这张表也解释了为什么销售单不能只写在界面上:单头、明细、库存流水必须被同一个 commit 保住,缺一步,整个系统的可信度就崩了。

5.4 动态 SQL 的参数绑定,比字符串拼接更值得养成习惯

进销存查询界面的筛选条件通常是商品名称模糊、条形码、分类、库存区间。老代码里为了省事,直接用字符串拼接关键字,这在单机版看着没风险,但一个单引号就能让 SQL 结构变化。即便没有外部攻击者,输入' OR '1'='1也会查出全部数据并拉慢界面。用 PreparedStatement 绑定参数,代码量没有增加多少,安全性高一个等级。

// 动态 SQL:按关键字和分类筛选 public List<Product> query(String keyword, Integer categoryId) throws SQLException { StringBuilder sql = new StringBuilder( "SELECT p.*, s.quantity FROM product p LEFT JOIN stock s ON p.product_id=s.product_id WHERE p.status=1"); List<Object> params = new ArrayList<>(); if (keyword != null && !keyword.isBlank()) { sql.append(" AND (p.product_name LIKE ? OR p.barcode LIKE ? OR p.py_code LIKE ?)"); String like = "%" + keyword + "%"; params.add(like); params.add(like); params.add(like); } if (categoryId != null) { sql.append(" AND p.category_id = ?"); params.add(categoryId); } // 用 pstmt.setObject(idx, params.get(idx-1)) 循环赋值 // 最后返回 List<Product> }

LIKE ?而不是LIKE '%' || ? || '%',是为了把通配符留给参数值去处理,数据库预编译也能缓存执行计划。params列表的顺序必须和 SQL 中?出现顺序一致;如果加了字段就漏加参数,运行时会报索引越界。这个方法在 Java 面试中容易出现“动态 sql 拼接和 PreparedStatement 哪个更安全”的题目,能把上面两点讲清楚,比背概念更落地。

6. 给进销存源码做对账和界面提速的两个小改造

6.1 用一条 SQL 验证流水累计量等于当前库存

在跑完一轮业务后,最怕界面显示有库存,数据库流水表却缺记录。可以用stock_flow表的direction字段做汇总,和stock表直接对比。

-- 全量核对:流水累计量与库存结存必须一致 SELECT f.product_id, p.product_name, SUM(f.direction * f.quantity) AS calc_qty, s.quantity AS stock_qty FROM stock_flow f JOIN product p ON p.product_id = f.product_id JOIN stock s ON s.product_id = f.product_id GROUP BY f.product_id, p.product_name, s.quantity HAVING calc_qty <> stock_qty;

这条 SQL 的前提是stock_flow里每一笔业务都写了流水,并且direction只有 1 和 -1 两种值。查询结果里有任何一行,都说明某张采购单或销售单在保存时没有走完整事务,按ref_no去查对应单据就能定位。如果在你的表结构里没有direction,把direction * quantity改成CASE WHEN ref_type=1 THEN quantity ELSE -quantity END,效果一样,只是稍微慢一点。

6.2 用 SwingWorker 把数据库查询移出事件线程

点击查询按钮后,如果界面卡住无法拖动,通常是数据查询在 UI 线程里执行。将耗时 JDBC 操作放到SwingWorker.doInBackground,再把结果切回 EDT 更新表格。

JButton btn = new JButton("查询"); btn.addActionListener(e -> { btn.setEnabled(false); new SwingWorker<List<Product>, Void>() { @Override protected List<Product> doInBackground() throws Exception { return productDao.query(keywordField.getText()); } @Override protected void done() { try { model.setProducts(get()); } catch (Exception ex) { JOptionPane.showMessageDialog(btn, ex.getMessage()); } finally { btn.setEnabled(true); } } }.execute(); });

doInBackground里不能调用任何 Swing 组件,否则还是会造成 UI 状态不安全;done方法会自动运行在 EDT 上,所以在这里更新表格模型是安全的。按钮设置setEnabled(false)是为了防止用户在查询期间重复点击,发起多个并行查询。把这段代码替换原有的同步查询块,重新编译后,查询上万行商品时窗口依然能正常拖动,做演示和交接都更专业。

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

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

img2threejs:电商产品图秒转Three.js 3D模型实战指南

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

作者头像 李华
网站建设 2026/9/13 16:37:47

ESP32-P4:RISC-V双核如何重塑AIoT边缘计算架构

1. 项目概述&#xff1a;为什么ESP32-P4不是“又一款ESP芯片”&#xff0c;而是AIoT开发范式的切换点 我第一次拿到ESP32-P4的工程样片时&#xff0c;没急着烧录固件&#xff0c;而是把它放在显微镜下看了十分钟——不是看封装&#xff0c;是看它引脚定义里那个被标为“AI Core…

作者头像 李华
网站建设 2026/9/13 16:32:07

北京河流水系矢量图层shp获取与处理实战

简介&#xff1a;这份2024年北京市河流水系矢量图层数据集&#xff0c;面向需要水域底图的GIS数据分析师、规划研究者与自然资源相关从业者&#xff0c;可作为水系制图、空间分析、工程选址及可视化应用的基础数据。压缩包内共11个文件&#xff0c;以shp矢量主文件为核心&#…

作者头像 李华
网站建设 2026/9/13 16:29:16

Android作业提交课设实战:SQLite数据层与RecyclerView交互完整指南

简介&#xff1a;一款基于 Android Studio 的作业提交管理课程设计项目&#xff0c;面向高校计算机相关专业学生及 Android 入门开发者&#xff0c;帮助理解从界面搭建、本地存储到服务端联调的应用开发全流程。压缩包共 207 个文件、约 3.15MB&#xff0c;主要内容是 112 个 J…

作者头像 李华
网站建设 2026/9/13 16:27:42

WorkBuddy连接实战:工作区、Skill与记忆迁移全解析

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

作者头像 李华
网站建设 2026/9/13 16:25:33

SpringBoot+Vue电商系统开发实战与优化

1. 项目概述这套2025年最新版的网上服装商城管理系统&#xff0c;采用当前主流的前后端分离架构&#xff0c;后端基于SpringBootMyBatis技术栈&#xff0c;前端使用Vue.js框架&#xff0c;数据库选用MySQL。系统完整实现了电商平台的核心功能模块&#xff0c;包括商品管理、订单…

作者头像 李华