package com.yunhai.userbase.service.impl; import com.alibaba.cloud.nacos.NacosConfigManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 前端运行时配置读取(方案:Projman/前端动态配置方案-env-config接入落地.md)。 *

* projman 配置管理中的 env-config-{前端目标名}.json 会双写到 Nacos * (dataId = front-{应用名}-{目标名},group = FRONT_GROUP,type = json), * 本服务按前端目标名读取并原样返回 JSON 串,经 {@code GET /api/env-config?front=xxx} 提供给前端 SPA, * 替代构建期写死的 VITE_* 环境变量与 config/index.js 静态文件。 *

* 容错口径:读不到(本地未开 Nacos / dataId 不存在 / 超时)一律返回 {@code "{}"}, * 前端拿空对象后走 import.meta.env 构建期兜底,不影响启动。 */ @Service public class EnvConfigService { private static final Logger log = LoggerFactory.getLogger(EnvConfigService.class); /** 前端目标名白名单:projman 前端目标创建时即限制为简单标识符,此处同口径校验,防 dataId 拼接注入 */ private static final String FRONT_NAME_PATTERN = "^[A-Za-z0-9_-]{1,64}$"; /** Nacos dataId 前缀:front-{应用名}-,与 projman AppConfigFileService 双写规则一致 */ @Value("${yh.env-config.data-id-prefix:front-userbase-}") private String dataIdPrefix; /** Nacos group,与 projman 双写侧 FRONT_GROUP 常量一致 */ @Value("${yh.env-config.group:FRONT_GROUP}") private String group; /** 结果缓存 TTL:前端 SPA 每次打开页面都会拉一次,缓存削峰避免轮询打穿 Nacos */ private static final long CACHE_TTL_MS = 30_000L; private final Map cache = new ConcurrentHashMap<>(); private static class CacheEntry { final String content; final long ts; CacheEntry(String content, long ts) { this.content = content; this.ts = ts; } } /** 本地 profile 未启用 Nacos 时为 null(NACOS_CONFIG_ENABLED=false 时不注册该 bean) */ @Autowired(required = false) private NacosConfigManager nacosConfigManager; /** 读取前端目标配置 JSON;任何失败返回 "{}"(前端走构建期兜底) */ public String get(String front) { if (front == null || !front.trim().matches(FRONT_NAME_PATTERN)) { return "{}"; } String key = front.trim(); CacheEntry cached = cache.get(key); long now = System.currentTimeMillis(); if (cached != null && now - cached.ts < CACHE_TTL_MS) { return cached.content; } String content = readFromNacos(key); cache.put(key, new CacheEntry(content, now)); return content; } private String readFromNacos(String front) { if (nacosConfigManager == null) { // 本地开发未接 Nacos:无运行时配置,前端走 import.meta.env 兜底 return "{}"; } try { String content = nacosConfigManager.getConfigService() .getConfig(dataIdPrefix + front, group, 3000); if (content == null || content.trim().isEmpty()) { return "{}"; } return content.trim(); } catch (Exception e) { // Nacos 不可达时 getConfig 自身有本地快照兜底;仍失败则返回空对象,不抛错不影响前端启动 log.warn("[env-config] 读取 Nacos 配置失败 dataId={} group={}: {}", dataIdPrefix + front, group, e.getMessage()); return "{}"; } } }