Spring Boot多数据源路由原理与分库分表实战避坑指南
简介本资源是一份面向Java后端开发者与数据库架构学习者的分库分表实战项目包聚焦高并发、大数据量场景下的数据库水平扩展难题以Sharding-JDBC为核心框架实现多数据源动态切换与透明化分片。资源包含73个文件主体为18个Java业务与配置类、21个编译后class文件、18个XML配置及映射文件涵盖MyBatis映射与Sharding规则定义辅以SQL建表脚本、Maven构建配置pom.xml、Eclipse项目元数据.project/.classpath及IDE调试偏好设置等整体仅66KB轻量易导入。已有2068人学习下载适合中高级开发者快速搭建分库分表验证环境。读者可直接运行SSMDemo示例项目完整掌握sharding-config.yaml配置要点、多数据源初始化流程、分片键路由逻辑、跨库SQL执行效果及基础事务行为无需修改业务代码即可直观理解Sharding-JDBC的集成方式与典型目录结构设计。1. 分库分表不是“加机器就能扛住流量”的玄学而是多数据源切换必须直面的路由控制问题你刚接手一个日增百万订单的电商后台数据库单表突破 2 亿行慢查询从“偶发”变成“每分钟必报”DBA 建议“赶紧分库分表”。你兴冲冲搭好 ShardingSphere配置完逻辑库、真实数据源、分片键、分片算法一跑压测——服务直接 500日志里满屏No route to datasource和Cannot determine target datasource。这不是环境没配对也不是 SQL 写错了而是分库分表落地最隐蔽的断点多数据源切换的上下文传递失效了。它不发生在 SQL 解析层也不在 JDBC 连接池里而是在你的业务线程里——事务传播、异步调用、线程池复用、甚至一次Async注解都可能让分片键丢失、数据源路由错乱、跨库事务无声回滚。本文不讲“为什么分库分表”只聚焦一线工程师每天真正在 debug 的事如何让DS(shard_01)精准落到对应物理库如何让user_id % 4的分片结果在事务链路中全程可信以及当 Spring Boot MyBatis Plus ShardingSphere 三者拧在一起时哪几行配置是救命绳、哪几处注解是定时炸弹。适合已跑通单库读写、正卡在“分片后查不到数据/写入到错误库/事务不生效”的中级后端开发者。2. 从单数据源到多数据源Spring Boot 中动态数据源的底层控制流分库分表的本质是把一个逻辑数据源如ds_master拆成多个物理数据源如ds_0,ds_1,ds_2,ds_3再由中间件根据分片规则决定本次 SQL 路由到哪个物理库。但 Spring Boot 默认只管理一个DataSourceBean所有JdbcTemplate、SqlSessionTemplate都绑定到它。要支持多数据源必须打破这个“单例绑定”让每次数据库操作前能动态指定用哪个DataSource实例。这不是靠改application.yml就能解决的——YAML 只负责声明有哪些数据源真正的切换动作发生在AbstractRoutingDataSource的determineCurrentLookupKey()方法里。这个方法被 Spring 在每次获取连接时调用返回一个 key比如ds_0Spring 拿这个 key 去查targetDataSourcesMap拿到对应的真实DataSource。所以整个动态切换链条是业务代码设置路由 key →ThreadLocal存储该 key →AbstractRoutingDataSource读取 key → 返回对应物理数据源 → JDBC 执行 SQL。漏掉任何一环就等于告诉数据库“我不知道该连谁”。2.1 手写DynamicDataSource理解AbstractRoutingDataSource的最小可运行实现下面是一个精简但生产可用的DynamicDataSource实现它不依赖任何分库分表框架纯粹展示 Spring 多数据源切换的核心机制Component public class DynamicDataSource extends AbstractRoutingDataSource { // 使用 InheritableThreadLocal确保子线程如 Async也能继承路由 key private static final ThreadLocalString CONTEXT_HOLDER new InheritableThreadLocal(); Override protected Object determineCurrentLookupKey() { // 此方法必须返回非 null否则 Spring 抛出 IllegalStateException String key CONTEXT_HOLDER.get(); if (key null) { // 默认数据源必须存在且在 targetDataSources 中注册 return ds_master; } return key; } // 提供给业务代码调用设置当前线程的路由 key public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } // 清理防止 ThreadLocal 内存泄漏尤其在线程池场景 public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } }关键参数说明InheritableThreadLocal比普通ThreadLocal多一层“子线程继承父线程值”的能力。这是Async、CompletableFuture场景下路由 key 不丢失的前提。determineCurrentLookupKey()返回null是致命错误Spring 会直接抛异常所以必须有兜底逻辑如默认ds_master。clearDataSourceKey()必须在每次数据库操作后显式调用否则线程池中线程复用时上一次的 key 会污染下一次请求——这是新手踩坑率最高的点之一。2.2 在 Spring Boot 中注册并注入多数据源光有DynamicDataSource类还不够必须把它注册为 Spring 容器的主DataSource并把所有物理数据源作为targetDataSources注入进去。以下是application.yml和 Java Config 的组合写法# application.yml spring: datasource: # 主数据源即 DynamicDataSource的配置仅用于占位实际不连接 url: jdbc:h2:mem:dummy;DB_CLOSE_DELAY-1 username: sa password: driver-class-name: org.h2.Driver shard: datasources: ds_0: url: jdbc:mysql://127.0.0.1:3306/shard_db_0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver ds_1: url: jdbc:mysql://127.0.0.1:3306/shard_db_1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverConfiguration public class DataSourceConfig { Value(${spring.shard.datasources.ds_0.url}) private String ds0Url; Value(${spring.shard.datasources.ds_0.username}) private String ds0Username; Value(${spring.shard.datasources.ds_0.password}) private String ds0Password; Value(${spring.shard.datasources.ds_1.url}) private String ds1Url; Value(${spring.shard.datasources.ds_1.username}) private String ds1Username; Value(${spring.shard.datasources.ds_1.password}) private String ds1Password; Bean Primary public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource new DynamicDataSource(); // 构建 targetDataSources Map MapObject, Object targetDataSources new HashMap(); targetDataSources.put(ds_0, createDataSource(ds0Url, ds0Username, ds0Password)); targetDataSources.put(ds_1, createDataSource(ds1Url, ds1Username, ds1Password)); // 设置默认数据源必须与 determineCurrentLookupKey 的兜底一致 dynamicDataSource.setDefaultTargetDataSource(targetDataSources.get(ds_0)); dynamicDataSource.setTargetDataSources(targetDataSources); // 必须调用 afterPropertiesSet()否则 targetDataSources 不生效 try { dynamicDataSource.afterPropertiesSet(); } catch (Exception e) { throw new RuntimeException(DynamicDataSource init failed, e); } return dynamicDataSource; } private DataSource createDataSource(String url, String username, String password) { HikariConfig config new HikariConfig(); config.setJdbcUrl(url); config.setUsername(username); config.setPassword(password); config.setDriverClassName(com.mysql.cj.jdbc.Driver); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); return new HikariDataSource(config); } }逻辑说明Primary标记dynamicDataSource()是 Spring 默认注入的DataSource所有Autowired DataSource都会拿到它。afterPropertiesSet()是InitializingBean接口方法AbstractRoutingDataSource依赖它来初始化内部缓存漏掉这行targetDataSources形同虚设。HikariDataSource是推荐的连接池实现其maximumPoolSize和minimumIdle需按物理库数量等比例放大例如 2 个库总连接数建议 40~60而非单库的 20否则高并发下会因连接耗尽而阻塞。2.3 业务层如何安全地触发数据源切换有了DynamicDataSource业务代码必须主动调用setDataSourceKey()来指定本次操作的目标库。最常见、最可控的方式是自定义注解 AOP 切面避免在每个 Service 方法里手写DynamicDataSource.setDataSourceKey(ds_0)。以下是一个轻量级实现// 自定义注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface DS { String value() default ds_master; // 默认指向主库 } // AOP 切面 Aspect Component Slf4j public class DynamicDataSourceAspect { Around(annotation(ds)) public Object around(ProceedingJoinPoint point, DS ds) throws Throwable { String dataSourceKey ds.value(); log.debug(Switching to datasource: {}, dataSourceKey); DynamicDataSource.setDataSourceKey(dataSourceKey); try { return point.proceed(); } finally { // 必须清理否则线程池复用时 key 污染 DynamicDataSource.clearDataSourceKey(); } } }然后在 Service 方法上使用Service public class OrderService { DS(ds_0) public Order getOrderById(Long id) { // 此方法内所有 DAO 操作都会路由到 ds_0 return orderMapper.selectById(id); } DS(ds_1) public void createOrder(Order order) { // 此方法内所有 DAO 操作都会路由到 ds_1 orderMapper.insert(order); } }参数说明与边界DS注解支持类级别整个类方法都走同一库和方法级别单个方法覆盖方法级优先级更高。finally块中的clearDataSourceKey()是铁律。若此处抛异常如数据库连接超时clear仍会执行保证线程干净。此方案不支持嵌套调用的“子方法覆盖父方法”场景如DS(ds_0)方法内调用DS(ds_1)方法因为ThreadLocal是栈式覆盖后设的 key 会覆盖前设的。如需支持需改用Stack结构管理 key但会显著增加复杂度一般通过设计规避如拆分为两个独立接口。3. 分库分表框架选型ShardingSphere-JDBC vs 手写路由什么场景该用哪个当你决定引入分库分表第一个技术决策不是“怎么分”而是“用谁来分”。目前主流有两条路径完全自研路由逻辑如上节手写DynamicDataSource或接入成熟分片中间件如 ShardingSphere-JDBC。二者不是替代关系而是适用阶段不同。我见过太多团队在 QPS 500 时就急着上 ShardingSphere结果被它的 YAML 配置、SQL 兼容性、事务模型绕晕也见过在日订单 50 万时还用手写路由硬扛最后发现user_id % 4分片后SELECT * FROM order WHERE status ?这种无分片键查询必须扫全库性能雪崩。3.1 ShardingSphere-JDBC 的核心价值它不只是“路由”而是“分片语义的标准化表达”ShardingSphere-JDBC 不是一个简单的DataSource包装器。它在JDBC Driver层拦截 SQL解析 AST抽象语法树识别分片键、分片算法、绑定表、广播表等语义再生成路由结果。这意味着你不用在业务代码里写DS(ds_0)只需声明sharding-column: user_id和algorithm-expression: ds_${user_id % 2}ShardingSphere 自动根据WHERE user_id ?中的参数值计算出目标库。它支持INSERT INTO order (...) VALUES (...), (...)这种批量插入并自动将不同user_id的记录路由到不同库前提是user_id在 VALUES 中明确给出。它能识别JOIN语句若order和order_item是绑定表binding-tables: t_order,t_order_item则确保关联查询在同一个库内执行避免跨库 JOIN。但代价是它要求你放弃一部分 SQL 自由度。例如SELECT * FROM order WHERE create_time 2023-01-01 AND status 1—— 因为create_time不是分片键ShardingSphere 无法确定路由会默认广播到所有库性能极差。SELECT COUNT(*) FROM order GROUP BY status—— 同样广播且需要内存归并结果大数据量下 OOM 风险高。所以ShardingSphere 的真正适用场景是业务有清晰、稳定、高频的分片键如 user_id、tenant_id且 80% 以上查询都带该键。如果你的系统大量存在“后台运营报表类”查询或者分片键本身是动态生成的如 UUID那 ShardingSphere 反而会成为枷锁。3.2 ShardingSphere-JDBC 最小化接入5 行 YAML 1 个启动类ShardingSphere-JDBC 以 Jar 包形式嵌入应用无需独立部署。以下是 Spring Boot 2.7 下的最小接入配置基于shardingsphere-jdbc-core-spring-boot-starter5.3.2# application.yml spring: shardingsphere: props: sql-show: true # 开发期开启查看实际路由日志 datasource: names: ds_0,ds_1 ds_0: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shard_db_0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 ds_1: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shard_db_1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 rules: - !SHARDING tables: t_order: actual-data-nodes: ds_${0..1}.t_order_${0..3} # 2库 × 4表 8物理表 table-strategy: standard: sharding-column: order_id sharding-algorithm-name: t_order_table_inline database-strategy: standard: sharding-column: user_id sharding-algorithm-name: t_order_database_inline sharding-algorithms: t_order_database_inline: type: INLINE props: algorithm-expression: ds_${user_id % 2} t_order_table_inline: type: INLINE props: algorithm-expression: t_order_${order_id % 4}关键参数说明actual-data-nodes: ds_${0..1}.t_order_${0..3}定义物理节点模板ShardingSphere 会自动展开为ds_0.t_order_0,ds_0.t_order_1, ...,ds_1.t_order_3共 8 个节点。sharding-column: user_id声明分库键所有WHERE user_id ?查询将据此路由。algorithm-expression: ds_${user_id % 2}Groovy 表达式user_id为 SQL 参数值% 2计算库序号。注意表达式中变量名必须与sharding-column完全一致大小写敏感否则路由失败。sql-show: true开启后控制台会打印Actual SQL如ds_0 ::: SELECT * FROM t_order_1 WHERE user_id 1001这是排查路由问题的第一手证据。3.3 当 ShardingSphere 和手写DS共存谁赢现实项目中常出现“核心交易链路用 ShardingSphere 分片但某些管理后台接口需直连特定库”的混合场景。此时DS(ds_0)和 ShardingSphere 的sharding-column规则会冲突吗答案是不会但必须明确优先级。ShardingSphere 的DataSource本身就是一个ShardingSphereDataSource它内部封装了多个真实DataSource并实现了自己的getConnection()。而我们手写的DynamicDataSource也是一个DataSource。如果两者同时存在Spring 容器只会有一个PrimaryBean。因此共存方案只能是以 ShardingSphere 为Primary手写DynamicDataSource作为辅助 Bean通过Qualifier显式注入Service public class AdminService { // 注入 ShardingSphere 的主数据源处理分片逻辑 Autowired private JdbcTemplate shardingJdbcTemplate; // 注入手写的 DynamicDataSource处理直连需求 Autowired Qualifier(dynamicDataSource) private DataSource directDataSource; public ListOrder getOrdersFromDs0() { // 手动切换到 ds_0 DynamicDataSource.setDataSourceKey(ds_0); try { return new JdbcTemplate(directDataSource).query( SELECT * FROM t_order WHERE status ?, new BeanPropertyRowMapper(Order.class), 1 ); } finally { DynamicDataSource.clearDataSourceKey(); } } }边界提醒ShardingSphereDataSource和DynamicDataSource是两套独立的路由体系互不感知。DS对 ShardingSphere 无效ShardingSphere 的sharding-column对DS也无效。混合使用会增加架构认知成本建议在项目初期就约定清楚哪些模块走分片哪些模块走直连避免后期维护者混淆。4. 多数据源切换的避坑指南5 个血泪经验总结分库分表落地过程中90% 的线上故障不是出在分片算法本身而是出在数据源切换的“毛细血管”里。这些坑往往不报错只表现为数据查不到、写入错库、事务不回滚、CPU 突增排查起来像黑匣子。以下是我在多个模拟项目 X 和某跨平台系统中踩过、验证过的 5 个高频坑每一条都附带现象、根因和可立即执行的修复方案。4.1 现象Async方法内数据库操作始终路由到默认库如ds_master原因Async创建的新线程无法继承主线程的ThreadLocal值普通ThreadLocal不支持线程间传递。DynamicDataSource.determineCurrentLookupKey()在新线程里读到null于是走默认路由。解决将ThreadLocal替换为InheritableThreadLocal已在 2.1 节代码中体现。若使用CompletableFuture还需手动传递CompletableFuture.supplyAsync(() - { DynamicDataSource.setDataSourceKey(ds_1); // 主动设置 try { return orderService.createOrder(order); } finally { DynamicDataSource.clearDataSourceKey(); } }, taskExecutor);4.2 现象事务方法内前半段写ds_0后半段查ds_1但查不到刚写的数据原因Spring 的Transactional默认使用PROPAGATION_REQUIRED它会复用当前事务的Connection。而Connection是绑定到某个DataSource的。一旦你在事务内调用DynamicDataSource.setDataSourceKey(ds_1)后续 SQL 会尝试从ds_1获取新连接但 Spring 事务管理器仍认为当前事务属于ds_0导致连接不一致甚至抛Transaction rolled back because it has been marked as rollback-only。解决绝对禁止在Transactional方法内动态切换数据源。正确做法是将需不同库的操作拆分为独立的Transactional方法每个方法只操作一个库或使用TransactionTemplate手动控制事务边界确保每个TransactionTemplate绑定到固定DataSource。4.3 现象压测时 CPU 持续 100%jstack显示大量线程阻塞在HikariPool.getConnection()原因HikariCP连接池的maximumPoolSize是按单个DataSource配置的。当有 4 个物理库时若每个库maximumPoolSize20则总连接数上限为 80。但若业务代码未及时clearDataSourceKey()线程池中线程复用时ThreadLocal里残留的 key 导致后续请求全部路由到同一个库如ds_0该库连接池瞬间打满其他请求排队等待CPU 被线程调度和锁竞争吃光。解决在 AOP 切面finally块中强制clear已在 2.3 节体现监控各库连接池活跃连接数如通过 Actuator/actuator/metrics/hikaricp.connections.active若发现严重不均衡立即检查clear是否遗漏。4.4 现象ShardingSphere 日志显示Actual SQL路由到ds_0.t_order_1但该表实际不存在报Table shard_db_0.t_order_1 doesnt exist原因actual-data-nodes配置中的表名模板如t_order_${0..3}与数据库中真实创建的表名不一致。常见错误数据库里建的是t_order_001而配置写的是t_order_${1..3}导致t_order_1不存在。解决严格遵循“配置即文档”原则actual-data-nodes必须与 DDL 脚本完全一致使用shardingsphere-jdbc-core-spring-boot-starter的schema-validation功能需开启启动时校验物理节点是否存在。4.5 现象MyBatis Plus 的LambdaQueryWrapper在分库分表下eq(User::getUserId, 1001)无法触发分片路由原因MyBatis Plus 的 Lambda 表达式在编译期会被擦除ShardingSphere 解析 SQL 时无法从WHERE userId ?中反推出userId对应的 Java 字段名因而无法匹配sharding-column: user_id注意Java 字段userId对应数据库列user_id但 ShardingSphere 的sharding-column配置的是列名不是字段名。解决在LambdaQueryWrapper中显式使用数据库列名eq(user_id, 1001)或在实体类User的userId字段上添加TableField(value user_id)注解确保 MP 生成的 SQL 列名与分片配置一致。5. 验证与可观测如何证明你的分库分表真的“切对了”配置写完、代码改好、服务跑起来了不代表分库分表就成功了。真正的落地完成是当你能在任意时刻对任意一次请求精准回答三个问题这次请求路由到了哪个物理库执行了哪些 SQL这些 SQL 是否真的只访问了目标库没有可观测性分库分表就是盲人骑瞎马。我一般会用三层手段交叉验证缺一不可。5.1 第一层日志取证——ShardingSphere 的sql-show与sql-simple这是最直接、最低成本的验证。开启sql-show: true后ShardingSphere 会在日志中打印[INFO] Logic SQL: SELECT * FROM t_order WHERE user_id ? [INFO] SQLStatement: SelectStatementContext [INFO] Actual SQL: ds_0 ::: SELECT * FROM t_order_0 WHERE user_id 1001 [INFO] Actual SQL: ds_0 ::: SELECT * FROM t_order_1 WHERE user_id 1001 [INFO] Actual SQL: ds_1 ::: SELECT * FROM t_order_2 WHERE user_id 1001注意看Actual SQL行ds_0 ::: ...表示该条 SQL 路由到ds_0数据源后面跟的是真实执行的 SQL。如果看到ds_0 ::: ...和ds_1 ::: ...同时出现说明是广播查询需优化如果所有Actual SQL都指向ds_0而user_id值是随机的如 1001, 1002, 1003那一定是algorithm-expression写错了比如写成了ds_0固定值。提示生产环境禁用sql-show但可保留sql-simple: true只打印Actual SQL不打印解析过程日志量减少 80%仍足够定位路由问题。5.2 第二层数据库侧验证——SHOW PROCESSLIST与慢查询日志日志是应用层视角数据库才是真相。登录到ds_0和ds_1的 MySQL执行-- 在 ds_0 上执行 SHOW PROCESSLIST; -- 查看当前活跃连接中是否有来自你应用 IP 的连接且 Command 为 Query -- 再查慢查询日志需提前开启 slow_query_log SELECT * FROM mysql.slow_log WHERE start_time NOW() - INTERVAL 1 MINUTE ORDER BY start_time DESC LIMIT 10;如果ds_0的PROCESSLIST里全是你的应用连接而ds_1里空空如也说明路由完全失效。如果slow_log中出现SELECT * FROM t_order WHERE create_time 2023-01-01这类无分片键的 SQL且Rows_examined高达百万这就是典型的“全库扫描”必须加索引或重构查询。5.3 第三层链路追踪——用 SkyWalking/Pinpoint 标记数据源日志和数据库命令是离散的而分布式追踪能串起完整链路。我习惯在DynamicDataSource.determineCurrentLookupKey()中埋点Override protected Object determineCurrentLookupKey() { String key CONTEXT_HOLDER.get(); // 向 SkyWalking 追踪上下文注入数据源标签 if (Tracing.isRunning()) { Tracing.currentSpan().tag(db.datasource, key null ? default : key); } return key null ? ds_master : key; }这样在 SkyWalking UI 中点击任意一个OrderService.getOrderById调用就能在 Span 标签里看到db.datasource: ds_0且该 Span 下的所有 DB 操作包括 MyBatis 的query、update都共享这个标签。如果发现某个getOrderById的 Span 标签是ds_master但业务逻辑明确应该走ds_0那问题一定出在setDataSourceKey()调用时机或ThreadLocal传递上。5.4 一张表看清你的分片健康度指标健康阈值如何采集异常含义单库 QPS 偏差率 15%SHOW GLOBAL STATUS LIKE Com_select各库对比某库 QPS 远高于其他说明分片算法倾斜如user_id % 2但用户 ID 分布不均跨库事务占比0%SkyWalking 中db.type: mysqlSpan 的db.datasource标签不一致事务内混用多数据源Spring 无法管理必然回滚广播查询次数/分钟≤ 5ShardingSphere 日志中Broadcast SQL出现频次无分片键查询过多需加缓存或重构连接池平均等待时间 5msHikariCP的poolName.connectionTimeout指标某库连接池长期打满可能是路由集中或慢 SQL 堵塞这张表不是摆设而是我每周五下午雷打不动的巡检项。只要其中任一指标越界我就立刻拉群贴出对应日志和 SQL而不是等下周的 P0 故障。最后说一句血泪教训不要相信“配置一次永久有效”。分库分表的路由逻辑会随着你新增一个DS注解、修改一行algorithm-expression、甚至升级一次 ShardingSphere 版本而悄然改变。唯一可靠的验证是你亲手在测试环境发起一次user_id1001的请求然后盯着Actual SQL、PROCESSLIST、SkyWalking Span三处输出确认它们指向同一个物理库。这种“三眼验证”的习惯我坚持了三年救过至少五次线上事故。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

云服务器上部署Nacos单机版:测试环境微服务注册与配置管理实战

云服务器上部署Nacos单机版:测试环境微服务注册与配置管理实战

1. 为什么单机版Nacos依然是测试环境的刚需做过微服务架构的朋友都清楚,服务注册与配置管理是整个体系的神经中枢。Nacos作为这个中枢的国产开源实现,这几年在中小团队里的普及速度非常快。但很多人第一次在云服务器上装Nacos的时候,会陷入一…

2026/10/16 0:14:12 阅读更多 →
并查集与树判定:图论中连通性与无环性的双核解析

并查集与树判定:图论中连通性与无环性的双核解析

1. 这不是两道题,而是一把打开图论思维的钥匙刚接触图论题目的新手常被HDU 1773“小希的迷宫”和HDU 1325/POJ 1308“Is It A Tree?”这两道题绕晕:一个说“迷宫”,一个问“是不是树”,看起来风马牛不相及。但我在某高校算法实训…

2026/10/16 0:14:12 阅读更多 →
ANP协议是什么?当Agent多到管不过来的时候,你需要一个“Agent黄页“

ANP协议是什么?当Agent多到管不过来的时候,你需要一个“Agent黄页“

ANP协议是什么?当Agent多到管不过来的时候,你需要一个"Agent黄页"写在前面 MCP和A2A我们都聊过了——MCP解决"Agent怎么用工具",A2A解决"Agent怎么跟Agent说话"。但随着你做的系统越来越大,会遇到一…

2026/10/16 0:14:12 阅读更多 →

最新新闻

仓颉 ArkUI Counter 计数器组件完全指南:增减计数、按钮控制与事件绑定

仓颉 ArkUI Counter 计数器组件完全指南:增减计数、按钮控制与事件绑定

文档知识库教程OpenHarmony 【免费下载链接】CangjieCorpus 本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 R…

2026/10/16 2:03:46 阅读更多 →
如何让多个git worktree并行开发零端口冲突?Sonar port: auto与${port}变量详解

如何让多个git worktree并行开发零端口冲突?Sonar port: auto与${port}变量详解

【免费下载链接】sonar CLI tool for inspecting and managing services listening on localhost ports 项目地址: https://gitcode.com/gh_mirrors/sonar17/sonar 点击查看 免费下载 Sonar 是一款本地端口管理与服务启动 CLI 工具,它用 port: auto 自动…

2026/10/16 2:03:46 阅读更多 →
仓颉鸿蒙 Location Kit 位置服务错误码全解:从 3301000 到 3301800 的成因分析与处理指南

仓颉鸿蒙 Location Kit 位置服务错误码全解:从 3301000 到 3301800 的成因分析与处理指南

文档知识库教程OpenHarmony 【免费下载链接】CangjieCorpus 本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 R…

2026/10/16 2:03:46 阅读更多 →
10分钟上手 Modly:从安装到首次生成本地 3D 模型

10分钟上手 Modly:从安装到首次生成本地 3D 模型

10分钟上手 Modly:从安装到首次生成本地 3D 模型 【免费下载链接】modly Desktop app to generate 3D models from images or prompt using local AI — runs entirely on your GPU 项目地址: https://gitcode.com/GitHub_Trending/mo/modly Modly 是一款本地…

2026/10/16 2:03:46 阅读更多 →
非线性最小二乘优化实战:高斯-牛顿、LM与Dog-Leg方法详解

非线性最小二乘优化实战:高斯-牛顿、LM与Dog-Leg方法详解

1. 为什么非线性最小二乘值得单独整理如果你做过视觉SLAM、点云配准、曲线拟合、相机标定,或者任何需要"让模型输出逼近真实观测"的工程任务,那你大概率已经和非线性最小二乘打过照面了。它要解决的问题非常朴素:给定一组观测数据和…

2026/10/16 2:03:46 阅读更多 →
四轮把模糊想法逼成明确决策:grill-me 实战指南

四轮把模糊想法逼成明确决策:grill-me 实战指南

四轮把模糊想法逼成明确决策:grill-me 实战指南 【免费下载链接】skills Skills for Real Engineers. Straight from my .agents directory. 项目地址: https://gitcode.com/GitHub_Trending/skills13/skills 你把一个没想清楚的主意丢给编码 agent&#xff…

2026/10/16 2:02:46 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

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

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/15 9:49:14 阅读更多 →