news 2026/10/19 16:36:57

Spring Boot集成ShardingSphere-JDBC实战:分库分表落地指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot集成ShardingSphere-JDBC实战:分库分表落地指南

简介:本资源是一个基于SpringBoot、ShardingJDBC与MyBatis的轻量级分库分表实战项目,面向Java中高级开发者及分布式系统学习者,聚焦高并发场景下数据库水平扩展的核心痛点。项目完整呈现了从数据源配置、分片策略定义(如用户ID哈希取模)、MyBatis动态SQL适配到多数据源透明路由的全流程实现,助读者深入理解ShardingJDBC在不侵入业务代码前提下的分片能力。压缩包共15个文件(61KB),含5个核心Java类(含Sharding配置与Mapper接口实现)、2个XML映射文件、1个application.yaml分片规则配置、1个SQL建表脚本、1个README.md说明文档及mvnw等构建支持文件,结构精简、开箱即用。目前已有188人学习下载,适合快速上手分库分表原理、复现典型配置、调试分片逻辑或作为微服务数据层演进的参考基线。

1. 分库分表不是为了炫技,而是当单库写入QPS突破3000、主从延迟飙升到8秒时,你手里的Spring Boot服务还能不能扛住下一轮秒杀

这不是一个“学完ShardingSphere就去面试”的玩具项目,而是一套我在某电商中台系统里真实落地的分库分表方案——上线前单库MySQL已承载27张核心业务表,日均订单写入量达420万,高峰期主库CPU持续92%,从库延迟峰值突破8秒,订单状态更新失败率一度达17%。当时团队第一反应是加机器、换SSD、调buffer pool,但DBA拍桌子说:“再堆硬件,成本翻倍,问题照旧。”真正破局点,是把原本“所有订单塞进一张order表”的设计,拆成按用户ID尾号分8库、按订单创建时间分12表的双维度路由策略,并用ShardingSphere-JDBC在应用层完成无感切换。这套方案不依赖DBA介入、不改SQL语法习惯、不引入中间件运维负担,特别适合Java后端同学做毕设、课设或中小团队快速落地。如果你正被“数据量一涨,服务就抖”卡住答辩进度,或需要一份能讲清“为什么这么分、怎么验证分得对、出问题怎么查”的完整交付物,那这份基于Spring Boot + ShardingSphere-JDBC + MyBatis的实战方案,就是你该直接抄作业的底稿。

2. 技术选型与架构设计:为什么放弃MyCat和ShardingSphere-Proxy,坚持用JDBC模式嵌入Spring Boot

2.1 三种主流分库分表方案的硬性对比:延迟、侵入性、运维成本三维度实测

在启动项目前,我拉了三套环境跑压测(统一用JMeter模拟500并发下单,数据量1000万级):

方案端到端平均延迟SQL兼容性问题数/100条运维组件数故障定位耗时(平均)是否需DBA配合
MyCat(1.6.7)128ms17(如子查询、UNION ALL报错)2(MyCat+ZK)42分钟(需查MyCat日志+MySQL慢日志)是(需授权MyCat账号、配置读写分离)
ShardingSphere-Proxy(5.3.2)96ms3(仅复杂分页有偏差)3(Proxy+ZK+MySQL)28分钟(Proxy日志层级深,需逐层过滤)否(但需开放Proxy端口,安全组要调)
ShardingSphere-JDBC(5.3.2)63ms0(完全透传SQL)0(纯Jar包)8分钟(直接看应用日志+Druid监控)否(零DBA协作)

结论很明确:JDBC模式在延迟、兼容性、运维轻量化上全面胜出。尤其对学生场景——没有独立运维资源、答辩演示要一次成功、代码要能本地IDE直连调试——JDBC是唯一能让你“改完配置就能跑通、出错立刻看到堆栈、答辩时现场改个分片键马上验证”的选择。Proxy虽好,但你得先搭好ZooKeeper、配好YAML、处理好连接池超时,这些在毕设截止前48小时,都是致命时间黑洞。

2.2 Spring Boot 3.x + ShardingSphere-JDBC 5.3.2 的最小可行集成结构

关键不是版本数字,而是如何让ShardingSphere不破坏Spring Boot的自动装配哲学。很多同学卡在“加了shardingsphere-jdbc-spring-boot-starter后,MyBatis的SqlSessionFactoryBean初始化失败”,根本原因是starter默认会接管所有DataSource,而你的项目可能已有Druid或HikariCP的自定义配置。

正确做法是:显式声明ShardingSphereDataSource为Primary Bean,并关闭starter的自动装配。以下是pom.xml关键依赖(注意版本对齐):

<!-- Spring Boot 3.x 基础 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis 3.5.11(必须≥3.5.10,否则不兼容ShardingSphere 5.3+) --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <!-- ShardingSphere-JDBC 核心(注意:不要引shardingsphere-jdbc-spring-boot-starter!) --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> </dependency> <!-- Druid连接池(ShardingSphere官方推荐,比HikariCP更易监控分片路由) --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.18</version> </dependency>

提示:shardingsphere-jdbc-core-spring-boot-starter是官方维护的轻量starter,它只注入ShardingSphere相关Bean,不抢MyBatis的DataSource控制权;而老版shardingsphere-jdbc-spring-boot-starter会强制覆盖所有数据源,是学生项目翻车重灾区。

2.3 双维度分片策略设计:为什么用user_id尾号分库 + create_time月分表,而不是简单取模

分片键选错,等于给系统埋雷。我们曾试过order_id % 8分库,结果发现订单ID是雪花算法生成,高位时间戳导致低8位分布极不均匀——8个库中3个库占了72%流量,另外5个库空转。最终定稿策略如下:

  • 分库键:user_id % 8→ 但user_id必须是数字类型且全局唯一(避免字符串哈希不一致)。若业务中user_id是UUID,必须先转成Math.abs(uuid.hashCode()) % 8,并在DAO层统一封装。
  • 分表键:create_time按月分表(如order_202401,order_202402),不是按天。原因:单日订单量约12万,按天分表会产生365张表,超出MySQL单库表数量限制(默认4096,但实际运维建议≤200);按月则全年仅12张,且便于冷热数据分离(如只保留近12个月表)。

分片逻辑不是写在XML里,而是用Java类实现,确保可单元测试:

@Component public class OrderTableShardingAlgorithm implements StandardPipelineShardingAlgorithm<String> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) { // shardingValue.getValue() 即 create_time 字符串,格式为 "2024-03-15 10:20:30" String timeStr = shardingValue.getValue(); String yearMonth = timeStr.substring(0, 7).replace("-", ""); // "202403" // 检查目标表是否存在(避免插入时表不存在报错) if (!availableTargetNames.contains("order_" + yearMonth)) { throw new RuntimeException("Table order_" + yearMonth + " not exists in available targets: " + availableTargetNames); } return "order_" + yearMonth; } @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<String> shardingValue) { // 范围查询(如 create_time BETWEEN '2024-01' AND '2024-03')需返回所有匹配表名 Set<String> result = new LinkedHashSet<>(); for (String target : availableTargetNames) { if (target.startsWith("order_")) { String tableMonth = target.substring(6); // "202401" if (isInTimeRange(tableMonth, shardingValue)) { result.add(target); } } } return result; } private boolean isInTimeRange(String tableMonth, RangeShardingValue<String> value) { // 实现月份范围判断逻辑(略,需解析value.getLowerEndpoint()/getUpperEndpoint()) return true; } }

这段代码的价值在于:它把分片规则变成了可Debug、可Mock、可写JUnit测试的Java对象。答辩时老师问“如果时间范围跨年怎么处理”,你直接打开这个类,指着isInTimeRange方法说:“这里我做了年份校验,比如202312到202402会返回order_202312,order_202401,order_202402三张表”,比背概念强十倍。

3. 核心模块实现:从数据源配置、分片规则到MyBatis无缝适配的全链路代码

3.1 YAML配置文件:如何用最少配置项驱动ShardingSphere完成8库12表路由

application.yml不是堆参数的地方,而是用清晰分组表达架构意图。我把配置拆成三层:基础数据源、分片规则、ShardingSphere特性开关。

# 1. 基础数据源:定义8个物理库,命名规则为 ds_0 ~ ds_7 spring: shardingsphere: # 关键:关闭自动配置,避免与Druid冲突 props: sql-show: true # 开发期开启,生产必须false check-table-metadata-enabled: false # 避免启动时扫描所有库表元数据(慢!) # 2. 数据源定义:每个ds_x对应一个MySQL实例 datasource: names: ds_0,ds_1,ds_2,ds_3,ds_4,ds_5,ds_6,ds_7 ds_0: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_0?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 ds_1: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_1?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 # ... ds_2 到 ds_7 同理(为节省篇幅省略,实际必须写全) # 3. 分片规则:核心!定义逻辑表order如何映射到物理库表 rules: - !SHARDING tables: # 逻辑表名 order: # 真实表名前缀 + 分片后缀(如 order_202401) actual-data-nodes: ds_${0..7}.order_${202401..202412} # 分库策略 database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db-inline # 分表策略 table-strategy: standard: sharding-column: create_time sharding-algorithm-name: tbl-inline # 分片算法定义 sharding-algorithms: db-inline: type: INLINE props: # 注意:INLINE算法只能用于简单取模,复杂逻辑必须用CLASS_BASED algorithm-expression: ds_${user_id % 8} tbl-inline: type: CLASS_BASED props: # 指向上面写的Java类全限定名 strategy: STANDARD algorithm-class-name: com.example.sharding.OrderTableShardingAlgorithm

注意:actual-data-nodes中的${0..7}和${202401..202412}是ShardingSphere的行表达式,它会在启动时自动展开为64个物理节点(8库×12表)。但前提是:你必须提前在8个MySQL实例中,手动创建好所有order_202401到order_202412表。ShardingSphere不会帮你建表,这是学生最容易忽略的“血泪经验”——本地跑通,部署到测试环境时因表不存在直接报错。

3.2 MyBatis层零改造:为什么Mapper XML里写select * from order就能自动路由

ShardingSphere-JDBC的魔法在于它包装了JDBC Driver,在SQL解析层就完成了路由决策。你不需要改任何一行MyBatis代码,只要保证:

  • Mapper接口方法参数中,必须包含分片键字段(如user_id和create_time);
  • SQL中不能用*代替具体字段(否则SELECT * FROM order WHERE user_id = ?能路由,但SELECT * FROM order全表扫描会广播到所有64个节点,性能雪崩);
  • 分页查询必须用PageHelper或ShardingSphere内置分页(LIMIT会被重写为LIMIT 0,10并下发到各节点,再合并结果)。

一个典型Mapper示例:

<!-- OrderMapper.xml --> <mapper namespace="com.example.mapper.OrderMapper"> <!-- 插入:user_id和create_time必须作为参数传入 --> <insert id="insertOrder" parameterType="com.example.entity.Order"> INSERT INTO order (id, user_id, amount, create_time, status) VALUES (#{id}, #{userId}, #{amount}, #{createTime}, #{status}) </insert> <!-- 查询:WHERE条件必须包含分片键,否则广播查询 --> <select id="selectByUserIdAndTime" resultType="com.example.entity.Order"> SELECT id, user_id, amount, create_time, status FROM order WHERE user_id = #{userId} AND create_time >= #{startTime} AND create_time <= #{endTime} </select> <!-- 全局查询(慎用!):会路由到所有64个节点 --> <select id="selectAll" resultType="com.example.entity.Order"> SELECT id, user_id, amount, create_time, status FROM order WHERE status = #{status} </select> </mapper>

关键点:selectByUserIdAndTime方法能精准路由到1个库的1张表(如ds_3.order_202403),而selectAll会变成64次查询再合并,QPS瞬间从5000掉到800。答辩时老师必问:“如果我要查某个用户的所有订单,SQL怎么写?”——答案是:WHERE里必须带user_id = ?,否则就是反模式。

3.3 全局ID生成器:为什么不用数据库自增,而用Snowflake + 时间戳前缀

分库分表后,AUTO_INCREMENT彻底失效——不同库的自增ID会重复。我们弃用MyBatis-Plus的IdWorker,自研一个带业务标识的Snowflake变体,确保ID全局唯一且可读:

@Component public class OrderIdGenerator { // 机器ID:取当前JVM进程ID的低5位(0-31),避免多实例冲突 private final long workerId = (int) (ManagementFactory.getRuntimeMXBean().getPid() & 0x1F); private final Snowflake snowflake = new Snowflake(workerId, 0); public long nextId() { long id = snowflake.nextId(); // 加上前缀:1位业务码(1=订单)+ 6位日期(202403)→ 1202403xxxxxx String datePart = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")).substring(0, 6); return Long.parseLong("1" + datePart + String.format("%09d", id % 1000000000)); } }

生成的ID示例:1202403123456789

  • 前1位1:业务类型(1=订单,2=用户,3=商品)
  • 中6位202403:生成日期(2024年3月)
  • 后9位123456789:Snowflake序列号

好处:

  • 可排序:ID越大,时间越新;
  • 可追溯:看到ID就知道是哪天哪个业务产生的;
  • 防猜解:不像纯Snowflake暴露毫秒时间戳,降低被爬虫遍历风险。

在MyBatis插入时,直接调用:

// OrderService.java @Transactional public void createOrder(Order order) { order.setId(orderIdGenerator.nextId()); // 主动赋值,不依赖数据库 orderMapper.insertOrder(order); }

4. 避坑指南:这5个点踩中任意一个,你的分库分表项目就会在答辩前夜崩溃

4.1 现象:启动时报java.lang.NoClassDefFoundError: org/apache/shardingsphere/infrastructure/transaction/spi/TransactionType

原因:ShardingSphere 5.3.2 依赖shardingsphere-infrastructure-transaction-spi,但某些低版本MyBatis Starter会传递引入旧版shardingsphere-transaction-core,造成SPI接口不兼容。
解决:在pom.xml中强制排除冲突依赖:

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> <exclusions> <exclusion> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-core</artifactId> </exclusion> </exclusions> </dependency>

4.2 现象:SELECT COUNT(*) FROM order返回结果比实际少一半

原因:ShardingSphere默认将COUNT聚合下推到各节点执行,再合并SUM。但如果某张物理表(如order_202401)在ds_0库中为空,ShardingSphere会跳过该节点,导致SUM漏加。
解决:在application.yml中开启count-star-routing:

spring: shardingsphere: props: # 强制COUNT(*)路由到所有节点(即使表为空也执行) count-star-routing: true

4.3 现象:INSERT INTO order (...) VALUES (...)报错Can not find owner data source of table [order_202403]

原因:actual-data-nodes配置中,ds_${0..7}.order_${202401..202412}展开后,ShardingSphere会检查每个组合是否存在。但如果你只在ds_0到ds_3中创建了order_202403表,而ds_4到ds_7没有,就会报此错。
解决:必须在全部8个库中,创建全部12张表。用脚本一键生成:

# 生成建表SQL(以order_202401为例) for db in {0..7}; do echo "CREATE DATABASE IF NOT EXISTS order_db_${db};" echo "USE order_db_${db};" echo "CREATE TABLE IF NOT EXISTS order_202401 (id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2), create_time DATETIME, status TINYINT);" done > init_all_tables.sql

4.4 现象:ORDER BY create_time DESC LIMIT 10查询结果乱序,第1页和第2页数据重复

原因:分页查询时,ShardingSphere会将LIMIT 10下发到每个节点,取各自前10条,再合并排序。但若各节点数据分布不均(如ds_0.order_202403有100条,ds_1.order_202403只有5条),合并后总结果不足10条,且顺序错乱。
解决:启用query-with-cipher-column: false(禁用加密列干扰)并强制使用ShardingSphere分页插件:

@Configuration public class ShardingConfig { @Bean public ShardingSphereDataSource dataSource(@Qualifier("shardingProps") DataSourceProperties props) throws SQLException { return ShardingSphereDataSourceFactory.createDataSource( // ... 数据源配置 Collections.singletonMap("sql-show", "true"), // 关键:启用分页归并 Collections.singletonMap("query-with-cipher-column", "false") ); } }

并在SQL中显式指定分页:SELECT * FROM order ORDER BY create_time DESC LIMIT 0,10(不要用MyBatis-Plus的IPage,它会绕过ShardingSphere分页逻辑)。

4.5 现象:Druid监控页面显示“SQL执行次数=64”,但业务QPS只有500

原因:SELECT * FROM order WHERE status = ?这种无分片键的查询,会广播到全部64个物理节点。即使业务只查10条数据,ShardingSphere也要发起64次MySQL请求。
解决:永远不要在生产环境执行无分片键的WHERE查询。建立开发规范:

  • 在Mapper XML中,所有<select>标签必须在WHERE子句中包含user_id = ?或create_time BETWEEN ? AND ?;
  • 在CI流程中加入SQL静态检查(用Alibaba Druid的WallFilter拦截非法SQL);
  • 答辩演示时,故意写一条广播SQL,然后当场展示Druid监控的64次执行,证明你懂原理。

5. 验证与答辩:用3张表、2个命令、1份日志,10分钟向老师证明你的分库分表真的work了

5.1 验证清单:不跑压测,也能让老师信服的4个关键证据

分库分表项目最怕“看起来很美,一问就懵”。我总结了一套答辩现场可快速演示的验证四件套,全程无需启动JMeter,5分钟内完成:

证据类型操作步骤预期结果老师想听的底层解释
路由日志证据启动应用,执行curl -X POST http://localhost:8080/order/create -d '{"userId":123,"amount":99.9}',查看控制台日志日志中出现Actual SQL: ds_3 ::: INSERT INTO order_202403 (...)“ShardingSphere在SQL解析阶段就确定了目标库表,user_id=123经123%8=3路由到ds_3,create_time当前是3月,所以落表order_202403”
Druid监控证据访问http://localhost:8080/druid/sql.html,查看最近SQL“执行次数”列显示1,而非64;SQL文本显示INSERT INTO order_202403“Druid作为连接池,只看到ShardingSphere封装后的逻辑SQL,证明分片在JDBC驱动层完成,应用层无感知”
物理库验证证据登录MySQL:mysql -h127.0.0.1 -P3306 -uroot -p123456 order_db_3,执行SELECT * FROM order_202403 LIMIT 1查到刚插入的订单记录,且user_id % 8 = 3“数据真实写入ds_3库的order_202403表,不是内存缓存或代理转发”
错误路由证据执行curl -X GET "http://localhost:8080/order/list?status=1"(无分片键的查询)控制台日志出现Broadcast SQL to [ds_0, ds_1, ..., ds_7],Druid显示执行次数=8“我故意触发广播查询,证明系统能识别无分片键SQL,并按设计路由到全部8库(注意:不是64表,因为分表键未命中,只分库不分表)”

提示:把这四步操作录成30秒GIF,答辩时直接投屏播放,比讲10分钟原理更有说服力。

5.2 答辩话术设计:用“问题-决策-验证”结构回答高频问题

老师最爱问“为什么选这个方案”,而不是“这个方案是什么”。把答案压缩成黄金15秒话术模板:

“老师,我们遇到的核心问题是单库写入瓶颈(停顿1秒,看老师点头)。最初尝试加从库,但主从延迟最高到8秒,订单状态更新失败率17%(用数据建立危机感)。我们对比了MyCat、Proxy和JDBC三种方案(手势比划三),最终选JDBC,因为它延迟最低(63ms)、零运维组件、SQL零改造(强调学生友好)。验证时,我现场演示了路由日志(指向屏幕)、Druid监控(切页面)、物理库查询(打开MySQL终端),三者结果一致,证明分片真实生效(自信微笑)。”

对于技术细节问题,用**“现象-根因-动作”** 回答,例如:

  • Q:“分表后跨月查询怎么处理?”
    A:“现象是BETWEEN '2024-01' AND '2024-03'会查3张表;根因是ShardingSphere的RangeShardingValue会自动解析时间范围;我的动作是在OrderTableShardingAlgorithm.doSharding()里实现了月份校验,返回order_202401,order_202402,order_202403三个表名(打开IDE,滚动到该方法)。”

5.3 交付物清单:让老师一眼看出你做了完整工程,不只是调API

一份合格的毕设/课设,交付物必须体现工程闭环。我要求自己每次交付都包含以下6个文件(全部在resources目录下):

文件名类型作用答辩时怎么说
sharding-config.ymlYAML完整分片配置,含8库12表定义“这是系统分片的‘宪法’,所有路由规则从此定义”
init_db_script.sqlSQL创建8个MySQL库、每个库12张表的脚本“运行此脚本,5分钟搭建完整分片环境,无需手动建表”
test_routing_cases.mdMarkdown记录10个典型SQL的路由结果(如user_id=123→ds_3)“这是我手工验证的路由表,覆盖了取模、范围、模糊查询等所有场景”
druid_monitor_screenshot.pngPNGDruid监控页面截图,标出SQL执行次数=1“这张图证明,我的SQL只打到1个物理节点,不是广播”
log_routing_sample.txtTXT控制台日志片段,高亮Actual SQL: ds_3 ::: INSERT...“日志是ShardingSphere的‘黑匣子’输出,直接证明路由发生”
README.mdMarkdown含启动命令、测试API、避坑说明“这是给老师快速上手的说明书,3分钟就能复现我的结果”

从那以后我每次做分库分表项目,都强制走一遍这个验证四件套+交付六件套。不是为了应付答辩,而是因为真正的工程能力,不体现在你写了多少行代码,而体现在你能否用最朴素的方式,向一个没看过你代码的人,10分钟内证明系统真的work了。希望帮到你。

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

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

open-code-review:可编程化代码审查范式

1. 这不是又一个“AI代码审查”玩具&#xff0c;而是一套可嵌入真实开发流水线的开源协作机制“open-code-review”这五个字母组合&#xff0c;乍看像某个GitHub仓库名&#xff0c;实则指向一个正在悄然重构团队协作底层逻辑的实践范式。它既不是SaaS服务&#xff0c;也不是某个…

作者头像 李华
网站建设 2026/10/19 16:35:03

FreeSWITCH集成libnice实现WebRTC ICE穿透实战

1. 项目概述&#xff1a;一次典型的VoIP底层调试实战记录2021年6月11日这个时间戳不是随便写的——它标记着一个真实发生过的、卡在生产环境边缘的VoIP通话故障。当时某跨平台语音通信模块在Linux服务器上持续出现单通、延迟突增和偶发性呼叫失败&#xff0c;日志里反复刷出osi…

作者头像 李华
网站建设 2026/10/19 16:35:01

AI辅助论文写作的正确姿势:从流程提效到合规使用

论文写作这件事&#xff0c;每年都能逼疯一批研究生。尤其到了三四月份&#xff0c;朋友圈里哀嚎一片&#xff1a;有人卡在文献综述整晚睡不着&#xff0c;有人对着摘要改到第十版还是被导师打回来&#xff0c;还有人把实验数据翻来覆去看了三天&#xff0c;就是想不出结果分析…

作者头像 李华
网站建设 2026/10/19 16:32:15

DeskcommCRM实战:本地优先的桌面端客户管理系统

先说结论&#xff1a;DeskcommCRM 是我现在团队每天都在用的桌面端客户关系管理系统&#xff0c;不是那种为了展示功能清单而存在的在线 CRM&#xff0c;是真正冲着“把客户沟通管清楚”这几件事去做的。我们团队一共四个人&#xff0c;三个做销售&#xff0c;一个做运营&#…

作者头像 李华
网站建设 2026/10/19 16:29:53

open-code-review实战:从零搭建可复用的代码评审流程

1. 为什么“open-code-review”值得单独拿出来聊第一次看到“open-code-review”这个标题&#xff0c;我脑子里蹦出来的不是某个具体工具&#xff0c;而是一整套协作习惯。代码评审这件事&#xff0c;几乎每个写过代码的人都经历过&#xff0c;但真正把它做成“开放、可追溯、可…

作者头像 李华
网站建设 2026/10/19 16:29:14

TradingAgents多智能体交易框架:基于LangGraph的架构设计与实战

作为一个长期搞大模型应用开发的从业者&#xff0c;我见到挂着"多智能体交易框架"名头的开源项目&#xff0c;第一反应往往是先打个问号。市面上太多项目只是把几段提示词串在一起&#xff0c;跑起来之后状态互相覆盖、流程不可控、问题根本没法定位。但TradingAgent…

作者头像 李华