From d093c5eb287112e2fef829d6b8aa62ae28506562 Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Mon, 29 Jun 2026 23:13:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=AD=98=E5=82=A8=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E5=8A=A8=E6=80=81=E6=A3=80=E6=9F=A5=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E6=94=B9PAR=5FSTORAGE=5FTYPE?= =?UTF-8?q?=E6=97=A0=E9=9C=80=E9=87=8D=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CachedStorageService 每次调用读 PAR_STORAGE_TYPE 环境变量 - QiniuStorageService 首次调用懒初始化,读 QINIU_* 环境变量 - 去掉 @ConditionalOnProperty/@ConditionalOnMissingBean --- .../par/api/controller/ConfigController.java | 6 +- .../core/service/impl/ConfigServiceImpl.java | 11 +-- .../core/storage/CachedStorageService.java | 61 +++++++------- .../par/core/storage/LocalStorageService.java | 2 - .../par/core/storage/QiniuStorageService.java | 79 +++++++++---------- 5 files changed, 72 insertions(+), 87 deletions(-) diff --git a/par-api/src/main/java/com/par/api/controller/ConfigController.java b/par-api/src/main/java/com/par/api/controller/ConfigController.java index b7a34aa..4947d71 100644 --- a/par-api/src/main/java/com/par/api/controller/ConfigController.java +++ b/par-api/src/main/java/com/par/api/controller/ConfigController.java @@ -4,7 +4,7 @@ import com.par.core.dto.ApiResponse; import com.par.core.dto.SiteConfigDTO; import com.par.core.entity.SiteConfig; import com.par.core.service.ConfigService; -import com.par.core.storage.StorageService; +import com.par.core.storage.CachedStorageService; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; @@ -24,9 +24,9 @@ import java.util.Map; public class ConfigController { private final ConfigService configService; - private final StorageService storageService; + private final CachedStorageService storageService; - public ConfigController(ConfigService configService, StorageService storageService) { + public ConfigController(ConfigService configService, CachedStorageService storageService) { this.configService = configService; this.storageService = storageService; } diff --git a/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java b/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java index ccca1f3..80c25f7 100644 --- a/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java +++ b/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java @@ -6,9 +6,8 @@ import com.par.core.entity.SiteConfig; import com.par.core.enums.ConfigStatus; import com.par.core.mapper.SiteConfigMapper; import com.par.core.service.ConfigService; -import com.par.core.storage.StorageService; +import com.par.core.storage.CachedStorageService; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; @@ -20,14 +19,12 @@ import java.util.stream.Collectors; public class ConfigServiceImpl implements ConfigService { private final SiteConfigMapper siteConfigMapper; - private final StorageService storageService; + private final CachedStorageService storageService; public ConfigServiceImpl(SiteConfigMapper siteConfigMapper, - @Autowired(required = false) StorageService storageService) { + CachedStorageService storageService) { this.siteConfigMapper = siteConfigMapper; - // 未注入 StorageService 时(极端情况),回退到本地文件存储 - this.storageService = storageService != null ? storageService - : new com.par.core.storage.LocalStorageService("./data/static"); + this.storageService = storageService; } @Override diff --git a/par-core/src/main/java/com/par/core/storage/CachedStorageService.java b/par-core/src/main/java/com/par/core/storage/CachedStorageService.java index 110f474..69a5429 100644 --- a/par-core/src/main/java/com/par/core/storage/CachedStorageService.java +++ b/par-core/src/main/java/com/par/core/storage/CachedStorageService.java @@ -1,61 +1,58 @@ package com.par.core.storage; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Service; /** - * 缓存存储:七牛(主) + 本地(读缓存/备份)。 - *
- * 写:先写七牛,成功后再缓存到本地 - * 读:本地缓存命中 → 返回;未命中 → 七牛拉取 → 缓存本地 → 返回 + * 动态存储:运行时判断 PAR_STORAGE_TYPE 环境变量。 + * qiniu → 七牛主存储 + 本地缓存,否则 → 纯本地存储。 + * 修改环境变量后无需重启,下次请求即生效。 */ @Slf4j @Service -@ConditionalOnBean(QiniuStorageService.class) public class CachedStorageService implements StorageService { - private final QiniuStorageService primary; - private final StorageService cache; + private final QiniuStorageService qiniu; + private final LocalStorageService local; - public CachedStorageService(QiniuStorageService primary, LocalStorageService cache) { - this.primary = primary; - this.cache = cache; - log.info("缓存存储就绪: 七牛(主) + 本地(缓存)"); + public CachedStorageService(QiniuStorageService qiniu, LocalStorageService local) { + this.qiniu = qiniu; + this.local = local; + log.info("动态存储就绪: {} 模式", isQiniuMode() ? "qiniu+本地缓存" : "本地"); + } + + private boolean isQiniuMode() { + return "qiniu".equalsIgnoreCase(System.getenv("PAR_STORAGE_TYPE")); } @Override public void store(String path, String content) { - // 先写七牛 - primary.store(path, content); - // 再写本地缓存(失败不影响) - try { - cache.store(path, content); - } catch (Exception e) { - log.warn("写本地缓存失败 {}: {}", path, e.getMessage()); + if (isQiniuMode()) { + qiniu.store(path, content); + try { local.store(path, content); } catch (Exception ignored) {} + } else { + local.store(path, content); } } @Override public String read(String path) { - // 本地缓存命中 - String cached = cache.read(path); - if (cached != null) return cached; - - // 从七牛拉取 - String remote = primary.read(path); - if (remote != null) { - // 回写本地缓存 - try { - cache.store(path, remote); - } catch (Exception ignored) {} + // qiniu 模式:本地缓存优先 + if (isQiniuMode()) { + String cached = local.read(path); + if (cached != null) return cached; + String remote = qiniu.read(path); + if (remote != null) { + try { local.store(path, remote); } catch (Exception ignored) {} + } return remote; } - return null; + return local.read(path); } @Override public boolean exists(String path) { - return cache.exists(path) || primary.exists(path); + if (isQiniuMode()) return local.exists(path) || qiniu.exists(path); + return local.exists(path); } } diff --git a/par-core/src/main/java/com/par/core/storage/LocalStorageService.java b/par-core/src/main/java/com/par/core/storage/LocalStorageService.java index 4f8a74e..c113e99 100644 --- a/par-core/src/main/java/com/par/core/storage/LocalStorageService.java +++ b/par-core/src/main/java/com/par/core/storage/LocalStorageService.java @@ -3,7 +3,6 @@ package com.par.core.storage; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.stereotype.Service; import java.io.IOException; @@ -18,7 +17,6 @@ import java.nio.file.Paths; */ @Slf4j @Service -@ConditionalOnMissingBean(QiniuStorageService.class) public class LocalStorageService implements StorageService { private final Path baseDir; diff --git a/par-core/src/main/java/com/par/core/storage/QiniuStorageService.java b/par-core/src/main/java/com/par/core/storage/QiniuStorageService.java index 67407e6..fd43ea9 100644 --- a/par-core/src/main/java/com/par/core/storage/QiniuStorageService.java +++ b/par-core/src/main/java/com/par/core/storage/QiniuStorageService.java @@ -7,8 +7,6 @@ import com.qiniu.storage.Region; import com.qiniu.storage.UploadManager; import com.qiniu.util.Auth; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.net.URI; @@ -18,46 +16,48 @@ import java.net.http.HttpResponse; import java.time.Duration; /** - * 七牛云存储(主存储)。 - * 读文件通过公开 URL 直接 HTTP GET,不依赖 SDK 内部 API。 + * 七牛云存储。 + * 每次调用时从环境变量读取配置,支持热更新无需重启。 */ @Slf4j @Service -@ConditionalOnProperty(name = "par.storage.type", havingValue = "qiniu") public class QiniuStorageService implements StorageService { - private final UploadManager uploadManager; - private final BucketManager bucketManager; - private final Auth auth; - private final String bucket; - private final String cdnDomain; - private final HttpClient httpClient; + private final HttpClient httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); - public QiniuStorageService( - @Value("${par.storage.qiniu.access-key:}") String ak, - @Value("${par.storage.qiniu.secret-key:}") String sk, - @Value("${par.storage.qiniu.bucket:par-configs}") String bucket, - @Value("${par.storage.qiniu.region:z2}") String region, - @Value("${par.storage.qiniu.domain:}") String domain) { + private UploadManager uploadManager; + private BucketManager bucketManager; + private Auth auth; + private String bucket; + private String cdnDomain; - this.bucket = bucket; + private synchronized void ensureInit() { + if (bucket != null) return; + String ak = env("QINIU_AK"); + String sk = env("QINIU_SK"); + String region = env("QINIU_REGION", "z2"); + this.bucket = env("QINIU_BUCKET", "par-configs"); + this.cdnDomain = env("QINIU_DOMAIN", "http://" + bucket + ".qiniucdn.com"); this.auth = Auth.create(ak, sk); - this.cdnDomain = domain != null && !domain.isBlank() - ? domain : "http://" + bucket + ".qiniucdn.com"; - - Configuration cfg = new Configuration(getRegion(region)); + Configuration cfg = new Configuration(switch (region) { + case "z0" -> Region.region0(); + case "z1" -> Region.region1(); + case "z2" -> Region.region2(); + case "na0" -> Region.regionNa0(); + case "as0" -> Region.regionAs0(); + default -> Region.autoRegion(); + }); this.uploadManager = new UploadManager(cfg); this.bucketManager = new BucketManager(auth, cfg); - this.httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(5)) - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); - log.info("Qiniu 存储就绪: bucket={}, domain={}", bucket, cdnDomain); } @Override public void store(String path, String content) { + ensureInit(); try { uploadManager.put(content.getBytes(), path, auth.uploadToken(bucket)); log.debug("Qiniu 上传: {}", path); @@ -69,16 +69,13 @@ public class QiniuStorageService implements StorageService { @Override public String read(String path) { + ensureInit(); try { String url = cdnDomain + "/" + path; - var req = HttpRequest.newBuilder() - .uri(URI.create(url)) - .timeout(Duration.ofSeconds(10)) - .GET().build(); + var req = HttpRequest.newBuilder().uri(URI.create(url)) + .timeout(Duration.ofSeconds(10)).GET().build(); var resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); - if (resp.statusCode() == 200) { - return resp.body(); - } + if (resp.statusCode() == 200) return resp.body(); log.warn("Qiniu 读取失败 {}: HTTP {}", path, resp.statusCode()); } catch (Exception e) { log.warn("Qiniu 读取失败 {}: {}", path, e.getMessage()); @@ -88,6 +85,7 @@ public class QiniuStorageService implements StorageService { @Override public boolean exists(String path) { + ensureInit(); try { return bucketManager.stat(bucket, path) != null; } catch (QiniuException e) { @@ -95,14 +93,9 @@ public class QiniuStorageService implements StorageService { } } - private Region getRegion(String region) { - return switch (region) { - case "z0" -> Region.region0(); - case "z1" -> Region.region1(); - case "z2" -> Region.region2(); - case "na0" -> Region.regionNa0(); - case "as0" -> Region.regionAs0(); - default -> Region.autoRegion(); - }; + private static String env(String key) { return System.getenv(key); } + private static String env(String key, String def) { + String v = System.getenv(key); + return v != null && !v.isEmpty() ? v : def; } }