feat: 合并配置包 - publish 时生成 bundle,mediabot 下载保证版本一致
This commit is contained in:
@@ -52,6 +52,7 @@ public class AdminController {
|
||||
private final com.par.core.mapper.RiskRuleMapper riskRuleMapper;
|
||||
private final com.par.core.service.SitePagesService sitePagesService;
|
||||
private final com.par.core.service.RiskRuleService riskRuleService;
|
||||
private final ConfigController configController;
|
||||
|
||||
/**
|
||||
* 获取所有用户列表
|
||||
@@ -337,18 +338,39 @@ public class AdminController {
|
||||
|
||||
// ===================== 站点审核 =====================
|
||||
|
||||
/** 发布站点配置到七牛云(生成 manifest/manifest.json) */
|
||||
/** 发布站点配置到七牛云(生成 manifest/manifest.json + 各站点 bundles/) */
|
||||
@PostMapping("/sites/publish")
|
||||
public ApiResponse<Map<String,Object>> publishManifest(HttpServletRequest request) {
|
||||
checkAdmin(request);
|
||||
try {
|
||||
// 1. 生成并发布 manifest
|
||||
var manifest = buildManifest();
|
||||
String json = new com.fasterxml.jackson.databind.ObjectMapper()
|
||||
.writerWithDefaultPrettyPrinter().writeValueAsString(manifest);
|
||||
storageService.store("manifest/manifest.json", json);
|
||||
log.info("Manifest published: {} sites", ((List<?>)manifest.get("sites")).size());
|
||||
|
||||
// 2. 为每个站点生成合并后的 bundle 文件,保证脚本+页面版本一致
|
||||
@SuppressWarnings("unchecked")
|
||||
var sites = (List<Map<String, Object>>) manifest.get("sites");
|
||||
int bundleCount = 0;
|
||||
for (var site : sites) {
|
||||
String siteId = (String) site.get("siteId");
|
||||
try {
|
||||
var bundle = configController.buildBundle(siteId);
|
||||
if (bundle != null) {
|
||||
String bundleJson = new com.fasterxml.jackson.databind.ObjectMapper()
|
||||
.writeValueAsString(bundle);
|
||||
storageService.store("bundles/" + siteId + ".json", bundleJson);
|
||||
bundleCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("生成 bundle 失败 siteId={}: {}", siteId, e.getMessage());
|
||||
}
|
||||
}
|
||||
log.info("Bundles generated: {} sites", bundleCount);
|
||||
return ApiResponse.success(Map.of("version", manifest.get("version"),
|
||||
"sites", ((List<?>)manifest.get("sites")).size()));
|
||||
"sites", sites.size(), "bundles", bundleCount));
|
||||
} catch (Exception e) { return ApiResponse.error(500, "发布失败: " + e.getMessage()); }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
package com.par.api.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.par.core.dto.ApiResponse;
|
||||
import com.par.core.dto.SiteConfigDTO;
|
||||
import com.par.core.entity.SiteConfig;
|
||||
import com.par.core.adapter.GroovyScriptEngine;
|
||||
import com.par.core.mapper.SiteMapper;
|
||||
import com.par.core.service.ConfigService;
|
||||
import com.par.core.service.SitePagesService;
|
||||
import com.par.core.storage.CachedStorageService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -28,11 +32,18 @@ public class ConfigController {
|
||||
private final ConfigService configService;
|
||||
private final CachedStorageService storageService;
|
||||
private final SiteMapper siteMapper;
|
||||
private final SitePagesService sitePagesService;
|
||||
private final com.par.core.mapper.RiskRuleMapper riskRuleMapper;
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public ConfigController(ConfigService configService, CachedStorageService storageService, SiteMapper siteMapper) {
|
||||
public ConfigController(ConfigService configService, CachedStorageService storageService,
|
||||
SiteMapper siteMapper, SitePagesService sitePagesService,
|
||||
com.par.core.mapper.RiskRuleMapper riskRuleMapper) {
|
||||
this.configService = configService;
|
||||
this.storageService = storageService;
|
||||
this.siteMapper = siteMapper;
|
||||
this.sitePagesService = sitePagesService;
|
||||
this.riskRuleMapper = riskRuleMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,4 +149,101 @@ public class ConfigController {
|
||||
SiteConfig config = configService.submitConfig(siteId, version, groovyScript, submitterId);
|
||||
return ApiResponse.success(configService.toDTO(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载合并配置包(供 mediabot 使用)
|
||||
* 返回 JSON 包含:脚本、页面配置、风险规则,保证版本一致性
|
||||
* 优先从云存储读取已发布的 bundle,无则实时合并
|
||||
*/
|
||||
@GetMapping("/{siteId}/bundle")
|
||||
public ResponseEntity<?> downloadBundle(@PathVariable("siteId") String siteId) {
|
||||
// 1. 尝试从云存储读取已发布的 bundle
|
||||
String bundlePath = "bundles/" + siteId + ".json";
|
||||
try {
|
||||
String cached = storageService.read(bundlePath);
|
||||
if (cached != null && !cached.isBlank()) {
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(cached);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 2. 实时合并(兜底)
|
||||
Map<String, Object> bundle = buildBundle(siteId);
|
||||
if (bundle == null) return ResponseEntity.notFound().build();
|
||||
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(bundle);
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(json);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize bundle for siteId={}", siteId, e);
|
||||
return ResponseEntity.status(500).body("{\"error\":\"serialization failed\"}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时构建站点配置包(脚本 + 页面配置 + 风险规则)
|
||||
*/
|
||||
Map<String, Object> buildBundle(String siteId) {
|
||||
Map<String, Object> bundle = new LinkedHashMap<>();
|
||||
bundle.put("siteId", siteId);
|
||||
bundle.put("generatedAt", java.time.LocalDateTime.now().toString());
|
||||
|
||||
// 脚本
|
||||
var site = siteMapper.selectBySiteId(siteId);
|
||||
String siteType = site != null && site.getSiteType() != null ? site.getSiteType().getValue() : null;
|
||||
|
||||
SiteConfig latest = configService.getLatestConfig(siteId);
|
||||
if (latest != null && latest.getScriptStoragePath() != null && !latest.getScriptStoragePath().isBlank()) {
|
||||
try {
|
||||
String script = storageService.read(latest.getScriptStoragePath());
|
||||
if (script != null && !script.isBlank()) {
|
||||
bundle.put("script", script);
|
||||
bundle.put("scriptVersion", latest.getVersion());
|
||||
bundle.put("scriptSource", "custom");
|
||||
}
|
||||
} catch (Exception e) { log.warn("读取脚本失败 siteId={}: {}", siteId, e.getMessage()); }
|
||||
}
|
||||
if (!bundle.containsKey("script") && siteType != null) {
|
||||
String defScript = GroovyScriptEngine.loadDefaultByType(siteType);
|
||||
if (defScript != null) {
|
||||
bundle.put("script", defScript);
|
||||
bundle.put("scriptSource", "default");
|
||||
}
|
||||
}
|
||||
|
||||
// 页面配置(优先自定义,回退默认模板)
|
||||
Map<String, Object> pages = sitePagesService.getPages(siteId);
|
||||
if (pages == null && siteType != null) {
|
||||
pages = sitePagesService.getPages("default:" + siteType);
|
||||
}
|
||||
if (pages != null) {
|
||||
bundle.put("pages", pages.get("entries"));
|
||||
bundle.put("pagesVersion", pages.get("version"));
|
||||
bundle.put("pagesSource", pages.containsKey("source") ? pages.get("source") : "custom");
|
||||
}
|
||||
|
||||
// 风险规则(通过 siteType 查找)
|
||||
if (siteType != null) {
|
||||
var rules = riskRuleMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.par.core.entity.RiskRule>()
|
||||
.eq(com.par.core.entity.RiskRule::getSiteType, siteType)
|
||||
.eq(com.par.core.entity.RiskRule::getDeleted, 0)
|
||||
.orderByDesc(com.par.core.entity.RiskRule::getId));
|
||||
if (!rules.isEmpty()) {
|
||||
var latestRule = rules.get(0);
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> rulesList = objectMapper.readValue(
|
||||
latestRule.getRulesJson(), List.class);
|
||||
bundle.put("riskRules", rulesList);
|
||||
bundle.put("riskRulesVersion", latestRule.getVersion());
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
return bundle.containsKey("script") ? bundle : null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user