fix: Spring 多构造器类显式 @Autowired 标注 + V95 迁移兼容 MySQL 8.4
Build Backend JAR / build (push) Has been cancelled

- 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
This commit is contained in:
2026-08-31 01:09:19 +08:00
parent 19ddfb9c5a
commit 483374744a
4 changed files with 56 additions and 8 deletions
@@ -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());
}
@@ -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<MeterRegistry> meterRegistryProvider;
/** 生产构造:测试用 MeterRegistry 构造器存在,Spring 不会自动推断,必须显式 @Autowired。 */
@Autowired
public ExternalCallMetricsRecorder(ObjectProvider<MeterRegistry> meterRegistryProvider) {
this.meterRegistryProvider = meterRegistryProvider;
}
@@ -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);
}
@@ -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_keydaily_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);