news 2026/10/9 10:57:11

Java课程设计火车票预订系统:数据库设计与JDBC事务实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java课程设计火车票预订系统:数据库设计与JDBC事务实战

简介:火车票预订系统源码压缩包,集成了Java与数据库课程设计的核心内容,适合计算机、数学、电子信息等专业学生用作课程设计、期末大作业或毕业设计的参考资料。包内含完整项目代码,共66个文件,主体为49个Java源文件,辅以8个XML配置文件、若干图像资源和清单文件,整包仅273KB,便于快速下载。其中Java代码涵盖车次信息管理、余票查询、预订下单等业务逻辑,XML文件负责界面布局与工程配置,图片资源用于可视化展示,README等文档提供基础使用说明。目前已有161人浏览学习,反映出该课题在课程设计中的常见需求。借助完整源码,读者可系统梳理火车票预订流程、数据库表结构设计与查询逻辑,并在此基础上自行调试、二次开发,非常适合有一定Java基础且愿意深入钻研的开发者参考。

1. 火车票预订系统:一门课程设计如何把数据库和Java缝合起来

如果你正在为Java课程设计选题发愁,又不想做那种烂大街的“学生管理系统”,火车票预订系统是一个性价比很高的方向。它不像电商项目那样要处理支付对账,也不像社交应用那样要面对高并发推送,但它把数据库设计、JDBC操作、事务控制、前端交互这些课程核心考点全部串在了一起:用户注册登录、车次查询、余票扣减、订单生成、退票释放,每一环都在逼你写真实的增删改查,而不是背概念。

这个系统做完,你的收获不止是一个能答辩的Demo,更重要的是你能说清楚一条完整的数据流——从浏览器点击“预订”按钮,到Servlet接收请求,再到JDBC执行UPDATE语句扣减余票,最后把订单写入数据库。这套链路是Java Web开发的基本功,面试时也常被问到。本文按我实际做过的方案来讲:技术栈怎么选、四张核心表怎么建、余票扣减的事务边界划在哪、前端怎么配合,以及五个让你半夜翻车的隐藏坑。

2. 技术选型与数据库设计:先把ER模型建对,再写代码

2.1 技术栈怎么选才不被答辩老师追问

做课程设计,技术栈的选择原则是“老师问得住、自己讲得清”。主流方案有两种:一是JSP + Servlet + JDBC + MySQL,二是Spring Boot + MyBatis-Plus + MySQL。我建议如果你对Java Web还不熟,选前者;如果已经学过框架,选后者。原因很简单:JSP + Servlet是课程大纲里明确要求的内容,答辩时老师问你“请求怎么从页面到数据库”,你从头到尾都是自己写的,链路清清楚楚。而Spring Boot + MyBatis-Plus虽然写起来快,但很多逻辑被框架“黑匣子”吞掉了,老师追问一句“MyBatis-Plus是怎么帮你生成SQL的”,你答不上来反而扣分。

我一般会选JSP + Servlet + JDBC这套经典组合,搭配MySQL 8.0和Tomcat 9。这里有个参数要提前确认:MySQL 8.0的JDBC驱动类名是com.mysql.cj.jdbc.Driver,连接URL里必须带serverTimezone=Asia/Shanghai,否则报时区错误。很多新手在这一步就卡住了,后面我会在避坑章节详细说。

再说数据库设计。火车票预订系统的核心难点不在表多,而在表之间的关系和余票的并发控制。最少需要四张表:用户表、车次表、余票表、订单表。为什么余票要单独建一张表而不是直接在车次表里放一个ticket_count字段?因为一张车次在不同日期、不同出发站和到达站组合下,余票数是不同的。比如G101次列车,北京到上海和北京到南京的余票就是两个独立数据。这个问题想通了,你的ER模型就立住了一半。

2.2 四张表的建表SQL与设计动机

下面是我实际用过的建表SQL,你可以直接复制到MySQL里执行。

-- 用户表 CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(64) NOT NULL, `real_name` VARCHAR(20) NOT NULL, `id_card` VARCHAR(18) NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 车次表 CREATE TABLE `train` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `train_no` VARCHAR(10) NOT NULL UNIQUE, `start_station` VARCHAR(30) NOT NULL, `end_station` VARCHAR(30) NOT NULL, `start_time` TIME NOT NULL, `end_time` TIME NOT NULL, `seat_type` VARCHAR(20) NOT NULL, -- 二等座/一等座/硬卧 `price` DECIMAL(10,2) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 余票表 CREATE TABLE `remain_ticket` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `train_id` INT NOT NULL, `travel_date` DATE NOT NULL, `remain_count` INT NOT NULL DEFAULT 0, FOREIGN KEY (`train_id`) REFERENCES `train`(`id`), UNIQUE KEY `uk_train_date` (`train_id`, `travel_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单表 CREATE TABLE `orders` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL UNIQUE, `user_id` INT NOT NULL, `train_id` INT NOT NULL, `travel_date` DATE NOT NULL, `seat_type` VARCHAR(20) NOT NULL, `ticket_count` INT NOT NULL DEFAULT 1, `total_price` DECIMAL(10,2) NOT NULL, `status` TINYINT NOT NULL DEFAULT 0, -- 0已支付 1已退票 `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`), FOREIGN KEY (`train_id`) REFERENCES `train`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个设计动机要说明。user表的username加了UNIQUE约束,因为登录时要用它做精确匹配,重复用户名会让查询结果集出现多行,代码里rs.next()只取第一条就会拿到错误数据。remain_ticket表用(train_id, travel_date)联合唯一键,这是“某天某车次”粒度的唯一性保证,也是后面做并发扣减时加行锁的抓手。orders表的order_no用UNIQUE,避免并发下生成重复订单号——这里的重复不是指数据库主键冲突,而是业务上同一用户同一车次重复下单的问题,后面会细说。

至于密码字段为什么用VARCHAR(64)而不是直接存明文,是因为我用了MD5加密后存储,64位长度是给加盐后的SHA-256预留的。课程设计里你可以只用MD5,但要跟答辩老师说清楚:真实系统不会这样存,密码必须加盐哈希。

3. 核心Java实现:从DBUtil到余票扣减的完整链路

3.1 DBUtil封装与三个必调参数

JDBC代码最容易写成一坨:每个方法里DriverManager.getConnection()、PreparedStatement、ResultSet、finally块里关连接,重复代码堆成山。我一般会先写一个DBUtil工具类,把连接获取和资源关闭统一收口。

import java.sql.*; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/train_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } 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(); if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

这段代码有三个参数值得你注意。第一个是useSSL=false,MySQL 8.0默认开启SSL握手,本地开发时很多旧版本驱动版本搭配会报SSL警告甚至连接失败,显式关掉省去一堆玄学问题。第二个是characterEncoding=utf8,如果不加,插入中文用户名时可能变成问号。第三个是serverTimezone=Asia/Shanghai,Java 8以上对无时区配置的MySQL连接默认会抛The server time zone value 'CST' is unrecognized异常。

关连接时注意:close()的顺序要和打开顺序相反,先rs再stmt最后conn。我见过有人只关了conn,连接池模式下连接是还回池里了,但ResultSet和Statement还挂在上面,内存泄漏不明显,跑久了必出问题。这一点答辩老师很爱问。

3.2 车次查询与余票扣减:JDBC事务边界怎么划

车次查询是只读操作,用PreparedStatement把搜索条件拼进去就行。真正难的是余票扣减+订单生成这一段,它必须在一个事务里完成。

public boolean bookTicket(int userId, int trainId, String travelDate, int ticketCount) { String checkSql = "SELECT remain_count FROM remain_ticket WHERE train_id = ? AND travel_date = ? FOR UPDATE"; String updateSql = "UPDATE remain_ticket SET remain_count = remain_count - ? WHERE train_id = ? AND travel_date = ? AND remain_count >= ?"; String insertOrderSql = "INSERT INTO orders(order_no, user_id, train_id, travel_date, seat_type, ticket_count, total_price, status) VALUES (?, ?, ?, ?, ?, ?, ?, 0)"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 先查余票,行锁锁住这一行 PreparedStatement checkPs = conn.prepareStatement(checkSql); checkPs.setInt(1, trainId); checkPs.setString(2, travelDate); ResultSet rs = checkPs.executeQuery(); if (!rs.next()) { conn.rollback(); return false; } int remain = rs.getInt("remain_count"); if (remain < ticketCount) { conn.rollback(); return false; } // 2. 扣减余票,用条件UPDATE防止并发超卖 PreparedStatement updatePs = conn.prepareStatement(updateSql); updatePs.setInt(1, ticketCount); updatePs.setInt(2, trainId); updatePs.setString(3, travelDate); updatePs.setInt(4, ticketCount); int rows = updatePs.executeUpdate(); if (rows == 0) { conn.rollback(); return false; } // 3. 生成订单 String orderNo = generateOrderNo(); PreparedStatement insertPs = conn.prepareStatement(insertOrderSql); insertPs.setString(1, orderNo); insertPs.setInt(2, userId); insertPs.setInt(3, trainId); insertPs.setString(4, travelDate); insertPs.setString(5, "二等座"); insertPs.setInt(6, ticketCount); // total_price 这里需要查车次表的价格再计算 insertPs.setBigDecimal(7, new BigDecimal("0")); insertPs.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

这段代码里最关键的是第1步的FOR UPDATE和第2步的条件UPDATE。FOR UPDATE会对remain_ticket表中命中的那行加排他锁,事务提交前其他事务的SELECT ... FOR UPDATE都会阻塞等待。条件更新里的WHERE remain_count >= ?是第二道保险——即使两个请求同时通过了FOR UPDATE查询(实际上不会,因为行锁已经串行化了),也保证扣减后的余票不会变成负数。两道保险一起用,才算把超卖问题堵死。

事务边界就划在“余票扣减”和“订单插入”这两步。注意这里必须先扣余票再插订单,顺序不能反过来。如果先插订单再扣余票,万一扣减失败,订单已经写进去了,你还得再写一条补偿逻辑把它标记为失效。反过来做的话,扣减一旦失败,直接rollback(),订单那步根本不会执行,代码逻辑简单了一个维度。

3.3 订单生成与状态流转的代码落地

订单表里有个status字段,0表示已支付,1表示已退票。退票操作的逻辑和订票刚好相反:要把订单状态改成1,同时把余票加回去。这段操作同样需要事务。

public boolean refundTicket(String orderNo) { String queryOrderSql = "SELECT id, train_id, travel_date, ticket_count, status FROM orders WHERE order_no = ?"; String updateOrderSql = "UPDATE orders SET status = 1 WHERE order_no = ? AND status = 0"; String updateRemainSql = "UPDATE remain_ticket SET remain_count = remain_count + ? WHERE train_id = ? AND travel_date = ?"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 先查订单是否存在且可退 PreparedStatement queryPs = conn.prepareStatement(queryOrderSql); queryPs.setString(1, orderNo); ResultSet rs = queryPs.executeQuery(); if (!rs.next() || rs.getInt("status") != 0) { conn.rollback(); return false; } int trainId = rs.getInt("train_id"); int ticketCount = rs.getInt("ticket_count"); String travelDate = rs.getString("travel_date"); // 更新订单状态 PreparedStatement updateOrderPs = conn.prepareStatement(updateOrderSql); updateOrderPs.setString(1, orderNo); int orderRows = updateOrderPs.executeUpdate(); if (orderRows == 0) { conn.rollback(); return false; } // 归还余票 PreparedStatement updateRemainPs = conn.prepareStatement(updateRemainSql); updateRemainPs.setInt(1, ticketCount); updateRemainPs.setInt(2, trainId); updateRemainPs.setString(3, travelDate); updateRemainPs.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

退票这里有个容易忽略的细节:更新订单状态时加了AND status = 0。这个条件的意义在于防重复退票——如果用户连续点了两次退票,第二次更新时status已经是1了,executeUpdate()返回0,事务回滚,余票不会被重复加回去。这就是用乐观锁思想处理状态流转,比先查再判断再更新更可靠。

travelDate这个字段建议在Java里用java.time.LocalDate类型接收,传给PreparedStatement时调用setObject()方法。MySQL的DATE类型和LocalDate是原生映射的,不需要手动做字符串拼接,避免了一堆时间格式问题。

4. 前端页面与Servlet路由:把数据库里的数据端到端跑通

4.1 JSP页面与数据回显的写法

课程设计的页面不需要花哨,但要把数据回显的逻辑写明白。我的方案是:user_login.jsp和user_register.jsp负责认证,train_list.jsp展示车次查询结果,order_list.jsp展示我的订单。查询页面是全系统的入口,用户输入出发站、到达站、日期,提交到Servlet,Servlet查库后forward回JSP,通过JSTL标签渲染表格。

搜索表单的核心是一个GET请求,参数是start_station、end_station、travel_date。这里我踩过一个坑:travel_date是日期类型,从前端传过来是字符串,如果用户不选日期,默认值怎么处理?我的做法是在JSP里用<input type="date" name="travel_date" value="<%= today %>">预填当天日期,这样后端永远能拿到一个合法的日期字符串。

数据回显时,如果查询结果为空,也要给用户明确提示。我见过很多课程设计页面,查无数据时表格直接空着,用户还以为系统坏了。至少要在表格下方显示一行“没有找到符合条件的车次”,这属于用户体验的基本功,答辩老师很吃这一套。

4.2 Servlet路由与请求参数校验

Servlet的职责是“接收请求→调Service→把结果放request域→forward/redirect”。不要往Servlet里堆SQL,那是DAO层的事。三层结构分开,答辩时你才好讲“分层设计”。

参数校验是个经常被忽略的环节。比如ticketCount,前端传过来的是字符串,你Integer.parseInt()之前要先判断它是不是纯数字,否则用户输入一个“abc”,整个请求直接抛NumberFormatException,用户看到的要么是500页面,要么是Tomcat的异常堆栈——这就把系统内部细节暴露给用户了,很掉价。

我的习惯是写一个简单的校验工具方法:

public static Integer parsePositiveInt(String str) { if (str == null || str.trim().isEmpty()) { return null; } try { int val = Integer.parseInt(str.trim()); return val > 0 ? val : null; } catch (NumberFormatException e) { return null; } }

所有从request.getParameter()拿到的字符串都过一遍这个方法,返回null就说明参数非法,直接request.setAttribute("errorMsg", "购票数量必须是正整数")然后转发回查询页面。这样用户在页面上能看到友好提示,而不是Tomcat的异常堆栈。

另外,Servlet的@WebServlet注解里urlPatterns的命名尽量用/login、/register、/searchTrain、/bookTicket、/refundTicket这种语义化路径,不要用/servlet1这种。倒不是说功能上有什么区别,而是代码可读性和答辩讲解时的顺畅度差异很大。

4.3 订单号生成:并发下不能靠时间戳

订单表里order_no字段设了UNIQUE,这是一个对强迫症友好的设计,但也有代价:你必须保证生成的订单号真的不重复。用System.currentTimeMillis()拼随机数在本地单机演示时没问题,但两个请求在同一个毫秒内提交时,随机数撞车的概率不小,一旦撞上,数据库直接抛DuplicateKeyException,用户看到的就是订票失败。

我的做法是用时间戳 + 用户ID + 随机数三段拼接:

public static String generateOrderNo(int userId) { String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); int random = (int) (Math.random() * 9000) + 1000; return time + userId + random; }

yyyyMMddHHmmss加上用户ID和四位数随机数,碰撞概率已经降到了课程设计可以接受的范围。如果你还想更稳一点,可以再加一个AtomicInteger做自增序列,但那就有点过度设计了。答辩时如果老师问“订单号怎么保证唯一”,你说清楚“时间戳+业务标识+随机数”的组合策略,比说“我直接用主键自增”要加分得多。

5. 避坑实录:这五个坑让我的项目差点翻车

5.1 数据库连接被“占满”,页面越玩越卡

现象:系统跑一段时间后,查询页面响应越来越慢,最后直接卡死,Tomcat控制台报Too many connections。

原因:DBUtil.close()方法里我只关了ResultSet和Statement,漏了Connection。每次请求都新建一个物理连接,用完不关,MySQL默认最大连接数是151,跑到顶就拒绝新连接了。

解决:把conn.close()补上,同时写一个Filter用finally块确保每个请求的数据库连接必然被释放。排查的时候可以执行SHOW PROCESSLIST;看当前连接数,如果Sleep状态的连接堆了几十个,八成就是连接没关。

5.2 时区报错:The server time zone value 'CST' is unrecognized

现象:项目启动后第一次访问数据库,控制台抛SQLException,提示时区无法识别。

原因:MySQL 8.0默认时区配置和JDBC驱动的不一致,驱动要求显式指定时区。

解决:连接URL加上serverTimezone=Asia/Shanghai。如果还不行,就在MySQL里执行SET GLOBAL time_zone = '+8:00';,双保险。

5.3 查询日期“差一天”:从页面到数据库的时间穿越

现象:用户在页面选择“2024-06-01”,查出来的车次却是6月2日的。

原因:数据库连接URL里没有characterEncoding=utf8,但这和日期无关。真正的原因是JSP页面里把日期字符串直接拼进了SQL,而数据库连接的serverTimezone和JVM默认时区不一致,导致字符串到DATE类型的转换偏移了一天。

解决:所有日期参数都用PreparedStatement.setObject()传LocalDate,绝对不要用字符串拼接SQL。这是我用血泪换来的教训——字符串拼接日期看起来方便,但时区、格式、非法值三座大山全压在你身上。

5.4 中文乱码:页面正常,数据库里全是问号

现象:注册用户时,用户名显示正常,但打开数据库一看,username字段存的是????。

原因:JSP页面编码是UTF-8,但JDBC连接没指定字符集,MySQL客户端连接用的默认字符集可能是latin1。

解决:连接URL加characterEncoding=utf8,JSP顶部写<%@ page contentType="text/html;charset=UTF-8" %>,MySQL表结构统一用utf8mb4。三层只要有一层漏了就乱码,排查时从前往后逐层检查。

5.5 并发订票把余票扣成负数

现象:用两个浏览器窗口同时订同一车次的最后一张票,两个请求都提示成功,数据库里remain_count变成了-1。

原因:代码里先SELECT余票再UPDATE扣减,两个步骤之间没有加锁,第二个请求读到的是扣减前的数据。

解决:SELECT语句里加FOR UPDATE,把这一步变成带行锁的查询,另一个事务必须等前一个事务提交后才能执行同一行的FOR UPDATE。再加上UPDATE ... WHERE remain_count >= ?这种条件更新,双保险才算堵死。这也是我前面章节里那段bookTicket代码为什么要那样写的根本原因。

6. 验证与进阶:用并发压测证明你的系统不脆

写完代码别急着提交demo,先用并发测试打一遍,这不仅让你自己有底,答辩时也能拿出来当亮点。

最简单的验证方式是开两个浏览器窗口,登录两个不同账号,同时去订同一车次最后一张余票。看结果是不是只有一个成功一个失败,然后查数据库确认余票不为负。这属于手工并发测试,能发现最明显的超卖问题。

更进一步,可以写一个简易的并发脚本。我常用的是Java内置的ExecutorService模拟多用户同时请求:

ExecutorService pool = Executors.newFixedThreadPool(20); CountDownLatch latch = new CountDownLatch(20); for (int i = 0; i < 20; i++) { final int userId = i + 1; pool.execute(() -> { try { latch.await(); TrainService service = new TrainService(); boolean result = service.bookTicket(userId, 1, "2024-06-15", 1); System.out.println("User " + userId + " : " + result); } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } }); }

脚本里用CountDownLatch让20个线程几乎同时发起订票请求,这样能模拟出临界区竞争的场景。跑完之后去看remain_ticket表的最终余票数和orders表里成功插入的订单数,加在一起必须等于初始余票数。

进阶的方向,我建议你重点做两件事。第一是把密码从MD5换成加盐的SHA-256,顺便写一个PasswordUtil工具类,这会让答辩老师觉得你有安全意识。第二是在remain_ticket表上建一个覆盖索引(train_id, travel_date, remain_count),查询和更新都能用到索引,你可以用EXPLAIN命令给学生演示一下执行计划的变化。

做完这些,这个系统就不再是“一个能跑的CRUD”,而是“一个有正确性保障的CRUD”。我自己当年做这个项目最大的教训就是:功能先跑通很容易,难的是你知道它为什么不会在并发下出错。希望这些经验能帮你少踩几个坑,把课程设计真正做成一件能拿得出手的东西。

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

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

基于机器学习的房价预测系统:从爬虫到Flask部署的完整毕业设计指南

毕业设计选题向来是个让人头大的事。既不能太简单显得没工作量&#xff0c;又不能太复杂搞得自己毕不了业。如果你正在找Python方向的题目&#xff0c;我强烈建议你看看“基于机器学习的房价预测系统”这个方向——它能串起爬虫、数据清洗、特征工程、scikit-learn建模、Flask …

作者头像 李华
网站建设 2026/10/9 10:56:58

iOS 27下打印机失联?openssl生成825天证书修复全教程

上周我把主力机升级到 iOS 27 后&#xff0c;办公室那台某品牌打印机的 Web 后台、无线扫描、手机打印一块儿“罢工”了。打印任务在队列里躺了十分钟&#xff0c;手机屏幕才蹦出一句“无法连接打印机”。最开始我以为是固件兼容问题&#xff0c;甚至把打印机恢复出厂设置折腾了…

作者头像 李华
网站建设 2026/10/9 10:54:54

Linux计划任务与进程:crond调度机制及故障排查实战

干运维这行&#xff0c;最怕的就是半夜收到告警&#xff0c;跑上服务器一看&#xff0c;某个该跑的备份没跑&#xff0c;该清理的日志堆积如山。而排查这类问题&#xff0c;绕不开两个关键词&#xff1a;Linux计划任务和进程。很多人到现在还把“计划任务”理解成一行crontab配…

作者头像 李华
网站建设 2026/10/9 10:54:26

AI编程实战指南:从提示词到代码副驾驶的高效用法

很多人跟我说&#xff0c;AI写代码就是“人工智障”&#xff0c;让它写个排序算法都能跑出一堆莫名其妙的报错。我一开始也这么觉得&#xff0c;直到我花了两周时间认真研究了一下自己到底是怎么提问的&#xff0c;才反应过来&#xff1a;不是AI太蠢&#xff0c;是我根本没把它…

作者头像 李华
网站建设 2026/10/9 10:53:48

OpenClaw 容器化实战:镜像构建、数据迁移与 Token 配置全攻略

用 Docker 部署 OpenClaw 这件事&#xff0c;其实坑不在 Docker 本身&#xff0c;而在编译、迁移和 Token 配置这三个环节。最近帮朋友迁移一台跑了半年多的 OpenClaw 服务&#xff0c;数据卷、镜像、环境变量一路折腾下来&#xff0c;踩了不少雷。这篇就把完整过程写出来&…

作者头像 李华