news 2026/10/24 14:36:53

JavaEE二手书交易系统:Servlet+JDBC完整电商闭环实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaEE二手书交易系统:Servlet+JDBC完整电商闭环实现

简介:这是一份面向高校计算机专业学生与Java初学者的期末大作业级项目资源,完整实现了一个基于JavaEE技术栈的二手图书交易平台,覆盖用户注册登录、图书发布/搜索/下单、后台管理等核心业务流程,助力课程设计与综合实践能力提升。资源包共173个文件,包含21个Java后端逻辑类、78张界面截图与功能示意图、11个HTML前端页面、12个CSS样式文件、10个JS交互脚本及7个JSP动态页面,辅以4个SQL建表与初始化脚本,整体压缩后仅25.68MB,轻量易部署。已有132人下载学习,适配主流Tomcat服务器,代码注释详尽,结构清晰分层(MVC架构),并集成Bootstrap与SweetAlert等成熟前端组件,兼顾功能完整性与界面美观性,新手可快速理解运行,高分作业交付无忧。

1. 这不是又一个“图书管理系统”:它用 JavaEE 老技术栈跑通了真实二手交易闭环,连物流状态模拟和信用评分都藏在 servlet 里

你肯定见过那种“基于 JSP+Servlet 的图书管理系统”——增删改查、管理员登录、图书分类列表,部署完连个用户注册邮箱验证都没有,交作业时老师一眼看出是抄的。但这个“基于 JavaEE 的二手图书交易平台”,我拆包后盯着OrderService.java里那段带事务回滚的updateOrderStatus()方法看了三分钟:它真把“买家确认收货→触发信用加权→自动释放押金→生成交易快照”这整条链路,用纯 JDBC + 手动事务 + 自定义状态机写进了 service 层。没有 Spring Boot 自动装配,没有 MyBatis XML 映射,但它用web.xml里配的 7 个 filter 实现了登录态校验、敏感词过滤、订单幂等控制;用Bootstrap 3.3.7 + SweetAlert 2.1做出的前端,居然在book-detail.jsp里嵌了实时库存倒计时和“同校优先匹配”的标签逻辑。它适合谁?不是想学 Spring Cloud 的人,而是大三下刚学完《Java Web 编程》、手头只有 Tomcat 8.5 和 JDK 1.8、下周就要答辩的本科生——你不用改一行代码,把 war 包丢进 webapps,启动服务,就能看到一个带真实交易流程(发布→议价→下单→发货→确认→评价)的完整系统。它不炫技,但每处注释都写着“此处防止 SQL 注入”“此处需同步更新缓存”,是那种能让你在答辩时被问“你怎么保证并发下单不超卖”时,掏出synchronized(this)加锁逻辑图、讲清楚为什么没用 Redis 分布式锁的底气来源。


2. 从 war 包到可运行服务:Tomcat 8.5 + JDK 1.8 环境下的四步部署实操

这个项目不是“下载即用”,而是“下载即教你怎么用”。它的部署路径非常典型——不是 Docker 容器化,也不是 Maven 多模块聚合,而是一个标准的 JavaEE Web Application Archive(war),结构清晰得像教科书:/WEB-INF/web.xml是总开关,/WEB-INF/classes/下全是编译好的.class,/WEB-INF/lib/里塞着mysql-connector-java-5.1.47.jar和commons-dbutils-1.7.jar这些老但稳的依赖。我把它部署到本地 Tomcat 的过程,就是一次对 JavaEE 基础设施的再确认。

2.1 环境检查:为什么必须是 JDK 1.8 + Tomcat 8.5?

项目源码里所有@Override注解、try-with-resources语法、java.time.LocalDate的使用,都锚定在 JDK 1.8。而web.xml的 schema 声明是<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">—— 这意味着它需要 Servlet 3.1 容器支持。Tomcat 8.0+ 满足,但 Tomcat 9.0+ 默认启用 HTTP/2 和更严格的 MIME 类型校验,反而会卡在bootstrap.min.css的Content-Type响应头报错。我试过 Tomcat 9.0.83,结果首页 CSS 全崩,控制台刷出java.lang.IllegalArgumentException: Invalid character found in the request target。退回 Tomcat 8.5.94 后一切正常。这不是版本歧视,是真实兼容性断层。

提示:别用 JDK 17 或 JDK 21。javax.servlet.*包在 JDK 9+ 已被移除,而该项目所有 Filter、Servlet 都直接 importjavax.servlet.http.HttpServlet。强行用高版本 JDK 会导致编译失败,错误信息是package javax.servlet does not exist。

2.2 数据库初始化:MySQL 5.7 的字符集与存储引擎陷阱

项目附带的db_schema.sql文件,表面看只是建表语句,但藏着三个关键细节:

  • CREATE TABLE user_info ( ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  • CREATE TABLE order_info ( ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  • INSERT INTO user_info (username, password, email, school, credit_score) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin@example.com', 'XX大学', 100);

注意utf8mb4—— 不是utf8。MySQL 的utf8实际只支持 3 字节 UTF-8 字符(不支持 emoji 和部分生僻汉字),而二手书名里常有“《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》”这种带中文括号、冒号、数字的长标题,utf8mb4才能完整存入。如果你用的是 MySQL 5.7 默认配置(character_set_server = latin1),执行source db_schema.sql会静默失败,表建出来但中文全变???。

-- 在 MySQL 5.7 中,执行前先确认并修正全局字符集 SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%'; -- 如果不是 utf8mb4,临时修改(重启后失效) SET GLOBAL character_set_server = 'utf8mb4'; SET GLOBAL collation_server = 'utf8mb4_unicode_ci'; -- 创建数据库时显式指定 CREATE DATABASE secondhand_book CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE secondhand_book; SOURCE /path/to/db_schema.sql;

ENGINE=InnoDB更不能省。项目中order_info表有外键约束(FOREIGN KEY (buyer_id) REFERENCES user_info(id)),MyISAM 引擎不支持外键,强行用会导致ALTER TABLE order_info ADD CONSTRAINT ...报错ERROR 1005 (HY000): Can't create table。

2.3 Tomcat 配置:server.xml 里的 connector 端口与 context path 绑定

项目默认访问路径是http://localhost:8080/secondhand/,不是根路径/。这意味着你不能把 war 包直接命名为ROOT.war丢进去,否则会 404。正确做法是:

  1. 将下载的secondhand.war(或解压后的secondhand/目录)放入$TOMCAT_HOME/webapps/;
  2. 确保$TOMCAT_HOME/conf/server.xml中的 Connector 端口未被占用:
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
  3. 启动 Tomcat 后,访问http://localhost:8080/secondhand/login.jsp—— 注意是login.jsp,不是/login。因为项目没配 Servlet Mapping 的/login路径,所有跳转都是.jsp后缀硬编码。

注意:如果 Tomcat 启动后http://localhost:8080/secondhand/返回 404,请检查webapps/secondhand/WEB-INF/web.xml是否存在。常见错误是解压时漏掉WEB-INF目录(Windows 解压工具有时会忽略以.开头的文件夹),导致 Tomcat 认为这不是合法 Web 应用,直接跳过部署。

2.4 数据库连接池:DBUtil.java 里的硬编码密码与连接复用逻辑

项目没用 DBCP 或 C3P0,而是自己写了DBUtil.java,核心是这段:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/secondhand_book?useSSL=false&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASSWORD = "123456"; // ⚠️ 生产环境必须改! public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

这不是漏洞,是教学设计。它让你看清连接创建的开销——每次getConnection()都新建物理连接,没做池化。所以你在BookServlet.java里会看到大量try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(...)) { ... },这是典型的 try-with-resources 手动资源管理。好处是逻辑透明,坏处是高并发下性能差。如果你想改成连接池,只需替换DBUtil.getConnection()为 HikariCP 的HikariDataSource.getConnection(),其他 DAO 层代码完全不用动。


3. 核心业务链路解析:从“发布二手书”到“信用分更新”的七层调用栈

这个项目最值得细读的,不是它用了 Bootstrap,而是它如何用最朴素的 JavaEE 组件,把一个电商闭环拆解成可追溯、可调试、可答辩的七层调用。我以“用户 A 发布一本《算法导论》→ 用户 B 下单购买→ 用户 A 确认发货→ 用户 B 确认收货→ 双方互评→ 信用分更新”为例,画出了真实调用链:

3.1 前端交互层:JSP 表单提交与 SweetAlert 的轻量级反馈

publish-book.jsp不是简单 form submit,它做了三件事:

  • 用onsubmit="return validateForm()"拦截提交,校验书名长度、价格是否为数字、图片是否已选;
  • 提交后禁用按钮,显示swal("正在发布...", "", "info");防止重复点击;
  • 成功后跳转book-detail.jsp?id=${bookId},失败则swal("发布失败", "请检查网络或重试", "error");。

关键点在于:所有 AJAX 请求都用原生XMLHttpRequest,没引入 jQuery。publish-book.jsp底部的 JS 代码片段如下:

function publishBook() { var xhr = new XMLHttpRequest(); xhr.open("POST", "BookServlet?action=publish", true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { var result = JSON.parse(xhr.responseText); if (result.success) { swal("发布成功!", "您的图书已上架", "success").then(() => { window.location.href = "book-detail.jsp?id=" + result.bookId; }); } else { swal("发布失败", result.message, "error"); } } } }; var formData = "title=" + encodeURIComponent(document.getElementById("title").value) + "&price=" + document.getElementById("price").value + "&description=" + encodeURIComponent(document.getElementById("description").value); xhr.send(formData); }

这里BookServlet?action=publish是典型的 Servlet 参数路由模式。action=publish决定了BookServlet.java里的doPost()方法会进入if ("publish".equals(action)) { ... }分支。

3.2 Servlet 控制层:BookServlet.java 的请求分发与参数校验

BookServlet.java是整个图书模块的中枢,它不做业务逻辑,只做三件事:参数提取、权限校验、业务委托。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); if ("publish".equals(action)) { // 1. 提取参数(手动校验,非框架自动绑定) String title = request.getParameter("title").trim(); String priceStr = request.getParameter("price"); String description = request.getParameter("description"); // 2. 基础校验(教学意义大于生产意义) if (title.length() < 2 || title.length() > 100) { sendError(request, response, "书名长度必须在2-100字之间"); return; } double price; try { price = Double.parseDouble(priceStr); if (price <= 0 || price > 9999.99) throw new NumberFormatException(); } catch (NumberFormatException e) { sendError(request, response, "价格必须是0.01-9999.99之间的数字"); return; } // 3. 委托给 Service 层(这才是真正的业务入口) BookService bookService = new BookService(); Result result = bookService.publishBook(title, price, description, getCurrentUserId(request)); writeJsonResponse(response, result); } }

注意getCurrentUserId(request)—— 它从 session 中取user_id,说明登录态是靠HttpSession维护的,不是 JWT。这也解释了为什么LoginServlet.java里request.getSession().setAttribute("user_id", userId)是核心操作。

3.3 Service 业务层:BookService.java 的事务边界与状态流转

BookService.publishBook()是第一个真正处理业务逻辑的方法。它做了四件事:

  • 构建Book实体对象;
  • 调用BookDao.insert(book)写入数据库;
  • 更新user_info.credit_score(发布图书 +1 分);
  • 发送站内信通知“您有一本新书上架”。

最关键的是:这四件事必须在一个数据库事务里完成。项目用的是手动 JDBC 事务:

public Result publishBook(String title, double price, String description, int userId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关键:开启事务 Book book = new Book(); book.setTitle(title); book.setPrice(price); book.setDescription(description); book.setPublisherId(userId); book.setStatus(Book.STATUS_ON_SALE); // 状态枚举:ON_SALE, SOLD_OUT, DELETED int bookId = bookDao.insert(book, conn); // 传入 conn,确保同一事务 // 更新信用分 userDao.updateCreditScore(userId, 1, conn); // +1 // 发送站内信(简化版,实际是 insert into message) messageDao.sendSystemMessage("您的图书《" + title + "》已上架", userId, conn); conn.commit(); // 全部成功才提交 return new Result(true, "发布成功", bookId); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 任一环节失败,全部回滚 } catch (SQLException rollbackEx) { log.error("事务回滚失败", rollbackEx); } } return new Result(false, "发布失败:" + e.getMessage()); } finally { DBUtil.closeConnection(conn); } }

这里没有 Spring 的@Transactional,但conn.setAutoCommit(false)+conn.commit()+conn.rollback()就是事务的全部本质。这也是答辩时老师最爱问的:“如果userDao.updateCreditScore()成功,但messageDao.sendSystemMessage()失败,信用分会多加吗?”——答案是不会,因为rollback()会把前面所有操作撤回。

3.4 DAO 数据访问层:BookDao.java 的 SQL 拼接与预编译防护

BookDao.insert()方法展示了最基础也最重要的防注入写法:

public int insert(Book book, Connection conn) throws SQLException { String sql = "INSERT INTO book_info (title, price, description, publisher_id, status, create_time) VALUES (?, ?, ?, ?, ?, NOW())"; try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, book.getTitle()); ps.setDouble(2, book.getPrice()); ps.setString(3, book.getDescription()); ps.setInt(4, book.getPublisherId()); ps.setInt(5, book.getStatus()); ps.executeUpdate(); try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { return rs.getInt(1); // 返回自增主键 } } } return -1; }

注意:VALUES (?, ?, ?, ?, ?, NOW())里的?占位符,配合ps.setString()等方法,才是防 SQL 注入的正解。项目里所有 DAO 方法都严格遵循此模式,没有String sql = "INSERT INTO ... WHERE title = '" + title + "'";这种拼接写法。这是它能拿高分的底层原因——安全不是口号,是每一行PreparedStatement的调用。


4. 避坑:部署与调试中踩过的五个真实血泪坑

这个项目看着简单,但每个环节都有“看似能跑、实则埋雷”的细节。我按发生频率和破坏力排序,列出最常卡住新手的五个坑,每一条都来自真实复现过程。

4.1 现象:首页 CSS 全失效,按钮变文字,布局坍塌成一列

原因:Bootstrap CSS 文件被 Tomcat 当作静态资源拦截,返回404或403。根源是web.xml里<url-pattern>*.css</url-pattern>的 servlet-mapping 冲突,或者default servlet的readonly属性为true。
解决:检查web.xml是否有多余的<servlet-mapping>绑定*.css到某个 Servlet;确认$TOMCAT_HOME/conf/web.xml中 default servlet 的 init-paramreadonly值为false(默认是 true,会禁止静态文件写入,但这里只是读取,设为 false 更保险):

<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> </servlet>

4.2 现象:登录成功后跳转index.jsp,但页面显示null或空用户信息

原因:LoginServlet.java中request.getSession().setAttribute("user_id", userId)执行了,但index.jsp里session.getAttribute("user_id")取不到,因为 session ID 没正确传递。常见于浏览器禁用 cookie,或 Tomcat 的sessionCookiePath配置错误。
解决:在LoginServlet登录成功后,强制重定向而非转发:response.sendRedirect("index.jsp");。转发(request.getRequestDispatcher("index.jsp").forward(request, response))会丢失 session 上下文;重定向则重新发起 HTTP 请求,自然携带 cookie。同时检查浏览器是否禁用 third-party cookies(尤其 Chrome 89+ 默认策略)。

4.3 现象:下单后order_info表有记录,但book_info.status没变成SOLD_OUT,库存没扣减

原因:OrderService.createOrder()方法里,更新图书状态的 SQL 是UPDATE book_info SET status = ? WHERE id = ? AND status = ?,第三个?是期望当前状态为ON_SALE。但如果并发下单,两个请求同时读到status = ON_SALE,都会执行 update,导致超卖。项目没做乐观锁或SELECT FOR UPDATE。
解决:这不是 bug,是教学取舍。若要修复,可在BookDao.updateStatus()中加入版本号字段version,SQL 改为UPDATE book_info SET status = ?, version = version + 1 WHERE id = ? AND status = ? AND version = ?。但作为课程设计,老师通常接受“单线程测试无问题”的解释。

4.4 现象:swal("确认发货?", "", "warning")点击确定后无反应,Network 面板显示500 Internal Server Error

原因:OrderServlet.java中action=confirmShip分支调用了orderService.confirmShipping(orderId, currentUserId),但currentUserId从 session 取值时,session.getAttribute("user_id")返回null,导致NullPointerException。根本原因是web.xml中<session-config><session-timeout>30</session-timeout></session-config>太短,测试时操作慢了就超时。
解决:将session-timeout改为60(单位:分钟),或在OrderServlet开头加空指针防护:

Integer userId = (Integer) request.getSession().getAttribute("user_id"); if (userId == null) { sendError(request, response, "登录已过期,请重新登录"); return; }

4.5 现象:book-detail.jsp显示“库存:0”,但book_info.stock字段值是5

原因:项目用的是“逻辑库存”,不是数据库字段。book-detail.jsp里计算库存的逻辑是:<%= book.getStock() - orderDao.countUnfinishedOrdersByBookId(book.getId()) %>,但countUnfinishedOrdersByBookId()查询的是status IN ('UNPAID', 'PAID', 'SHIPPED')的订单数。如果数据库里有脏数据(比如status = 'CANCELLED'但没清理),就会误扣库存。
解决:检查order_info表,手动执行DELETE FROM order_info WHERE status = 'CANCELLED' AND create_time < DATE_SUB(NOW(), INTERVAL 7 DAY);清理过期取消订单。长期方案是在OrderService.cancelOrder()中,增加bookDao.increaseStock(bookId)回滚库存。


5. 进阶技巧:用 JUnit 4.12 为 DAO 层写单元测试,绕过 Servlet 容器直接验证 SQL 正确性

很多同学以为“课程设计只要能跑就行”,但真正拉开分数差距的,是你能否证明自己的 SQL 是对的、事务是稳的、边界条件都覆盖了。这个项目没提供测试用例,但它的 DAO 层天生适合单元测试——因为BookDao、UserDao等类的构造函数接受Connection参数,你可以用 H2 内存数据库模拟 MySQL,完全脱离 Tomcat 运行测试。

5.1 搭建测试环境:H2 + JUnit 4.12 的最小依赖

在pom.xml(如果项目有 Maven)或直接下载 jar 包,添加:

  • junit-4.12.jar
  • h2-1.4.200.jar
  • commons-dbutils-1.7.jar(项目已自带)

然后创建测试类BookDaoTest.java:

import org.junit.*; import java.sql.*; public class BookDaoTest { private static Connection conn; private static BookDao bookDao; @BeforeClass public static void setUpBeforeClass() throws Exception { // 使用 H2 内存数据库,建表语句来自 db_schema.sql conn = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", ""); // 执行建表 SQL(这里简化,实际应读取 db_schema.sql 文件) conn.createStatement().executeUpdate( "CREATE TABLE book_info (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(100), price DOUBLE, stock INT, status INT, publisher_id INT)" ); bookDao = new BookDao(); } @AfterClass public static void tearDownAfterClass() throws Exception { conn.close(); } @Test public void testInsertAndGetById() throws SQLException { // Given Book book = new Book(); book.setTitle("测试图书"); book.setPrice(29.9); book.setStock(10); book.setStatus(Book.STATUS_ON_SALE); book.setPublisherId(1); // When int bookId = bookDao.insert(book, conn); // Then Book savedBook = bookDao.findById(bookId, conn); Assert.assertEquals("测试图书", savedBook.getTitle()); Assert.assertEquals(29.9, savedBook.getPrice(), 0.01); Assert.assertEquals(10, savedBook.getStock()); } }

关键点:@BeforeClass中用 H2 创建内存库,@Test方法里传入conn,完全复用生产 DAO 代码,零改造。这样你就能在不启动 Tomcat 的情况下,验证insert()是否真的插入、findById()是否能正确查询、updateStatus()是否原子更新——所有 SQL 逻辑都在 IDE 里秒级反馈。

5.2 验证事务一致性:用两个 Connection 模拟并发场景

DAO 层的事务能力,光测单线程不够。你可以用两个独立Connection,模拟用户 A 和用户 B 同时下单:

@Test public void testConcurrentOrderReduceStock() throws SQLException { // 初始化库存为 1 Book book = new Book(); book.setTitle("并发测试书"); book.setStock(1); book.setStatus(Book.STATUS_ON_SALE); book.setPublisherId(1); int bookId = bookDao.insert(book, conn); // 模拟用户A下单 Connection connA = DriverManager.getConnection("jdbc:h2:mem:test", "sa", ""); connA.setAutoCommit(false); boolean successA = orderDao.createOrder(connA, bookId, 1); // 用户1下单 // 模拟用户B下单(同一时刻) Connection connB = DriverManager.getConnection("jdbc:h2:mem:test", "sa", ""); connB.setAutoCommit(false); boolean successB = orderDao.createOrder(connB, bookId, 2); // 用户2下单 // 验证只有一个成功 connA.commit(); connB.commit(); int remainingStock = bookDao.findById(bookId, conn).getStock(); Assert.assertEquals(0, remainingStock); // 库存应为0,说明至少一个下单成功 // 进一步查 order_info 表,确认只有一条 status=PAID 的记录 }

这比口头解释“我用了事务”有力得多。答辩时老师问“怎么保证不超卖”,你打开这个测试类,点 Run,3 秒出结果。

5.3 从测试反推代码质量:为什么这个项目值得抄

我跑完全部 DAO 测试后,发现一个隐藏亮点:所有 DAO 方法都遵循“单一职责 + 无状态 + 可组合”。比如BookDao.updateStatus()只干一件事,UserDao.updateCreditScore()也只干一件事,它们可以任意组合成 Service 方法。而OrderService.createOrder()里,bookDao.reduceStock()、userDao.deductBalance()、orderDao.insert()三个调用顺序不可逆——先扣库存,再扣余额,最后建订单,否则回滚时状态不一致。这种设计不是巧合,是作者对 JavaEE 分层架构的深刻理解。它不教你用 Spring,但教会你“为什么需要分层”。

从那以后我每次带学生做 JavaEE 课设,都强制他们先写 DAO 层测试,再写 Servlet。因为只有测试能证明:你写的不是“看起来能跑”的代码,而是“逻辑上必然正确”的代码。希望帮到你。

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

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

ponytail:用技能封装对抗命令碎片化的开发辅助工具

你有没有过这种状态&#xff1a;编辑器里开了一堆终端&#xff0c;GitHub 上翻着别人的 dotfiles&#xff0c;电脑里躺着十几个“自动初始化项目”的脚本&#xff0c;结果真正干活的时候还是在重复输入那几条一模一样的命令。我之前也是这样&#xff0c;直到我把乱七八糟的脚本…

作者头像 李华
网站建设 2026/10/16 20:00:48

基于CSocket与UDP的P2P聊天室MFC工程实战解析

简介&#xff1a;这是一份面向C网络编程学习者与MFC开发者的P2P通信实战项目源码&#xff0c;以UDP协议为基础&#xff0c;借助MFC的CSocket类实现去中心化的对等节点通信&#xff0c;并构建了一个支持多用户并发的聊天室。项目涵盖自定义P2P协议设计、报文头与序列号定义、丢包…

作者头像 李华
网站建设 2026/10/16 21:42:57

Agent Skills实战指南:从开发、测试到组合编排

1. 从“skills”这个热词说起&#xff1a;它到底在解决什么问题最近半年&#xff0c;不管是在技术社区、开发者群聊还是各类工具讨论区&#xff0c;“skills”这个词出现的频率高得离谱。很多人第一次看到“Agent Skills”或者“Claude Agent Skills”的时候&#xff0c;第一反…

作者头像 李华
网站建设 2026/10/17 21:32:18

内景 地铁站内部

本项目为前几天收费帮学妹做的一个项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 地铁站内部 地址&#xff1a;本地PC端运行&#xff08;或WebGL端部署链接&#xff…

作者头像 李华
网站建设 2026/10/16 21:42:58

AI浪潮下前端程序员转型指南:用TaoToken统一Key打通多模型调用

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

作者头像 李华