新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java图书管理系统开发复盘:从集合框架到JDBC数据库实战

发布时间:2026/10/1 15:10:37来源:尧图网络
Java图书管理系统开发复盘:从集合框架到JDBC数据库实战
1. 项目概述与复盘价值1.1 为什么偏偏是图书管理系统很多Java初学者都会遇到一个困惑跟着教程敲了一堆语法一到自己动手写项目就大脑空白。我当时也是这个状态JavaSE学了集合、面向对象、异常处理、IO流感觉什么都会一点但真让我做一个能跑起来的完整程序完全不知道从哪里下手。图书管理系统能成为Java基础阶段的“经典练习项目”不是没有原因的。你再想想登录验证涉及用户输入处理和字符串比较图书的增删改查覆盖了数组/集合的操作借书还书牵扯到对象状态的切换这些场景几乎完美贴合JavaSE的核心知识点。更妙的是它不需要数据库也能做成文件存储版引入了JDBC之后又能无缝升级成数据库版——从一个纯JavaSE练习平滑过渡到JavaWeb开发这个坡度设计得刚刚好。我自己在做这个项目复盘的时候最大的感受是图书管理系统就像Java基础语法的一场大阅兵。你学过的每一个知识点都能在这里找到一个具体的用武之地。如果你还在犹豫拿什么项目练手这个项目是最稳妥的选择既不简单到无聊也不复杂到劝退。1.2 盘点项目里真正用到的Java基础知识点这个项目虽然叫“基础”但涵盖的知识面其实相当广。我在实际编码过程中几乎把JavaSE的重要章节都过了一遍面向对象三大特性封装体现在Book类私有字段加getter/setter继承体现在把公共的BaseDao抽出来让子类复用多态则体现在用接口定义BookService规范再用实现类填充细节。集合框架用ArrayList存储图书列表用HashMap做用户登录凭证的缓存用Iterator遍历删除元素。异常处理自定义一个BookNotFoundException处理图书Id不存在时的业务逻辑catch SQLException做数据库操作的异常兜底。IO流与序列化文件版中用ObjectOutputStream把ArrayList 整体写入book.dat文件数据库版中用JDBC把记录读进ResultSet再封装成对象。泛型弄了一个泛型DAO基类定义了T findById(int id)和ListT findAll()子类继承后指定具体实体类型代码复用率一下就上去了。常用类SimpleDateFormat处理借书日期和应还日期BigDecimal处理图书定价避免浮点误差。有些初学者会觉得做图书管理系统有点“老土”街上到处都是更炫酷的项目。但实话实说花里胡哨的电商秒杀系统、高并发IM软件基础阶段你根本hold不住强行上手只会被各种框架和中间件淹没。图书管理系统恰好踩在舒适区的边缘向前走一步是挑战回头看一步是复习这种节奏对打地基来说是最舒服的。2. 项目整体架构与设计思路2.1 功能模块划分先画好地图再动工动手写代码之前我的习惯是先在纸上把功能模块拆出来。图书管理系统不管怎么变核心模块都是下面这几块用户登录模块管理员登录验证、权限校验、忘记密码处理。图书管理模块图书信息的增删改查包括ISBN、书名、作者、出版社、定价、库存数量。借阅管理模块借书操作、还书操作、借阅记录查询、逾期判断。统计与展示模块图书总数统计、类型分布统计、借阅排行榜。模块划分这件事看着简单实际做起来是有讲究的。我一开始犯过的错误是把所有逻辑全塞在main方法里导致整个程序是一个巨型面条代码找bug找到怀疑人生。后来学乖了按照三层架构的思路来拆表现层接收用户输入、展示结果、业务层处理借书还书的业务规则、数据层负责存取图书数据。这样拆完以后每个类职责单一改起来心理负担小得多。比如我后来想从文件存储切到数据库存储只需要替换数据层的实现类业务层和表现层的代码几乎不用动。这就是分层的威力——它让你敢改代码。2.2 代码结构规划包名和类名的设计智慧包结构的好坏直接决定你后期定位问题的效率。我这次项目的包名规划如下com.learn.library ├── model // 实体类Book、BorrowRecord、AdminUser ├── dao // 数据访问层BookDao、BorrowDao、AdminDao ├── service // 业务逻辑层BookService、BorrowService ├── controller // 控制层LoginController、BookController ├── util // 工具类DBUtil、DateUtil └── app // 程序入口LibraryApp这个包结构看起来平淡无奇但这是无数Java项目沉淀下来的通用惯例。我见过有人把几十个类全扔在一个包里类一多找起来眼睛都花了。命名规范这个东西基础阶段养成好习惯后面做真正的企业项目才会少挨骂。类名设计遵循“见名知意”原则模型类用名词工具类用Util结尾接口用I开头或者直接以能力命名。方法名用动词开头get、set、add、remove、find、update一看就知道干什么的。我踩过的坑是在一个DAO里写了saveBookInfo在另一个类里写了addBook名字不统一调用的时候全靠记忆非常痛苦。2.3 从文件存储到数据库的演进思路文件存储版和数据库版并不是两个割裂的项目反而更像一个项目的两个迭代阶段。文件存储版帮你理解数据的持久化本质数据库版让你接触真实开发中的数据管理方式。文件版的核心逻辑特别直白程序启动时从book.dat读数据到内存ArrayList程序退出或每次操作后把ArrayList整个写回文件。这种全量读全量写的方案虽然效率不高但胜在符合直觉——计算机里的存储本来就是这么回事内存操作快但断电丢失文件存放慢但能持久保留。引入数据库之后操作就变成了通过JDBC连接MySQL执行INSERT、DELETE、UPDATE、SELECT语句。底层从“读写文件”换成了“执行SQL”但上层业务逻辑几乎没有变化。我强烈建议初学者先做文件版再做数据库版你会在切换的瞬间理解DAO模式到底在解耦什么东西。3. 关键模块的实现拆解3.1 面向对象建模Book类的设计细节实体类是整个系统的数据核心。我最初设计的Book类比较粗糙就是简单地把数据库字段映射成Java属性。但实际编码过程中我发现一些容易被忽视的细节开始影响后续开发了public class Book { private String bookId; // 图书编号业务主键 private String isbn; // ISBN号全国统一 private String title; // 书名 private String author; // 作者 private String publisher; // 出版社 private BigDecimal price; // 定价用BigDecimal避免精度丢失 private int totalStock; // 总库存 private int availableStock; // 可借库存 private Date createTime; // 入库时间 private Date updateTime; // 信息更新时间 // getter/setter 省略 // 业务方法判断是否可以借出 public boolean canBorrow() { return availableStock 0; } // 业务方法借出后扣减库存 public void borrowOne() { if (availableStock 0) { throw new IllegalStateException(库存不足无法借出); } availableStock--; } // 业务方法归还后增加库存 public void returnOne() { availableStock; } }这里有个非常好的设计细节把“借书扣库存”的逻辑封装在Book对象内部而不是让Service层直接去修改availableStock字段。这就是面向对象里的“行为内聚”——对象不仅要存储数据还要管理自己的状态变化。滥用getter/setter然后到处改字段会让你有一天陷入“数据被改得乱七八糟但完全不知道谁改的”的困境。价格用BigDecimal而不是double算是个经典陷阱了。之前觉得double照样能算直到我调试时发现借书逾期罚款计算出现了0.30000000000000004这种结果才明白浮点数在二进制里根本没法精确表示十进制小数。图书定价、罚款金额这类数据用BigDecimal是底线要求。3.2 登录模块字符串比较中暗藏的安全意识登录模块虽然简单但它是理解安全编码的启蒙点。我最初的实现非常天真public boolean login(String username, String password) { // 反模式示例千万别学 if (user.getPassword().equals(password)) { return true; } return false; }问题出在哪里如果数据库中存的是明文密码一旦数据文件泄露所有人的密码都暴露了。虽然基础项目不要求做到多高的安全级别但养成“密码不存明文”的意识却非常重要。我引入了一个简单的哈希处理把用户输入的密码加盐后用SHA-256哈希再与存储的哈希值比较。public static String hashPassword(String password, String salt) { try { MessageDigest md MessageDigest.getInstance(SHA-256); md.update((salt password).getBytes(StandardCharsets.UTF_8)); byte[] bytes md.digest(); StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(加密算法不存在, e); } }登录成功之后我还会设置一个“当前登录用户”的会话变量后续所有操作都会校验这个变量避免未登录直接操作业务数据。有些初学者图省事登录判断全在控制器里用if else硬编码我后来把权限校验抽成了一个拦截方法代码清爽很多。3.3 图书CRUD集合遍历与删除操作的那些坑图书管理最核心的操作就是增删改查。按理说CRUD是基础中的基础但我在这块儿发现了不少值得记录的细节尤其是删除操作。Java基础里讲遍历集合时反复强调不要在foreach里直接删除元素会抛ConcurrentModificationException。原理很简单foreach底层用的是迭代器迭代器维护了一个modCount计数器你在遍历过程中修改集合结构modCount变了迭代器就会觉得“不对有人动了我的数组”立即抛异常。正确的删除姿势是使用迭代器的remove方法public boolean deleteBook(String bookId) { IteratorBook iterator books.iterator(); while (iterator.hasNext()) { Book book iterator.next(); if (book.getBookId().equals(bookId)) { iterator.remove(); // 安全删除 return true; } } return false; }或者用JDK 8的Lambda表达式一行搞定books.removeIf(book - book.getBookId().equals(bookId));这里还有个更隐蔽的性能问题。如果图书数量大了线性查找的复杂度是O(n)每次删除都得从头遍历。为了提高效率我引入了一个HashMapString, Integer做索引键是bookId值是bookId在ArrayList中的下标。每次添加图书时维护索引删除时查索引直接定位删除后再重建索引。实际体验下来对于上千本图书的规模这个优化感知不明显但让我理解了“索引为什么能加速查询”这个数据库领域的核心概念。3.4 借还书逻辑事务思维在基础阶段的播种借书流程看起来就是三步查图书、扣库存、加借阅记录。但这里有个经典问题如果扣库存成功了加借阅记录时抛了异常怎么办没有数据库事务时我第一版代码是这样的public void borrowBook(String bookId, String username) { // 简化版逻辑存在数据不一致风险 book.borrowOne(); // 先扣库存 borrowRecordDao.insert(new BorrowRecord(bookId, username, new Date())); // 再加记录 }如果insert这行抛了SQLException库存已经被扣了但借阅记录没写进去到时候用户拿着书走了系统却不知道这本书借给了谁。这种数据不一致问题在基础的ArrayList版本里不明显因为内存操作基本不会出错但一旦换成数据库操作异常发生的概率就上升了。处理办法要么用数据库事务统一控制要么在程序里手动补偿。数据库版里我用了Connection的setAutoCommit(false)、commit()和rollback()public void borrowBook(String bookId, String username) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 扣减库存 bookDao.updateStock(bookId, -1, conn); // 添加借阅记录 borrowDao.insert(new BorrowRecord(bookId, username, new Date()), conn); conn.commit(); // 全部成功则提交 } catch (SQLException e) { if (conn ! null) { conn.rollback(); // 任一失败则回滚 } throw new RuntimeException(借书失败事务回滚, e); } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }这段代码虽然基础但“要么全成功要么全失败”的事务思想是后面理解Spring声明式事务、分布式事务的基础。基础阶段把事务玩明白了比多背三个框架接口都管用。3.5 自定义异常让错误处理有明确的出口项目初期我用返回值表示错误“返回-1表示失败返回0表示成功返回2表示未找到”。时间一长就发现问题了调用方根本记不住这些魔法数字的含义而且业务错误被吞没在普通的返回值里排错靠肉眼扫描。后来我引入了异常体系// 自定义业务异常基类 public class LibraryException extends RuntimeException { public LibraryException(String message) { super(message); } } // 图书不存在的异常 public class BookNotFoundException extends LibraryException { public BookNotFoundException(String message) { super(message); } } // 库存不足的异常 public class InsufficientStockException extends LibraryException { public InsufficientStockException(String message) { super(message); } }然后在业务层抛出具体的异常在控制层统一捕获并提示用户public void deleteBook(String bookId) { Book book bookDao.findById(bookId); if (book null) { throw new BookNotFoundException(图书编号【 bookId 】不存在); } // 如果图书正在被借阅不允许删除 if (borrowDao.countActiveByBookId(bookId) 0) { throw new BusinessException(图书正在借阅中无法删除); } bookDao.delete(bookId); }能把自定义异常玩明白说明你理解了Java异常处理的精髓异常不仅仅是错误提示更是一种流程控制机制它能让业务逻辑从繁琐的错误分支中解放出来。4. 数据持久化从对象流到JDBC的跳跃4.1 文件存储版对象序列化实战文件存储版里最核心的技术就是Java对象序列化。所谓序列化说白了就是把内存里的对象变成字节流存到文件里反序列化就是再把字节流读回来变成对象。// 保存图书列表到文件 public void saveBooks(ListBook books) { try (ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(book.dat))) { oos.writeObject(books); } catch (IOException e) { e.printStackTrace(); } } // 从文件读取图书列表 SuppressWarnings(unchecked) public ListBook loadBooks() { File file new File(book.dat); if (!file.exists()) { return new ArrayList(); } try (ObjectInputStream ois new ObjectInputStream(new FileInputStream(file))) { return (ListBook) ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return new ArrayList(); } }这里有个必要的提醒被序列化的Book类必须实现Serializable接口否则运行时会直接抛NotSerializableException。另外用try-with-resources语法能自动关闭流避免手动close遗漏导致的资源泄漏。我第一版代码用的是手动close写了三行finally里什么都没干后来才知道这个语法糖能让代码精简不少。文件存储版的局限很明显并发读写容易出问题数据量大了以后性能扛不住也没有便捷的查询语言。但它的学习价值在于你能亲眼看到数据是如何被持久化的这种“眼见为实”的感觉对理解抽象的数据库概念是很好的铺垫。4.2 JDBC连接手写连接池的意外收获数据库版的核心就是用JDBC操作MySQL。我第一步是引入MySQL驱动依赖然后写了一个DBUtil工具类获取Connectionpublic class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/Shanghai; private static final String USERNAME root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }写完之后我测试了一下每次查询都新建一个Connection关闭后再连接几百次操作耗时感人。后来我研究了一下明白了这是“获取连接”的高成本带来的问题。数据库连接不是普通的对象创建它涉及TCP握手、认证、分配资源等过程每次新建是非常浪费的。于是我自己手写了一个极简连接池public class SimpleConnectionPool { private static final int MAX_SIZE 10; private static final ListConnection pool new ArrayList(); public static synchronized Connection getConnection() throws SQLException { if (pool.isEmpty()) { return createNewConnection(); } return pool.remove(pool.size() - 1); } public static synchronized void returnConnection(Connection conn) { if (pool.size() MAX_SIZE) { pool.add(conn); } else { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这个手写连接池虽然简陋但让我彻底理解了HikariCP、Druid这些连接池框架到底在解决什么问题。等后面用上Spring Boot你配置Druid连接池的时候亲手实践过的人看配置项完全不虚。4.3 PreparedStatement为什么拼SQL极其危险很多初学者刚开始学JDBC时图方便喜欢用字符串拼接的方式构造SQL// 反模式示例会引发SQL注入 String sql SELECT * FROM book WHERE title title ;如果有人在输入框里输入 OR 11拼接出来的SQL就变成了SELECT * FROM book WHERE title OR 11这个查询条件恒为真会查出所有图书信息。如果换成DELETE语句后果更严重——整个表的数据都可能被清空。这就是臭名昭著的SQL注入攻击。用PreparedStatement的话参数和SQL模板分离预处理阶段就会对值做类型转换和转义处理注入攻击基本无门public ListBook searchBooksByTitle(String title) { String sql SELECT * FROM book WHERE title LIKE ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % title %); try (ResultSet rs ps.executeQuery()) { ListBook result new ArrayList(); while (rs.next()) { result.add(mapRowToBook(rs)); } return result; } } catch (SQLException e) { throw new RuntimeException(查询图书失败, e); } }PreparedStatement还有额外的好处相同结构的SQL只需要编译一次执行阶段只需要传参性能上也有优势。我在实战中用它解决了模糊查询、按条件组合查询等各种场景算是我在基础阶段学得最值的一个知识点。5. 常见问题排查与实战心得5.1 中文乱码问题的追根溯源做文件存储版的时候我遇到过图书名称显示乱码的情况。排查思路是这样的首先确认写入文件时的编码和读取文件时用的编码是否一致。我用ObjectOutputStream写文件时默认使用了平台默认编码而读出来的字符串又是用另一个编码解释的自然就对不上了。解决方法是统一使用UTF-8编码并显式指定不依赖平台默认编码。数据库连接串里也要加上useUnicodetruecharacterEncodingutf8否则MySQL默认的latin1字符集会让你想掀桌。乱码问题在Java开发中出现的频率非常之高核心思路只有一个写和读的编码必须一致。排查时先确认“数据在哪个环节被转换了编码”再用二分法逐步定位一般都能快速搞定。5.2 借阅日期计算SimpleDateFormat的线程安全隐患计算逾期天数的时候我写了一个看似简单的工具方法public static int calculateOverdueDays(Date borrowDate, Date returnDate) { long diff returnDate.getTime() - borrowDate.getTime(); return (int) (diff / (24 * 60 * 60 * 1000)); }这段代码看起来逻辑通顺但实际有两个问题。第一个是时间戳相减得到的毫秒差除以一天的毫秒数时如果跨了夏令时或者闰秒天数计算可能不准。对于基础项目来说这个问题可以暂时容忍但至少要知道它的存在。第二个问题更隐蔽我在多线程测试时发现 SimpleDateFormat 不是线程安全的。它的内部使用了Calendar对象在多线程环境下一个线程正在格式化日期另一个线程修改了Calendar的状态得到的结果可能完全错乱。解决方案是用ThreadLocal包一层或者直接用JDK 8的DateTimeFormatterpublic static final DateTimeFormatter DATE_FORMATTER DateTimeFormatter.ofPattern(yyyy-MM-dd);LocalDate本身就不可变且线程安全用它做日期计算也更符合直觉。我后来把整个项目的日期处理都换成了Java 8的日期时间API代码瞬间清爽了不少。5.3 实测出现频率最高的10个报错及解决方案我把自己做项目过程中遇到的报错整理成了一张速查表全是真实踩坑记录含金量很高报错信息出现原因解决方案ConcurrentModificationException遍历集合时直接删除了元素用iterator.remove()或removeIf()java.io.NotSerializableException实体类没有实现Serializable接口在类声明处加上implements SerializableClassNotFoundException忘记引入MySQL驱动依赖pom.xml中添加mysql-connector-java依赖SQLException: No suitable driver驱动类未加载在静态块中Class.forName()加载驱动NullPointerException数据库查询返回了空结果用Optional或先判空再访问属性NumberFormatException用户输入非数字字符串后强制转换用正则校验输入格式后再解析UnsupportedOperationExceptionArrays.asList得到的List不支持add/remove用new ArrayList(Arrays.asList(...))包装一层FileNotFoundException存储文件的路径不存在先判断父目录是否存在不存在则mkdirs()The server time zone value is unrecognizedMySQL连接串缺少时区配置URL中加上serverTimezoneAsia/ShanghaiDuplicate entry xxx for key PRIMARY业务主键重复插入插入前先查重或设置唯一索引异常捕获这些报错里面有几条值得多说几句。Arrays.asList这个坑我在做图书列表排序的时候踩过一次拿到的list底层其实还是数组不支持结构上的增删操作直接调用add就抛异常。有点像是你拿到了一个玻璃杯看着跟普通杯子一样但你非要往里钉钉子那肯定要碎的。5.4 复盘感悟从“会语法”到“能写项目”的最后一公里这个图书管理系统做下来我最深的体会是基础语法的学习和项目的落地之间有一道巨大的鸿沟而跨越这道鸿沟的唯一方式就是写完整的项目。你光知道List有add、remove、get方法和你真的在图书管理里用ArrayList管理一堆Book对象感受完全不一样。语法学习是线性积累而项目开发是网状编织——你需要把分散的知识点串起来按照业务逻辑重新组织它们。这个过程无法靠刷题替代。如果你准备做这个项目我建议你采取“增量迭代”的节奏第一版控制台程序文件存储全部代码写在main方法里。先跑通。第二版按照MVC思路重构拆分成model、dao、service、controller四大层。感受分层的好处。第三版引入MySQL数据库替换文件存储。体验JDBC和DAO模式的威力。第四版增加自定义异常、事务处理、日期计算优化。提升代码的健壮性。每一版都解决上一版发现的问题这个“发现问题-分析问题-解决问题”的循环才是做项目最值钱的部分。代码写得多不如写得好写得好不如复盘得深刻。我在实际写完这个项目之后再回头去看那些以前觉得晦涩的Java面试题什么“重载和重写的区别”“深拷贝和浅拷贝”“ArrayList和LinkedList的适用场景”一下子全都理解了——因为我在项目里真的遇到过这些问题有了具象的认知抽象的概念就变得很好理解了。这也是为什么很多人都说做项目是掌握Java的不二法门。最后再分享一个小技巧项目做完之后把代码放到Gitee或GitHub上每个阶段打一次tag。这样你不仅留了下成长的轨迹以后面试时还能把仓库链接贴在简历上。我当时就是靠这个项目的Gitee链接在毕业找实习时让面试官有了具体的提问依据。图书管理系统虽然是个小项目但把它做透做完整它能带来的东西远远超过项目本身。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

长三角GEO优化服务商合作实力参考与透明报价服务商 2026/10/1 15:46:03

长三角GEO优化服务商合作实力参考与透明报价服务商

长三角GEO优化服务商合作实力参考与透明报价服务商当采购决策的第一站搬到AI对话框,企业能否被AI主动推荐,直接决定能否进入客户候选名单。选对生成式引擎优化服务商,是制造业、装备企业、法律服务、生鲜配送等各行业在AI搜索时代抢跑的关键一…

阅读更多 →
Linux top命令详解:从输出含义到CPU、内存与I/O排障实战 2026/10/1 15:46:03

Linux top命令详解:从输出含义到CPU、内存与I/O排障实战

1. 为什么top调用的输出总让人“看不懂”——先搞清楚每个区块在讲什么top命令大概是每个运维人除了ls之外敲得最多的一条命令。服务器一卡,下意识就是登上去敲个top看一眼。但说句实话,我见过太多人(包括几年前的我)看着屏幕上一…

阅读更多 →
苏州知名的GEO优化服务专业公司推荐 客户真实体验口碑 2026/10/1 15:46:03

苏州知名的GEO优化服务专业公司推荐 客户真实体验口碑

当越来越多的采购决策从搜索引擎对话框转移到AI问答窗口,能否被豆包、DeepSeek、元宝、千问、文心一言、Kimi、ChatGPT、Gemini等主流AI平台主动推荐,正在成为企业能否进入客户候选名单的关键。GEO优化服务(生成式引擎优化)因此站上了营销舞台的中央。在…

阅读更多 →
聚合增长AI GEO优化服务商价格公道不玩套路 2026/10/1 15:46:03

聚合增长AI GEO优化服务商价格公道不玩套路

苏州聚合增长信息科技有限公司(简称聚合AI GEO)是一家深耕生成式引擎优化(GEO,Generative Engine Optimization)领域的企业级AI全域营销服务商。公司以精确投喂交叉验证为核心底层逻辑,将生成式引擎优化与智能体技术深度融合,帮助制造业企业在…

阅读更多 →
cgft-llm Ollama快速上手:5分钟本地部署开源大模型(含Linux手动安装避坑教程) 2026/10/1 15:46:03

cgft-llm Ollama快速上手:5分钟本地部署开源大模型(含Linux手动安装避坑教程)

cgft-llm Ollama快速上手:5分钟本地部署开源大模型(含Linux手动安装避坑教程) 【免费下载链接】cgft-llm cgft-llm 是一个学习大语言模型(LLM)开发的开源资源。它提供代码、文档和视频教程,帮助用户通过实践…

阅读更多 →
2026年长三角GEO优化服务商口碑公司汇总,成立年限与行业从业资质等级排行 2026/10/1 15:45:56

2026年长三角GEO优化服务商口碑公司汇总,成立年限与行业从业资质等级排行

苏州聚合增长信息科技有限公司(简称聚合AI GEO)是聚焦生成式引擎优化的企业级AI全域营销服务商,核心产品为聚合AI GEO国内版与国际版代运营服务,以精确投喂交叉验证为底层逻辑,帮助制造企业解决AI搜索时代信息错位、获客成本高的痛点&#xf…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉