From 483374744a2e7113074629a1bc383a4079894716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 31 Aug 2026 01:09:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Spring=20=E5=A4=9A=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=99=A8=E7=B1=BB=E6=98=BE=E5=BC=8F=20@Autowired=20=E6=A0=87?= =?UTF-8?q?=E6=B3=A8=20+=20V95=20=E8=BF=81=E7=A7=BB=E5=85=BC=E5=AE=B9=20My?= =?UTF-8?q?SQL=208.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CapacityPlanService / ExternalCallMetricsRecorder / ZiniaoClientImpl: 双构造器(生产 + 测试入口)导致 Spring 6 不自动推断构造器注入, 启动报 'No default constructor found';生产构造器显式加 @Autowired - V95:shop_key 列 V85 建表时已有,原 ADD COLUMN shop_key 会报 duplicate column;MySQL 8.4 不支持 ADD COLUMN IF NOT EXISTS,改 information_schema 动态 SQL;shop_key 缩到 VARCHAR(255) 以容纳 (shop_key, business_date) 唯一键(1000 在 utf8mb4 下超 3072 字节 索引上限);删除多余行改为单表 DELETE --- .../aiimage/config/CapacityPlanService.java | 4 +- .../metrics/ExternalCallMetricsRecorder.java | 3 ++ .../ziniao/client/ZiniaoClientImpl.java | 3 ++ ..._shop_data_crawl_daily_file_shop_level.sql | 54 ++++++++++++++++--- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/CapacityPlanService.java b/backend-java/src/main/java/com/nanri/aiimage/config/CapacityPlanService.java index 425ce6aa..05dc59cd 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/config/CapacityPlanService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/config/CapacityPlanService.java @@ -1,6 +1,7 @@ package com.nanri.aiimage.config; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @@ -20,7 +21,8 @@ public class CapacityPlanService implements CommandLineRunner { private final CapacityPlanProperties properties; private final long machineMaxMemoryBytes; - /** 供测试注入机器内存字节数;生产构造时取 Runtime.maxMemory()。 */ + /** 生产构造:测试用双参构造器存在,Spring 不会自动推断,必须显式 @Autowired。 */ + @Autowired public CapacityPlanService(CapacityPlanProperties properties) { this(properties, maxMemoryBytes()); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/metrics/ExternalCallMetricsRecorder.java b/backend-java/src/main/java/com/nanri/aiimage/metrics/ExternalCallMetricsRecorder.java index 0c75eb79..22ca3e36 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/metrics/ExternalCallMetricsRecorder.java +++ b/backend-java/src/main/java/com/nanri/aiimage/metrics/ExternalCallMetricsRecorder.java @@ -4,6 +4,7 @@ import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; @@ -28,6 +29,8 @@ public class ExternalCallMetricsRecorder { private final ObjectProvider meterRegistryProvider; + /** 生产构造:测试用 MeterRegistry 构造器存在,Spring 不会自动推断,必须显式 @Autowired。 */ + @Autowired public ExternalCallMetricsRecorder(ObjectProvider meterRegistryProvider) { this.meterRegistryProvider = meterRegistryProvider; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/client/ZiniaoClientImpl.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/client/ZiniaoClientImpl.java index 5f587d92..0b220a49 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/client/ZiniaoClientImpl.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/client/ZiniaoClientImpl.java @@ -6,6 +6,7 @@ import com.nanri.aiimage.common.exception.BusinessException; import com.nanri.aiimage.config.ZiniaoProperties; import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto; import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoStaffItemVo; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -26,6 +27,8 @@ public class ZiniaoClientImpl implements ZiniaoClient { private final ObjectMapper objectMapper; private final com.nanri.aiimage.metrics.ExternalCallMetricsRecorder externalCallMetrics; + /** 生产构造:双构造器下 Spring 不会自动推断,必须显式 @Autowired。 */ + @Autowired public ZiniaoClientImpl(ZiniaoProperties ziniaoProperties, ObjectMapper objectMapper) { this(ziniaoProperties, objectMapper, null); } diff --git a/backend-java/src/main/resources/db/V95__shop_data_crawl_daily_file_shop_level.sql b/backend-java/src/main/resources/db/V95__shop_data_crawl_daily_file_shop_level.sql index 78a601c5..04171eda 100644 --- a/backend-java/src/main/resources/db/V95__shop_data_crawl_daily_file_shop_level.sql +++ b/backend-java/src/main/resources/db/V95__shop_data_crawl_daily_file_shop_level.sql @@ -11,10 +11,50 @@ -- 3) 唯一键改为 (shop_key, business_date) -- 1. 新增列(country_codes_json 供后台管理页展示累计文件实际包含的国家) -ALTER TABLE biz_shop_data_crawl_daily_file - ADD COLUMN `shop_key` VARCHAR(1000) NULL DEFAULT NULL COMMENT '店铺名(去 user 后的店铺级归属键)' AFTER `shop_key_hash`, - ADD COLUMN `country_codes_json` VARCHAR(512) NULL DEFAULT NULL COMMENT '累计文件实际包含的国家代码 JSON(后台管理页展示用)' AFTER `result_content_type`, - ADD COLUMN `compensation_done` TINYINT NOT NULL DEFAULT 0 COMMENT '0=启动补偿组件待重建,1=已按成员快照重建' AFTER `version`; +-- 注意:shop_key 列已在 V85 建表时存在(VARCHAR(1000) NOT NULL),且 +-- 生产数据已回填(25 行,最大 22 字符)。此处只补缺失的 2 列,并把 +-- shop_key 缩到 VARCHAR(255) 以容纳 (shop_key, business_date) 唯一键 +-- (VARCHAR(1000) 在 utf8mb4 下 4000 字节超 MySQL 3072 字节索引上限)。 +-- MySQL 8.4 不支持 ADD COLUMN IF NOT EXISTS,用 information_schema 判断后动态执行。 +SET @add_patch_columns_sql := ( + SELECT IF( + (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'biz_shop_data_crawl_daily_file' + AND column_name = 'country_codes_json') = 0, + 'ALTER TABLE biz_shop_data_crawl_daily_file ADD COLUMN `country_codes_json` VARCHAR(512) NULL DEFAULT NULL COMMENT ''累计文件实际包含的国家代码 JSON(后台管理页展示用)'' AFTER `result_content_type`', + 'SELECT 1' + ) +); +PREPARE stmt FROM @add_patch_columns_sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @add_compensation_column_sql := ( + SELECT IF( + (SELECT COUNT(*) FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'biz_shop_data_crawl_daily_file' + AND column_name = 'compensation_done') = 0, + 'ALTER TABLE biz_shop_data_crawl_daily_file ADD COLUMN `compensation_done` TINYINT NOT NULL DEFAULT 0 COMMENT ''0=启动补偿组件待重建,1=已按成员快照重建'' AFTER `version`', + 'SELECT 1' + ) +); +PREPARE stmt FROM @add_compensation_column_sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- shop_key 缩列(数据最大 22 字符,256 以内安全;VARCHAR(1000) 无法承载唯一键) +SET @shrink_shop_key_sql := ( + SELECT IF( + (SELECT character_maximum_length FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'biz_shop_data_crawl_daily_file' + AND column_name = 'shop_key') > 255, + 'ALTER TABLE biz_shop_data_crawl_daily_file MODIFY COLUMN `shop_key` VARCHAR(255) NOT NULL COMMENT ''店铺名(去 user 后的店铺级归属键)''', + 'SELECT 1' + ) +); +PREPARE stmt FROM @shrink_shop_key_sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; -- 2. 回填 shop_key:daily_member 记录了每个结果行属于哪个 daily_file UPDATE biz_shop_data_crawl_daily_file df @@ -50,8 +90,8 @@ JOIN ( SET m.daily_file_id = g.max_id; -- 7. 删除同店同日合并后的多余 daily_file 行(对象存储文件由补偿组件按引用计数清理) -DELETE df FROM biz_shop_data_crawl_daily_file df -WHERE df.id NOT IN ( +DELETE FROM biz_shop_data_crawl_daily_file +WHERE id NOT IN ( SELECT keep_id FROM ( SELECT MAX(id) AS keep_id FROM biz_shop_data_crawl_daily_file @@ -65,6 +105,6 @@ UPDATE biz_shop_data_crawl_daily_file SET shop_key = '未命名' WHERE TRIM(shop -- 9. country_codes_json 由启动补偿组件加载成员 result 快照后回填 -- (biz_task_result_item.payload_json 是 rustfs 指针,SQL 无法解析内容) --- 10. 新唯一键 +-- 10. 新唯一键(shop_key 已缩为 VARCHAR(255),utf8mb4 下 1020 字节,不超 3072 上限) ALTER TABLE biz_shop_data_crawl_daily_file ADD UNIQUE KEY uk_shop_data_crawl_daily_file (shop_key, business_date);