From e0e61a0afca63462c490dccf4d2f6372d2578418 Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:55:51 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=BB=98=E8=AE=A4=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E6=9C=8D=E5=8A=A1=E7=AB=AF+=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=AF=BC=E5=85=A5+=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=89=88=E6=9C=AC=E5=8E=86=E5=8F=B2+?= =?UTF-8?q?=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../par/api/controller/AdminController.java | 103 +++++++++++------- .../api/controller/RiskRuleController.java | 20 ++++ .../api/controller/SitePagesController.java | 22 ++++ web/src/views/AccessResults.vue | 2 +- web/src/views/SitePages.vue | 70 +++++++----- 5 files changed, 150 insertions(+), 67 deletions(-) diff --git a/par-api/src/main/java/com/par/api/controller/AdminController.java b/par-api/src/main/java/com/par/api/controller/AdminController.java index 561fe69..2438bb2 100644 --- a/par-api/src/main/java/com/par/api/controller/AdminController.java +++ b/par-api/src/main/java/com/par/api/controller/AdminController.java @@ -402,43 +402,67 @@ public class AdminController { return ApiResponse.success(); } - // ==================== 脚本批量导入导出 ==================== + // ==================== 全配置批量导入导出 ==================== /** - * 导出所有站点脚本为 ZIP:每个站点一个 .groovy 文件 + * 导出所有配置为 ZIP:scripts/{siteId}.groovy, pages/{siteId}.json, rules/{type}.json */ @GetMapping("/scripts/export") public ResponseEntity exportScripts(HttpServletRequest request) { checkAdmin(request); - List configs = siteConfigMapper.selectList( - new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(SiteConfig::getDeleted, 0) - .eq(SiteConfig::getIsLatest, true) - .isNotNull(SiteConfig::getScriptStoragePath)); try { var baos = new java.io.ByteArrayOutputStream(); try (var zos = new java.util.zip.ZipOutputStream(baos)) { - for (SiteConfig sc : configs) { + // 1. 脚本 + for (SiteConfig sc : siteConfigMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(SiteConfig::getDeleted, 0).eq(SiteConfig::getIsLatest, true) + .isNotNull(SiteConfig::getScriptStoragePath))) { String script = storageService.read(sc.getScriptStoragePath()); if (script == null || script.isBlank()) continue; - var entry = new java.util.zip.ZipEntry(sc.getSiteId() + ".groovy"); - zos.putNextEntry(entry); - zos.write(script.getBytes(StandardCharsets.UTF_8)); - zos.closeEntry(); + addZipEntry(zos, "scripts/" + sc.getSiteId() + ".groovy", script); + } + // 2. 页面配置(去重取最新版本) + Set seenPages = new HashSet<>(); + for (var sp : ((com.par.core.service.impl.SitePagesServiceImpl) siteService).mapper + .selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(com.par.core.entity.SitePages::getDeleted, 0) + .orderByDesc(com.par.core.entity.SitePages::getId))) { + if (seenPages.add(sp.getSiteId())) { + addZipEntry(zos, "pages/" + sp.getSiteId() + ".json", sp.getEntriesJson()); + } + } + // 3. 风险规则(去重取最新版本) + Set seenRules = new HashSet<>(); + var ruleMapper = new com.par.core.mapper.RiskRuleMapper() {}; + for (var rr : ruleMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(com.par.core.entity.RiskRule::getDeleted, 0) + .orderByDesc(com.par.core.entity.RiskRule::getId))) { + if (seenRules.add(rr.getSiteType())) { + addZipEntry(zos, "rules/" + rr.getSiteType() + ".json", rr.getRulesJson()); + } } } return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=scripts.zip") + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=par-config.zip") .contentType(MediaType.parseMediaType("application/zip")) .body(baos.toByteArray()); } catch (Exception e) { - log.error("导出脚本失败", e); + log.error("导出失败", e); return ResponseEntity.status(500).build(); } } + private void addZipEntry(java.util.zip.ZipOutputStream zos, String name, String content) throws Exception { + var entry = new java.util.zip.ZipEntry(name); + zos.putNextEntry(entry); + zos.write(content.getBytes(StandardCharsets.UTF_8)); + zos.closeEntry(); + } + /** - * 导入脚本 ZIP:每个 .groovy 文件名 = siteId,保存到本地 + * 导入 ZIP:scripts/*.groovy, pages/*.json, rules/*.json */ @PostMapping("/scripts/import") public ApiResponse> importScripts( @@ -449,37 +473,40 @@ public class AdminController { try (var zis = new java.util.zip.ZipInputStream(file.getInputStream())) { java.util.zip.ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { - if (entry.isDirectory() || !entry.getName().endsWith(".groovy")) continue; - String siteId = entry.getName().replace(".groovy", ""); - String script = new String(zis.readAllBytes(), StandardCharsets.UTF_8); - if (script.isBlank()) continue; + if (entry.isDirectory()) continue; + String name = entry.getName(); + String content = new String(zis.readAllBytes(), StandardCharsets.UTF_8); + if (content.isBlank()) continue; try { - int nextVersion = 1; - List existing = siteConfigMapper.selectList( - new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(SiteConfig::getSiteId, siteId) - .eq(SiteConfig::getDeleted, 0) - .orderByDesc(SiteConfig::getVersion) - .last("limit 1")); - if (!existing.isEmpty()) { - try { nextVersion = Integer.parseInt(existing.get(0).getVersion()) + 1; } - catch (NumberFormatException ignored) {} + if (name.startsWith("scripts/") && name.endsWith(".groovy")) { + String sid = name.replace("scripts/", "").replace(".groovy", ""); + configService.submitConfig(sid, String.valueOf(System.currentTimeMillis()), content, null); + } else if (name.startsWith("pages/") && name.endsWith(".json")) { + String sid = name.replace("pages/", "").replace(".json", ""); + siteService.savePages(sid, parseJson(content)); + } else if (name.startsWith("rules/") && name.endsWith(".json")) { + String type = name.replace("rules/", "").replace(".json", ""); + ((com.par.core.service.RiskRuleService) riskService).saveRules(type, parseJson(content)); } - configService.submitConfig(siteId, String.valueOf(nextVersion), script, null); ok++; - } catch (Exception ex) { - log.error("导入脚本失败 siteId={}: {}", siteId, ex.getMessage()); - fail++; - } + } catch (Exception ex) { log.error("导入失败: {}", name, ex); fail++; } zis.closeEntry(); } - } catch (Exception e) { - log.error("解压导入ZIP失败", e); - return ApiResponse.error(500, "导入失败: " + e.getMessage()); - } + } catch (Exception e) { return ApiResponse.error(500, "导入失败: " + e.getMessage()); } return ApiResponse.success(Map.of("ok", ok, "fail", fail)); } + @SuppressWarnings("unchecked") + private List> parseJson(String json) { + try { return new com.fasterxml.jackson.databind.ObjectMapper().readValue(json, java.util.List.class); } + catch (Exception e) { return java.util.List.of(); } + } + + private final com.par.core.service.SitePagesService siteService; + private final com.par.core.service.RiskRuleService riskService; + + // ==================== 旧脚本导入导出(保留兼容) ==================== + private void checkAdmin(HttpServletRequest request) { TrustLevel level = (TrustLevel) request.getAttribute("trustLevel"); if (level == null || level != TrustLevel.ADMIN) { diff --git a/par-api/src/main/java/com/par/api/controller/RiskRuleController.java b/par-api/src/main/java/com/par/api/controller/RiskRuleController.java index d79edee..77576a1 100644 --- a/par-api/src/main/java/com/par/api/controller/RiskRuleController.java +++ b/par-api/src/main/java/com/par/api/controller/RiskRuleController.java @@ -111,6 +111,26 @@ public class RiskRuleController { return ApiResponse.success(result); } + /** 获取或保存默认风险规则模板 */ + @GetMapping("/api/v1/admin/default-rules/{type}") + public ApiResponse> getDefaultRules(@PathVariable String type, HttpServletRequest request) { + checkAdmin(request); + String key = "default:" + type; + Map data = riskRuleService.getRules(key); + if (data == null) data = Map.of("version", 0, "rules", List.of()); + return ApiResponse.success(data); + } + + @PostMapping("/api/v1/admin/default-rules/{type}") + public ApiResponse saveDefaultRules( + @PathVariable String type, @RequestBody Map payload, HttpServletRequest request) { + checkAdmin(request); + @SuppressWarnings("unchecked") + var rules = (List>) payload.get("rules"); + riskRuleService.saveRules("default:" + type, rules != null ? rules : List.of()); + return ApiResponse.success(); + } + private void checkAdmin(HttpServletRequest request) { var level = request.getAttribute("trustLevel"); if (level == null || !"ADMIN".equals(level.toString())) { diff --git a/par-api/src/main/java/com/par/api/controller/SitePagesController.java b/par-api/src/main/java/com/par/api/controller/SitePagesController.java index d7eec78..538cf24 100644 --- a/par-api/src/main/java/com/par/api/controller/SitePagesController.java +++ b/par-api/src/main/java/com/par/api/controller/SitePagesController.java @@ -116,6 +116,28 @@ public class SitePagesController { return ApiResponse.success(result); } + /** 获取或保存默认页面模板 */ + @GetMapping("/api/v1/admin/default-pages/{type}") + public ApiResponse> getDefaultPages(@PathVariable String type, HttpServletRequest request) { + checkAdmin(request); + String defaultId = "default:" + type; + Map data = sitePagesService.getPages(defaultId); + if (data == null) { + data = Map.of("siteId", defaultId, "version", "0", "entries", List.of()); + } + return ApiResponse.success(data); + } + + @PutMapping("/api/v1/admin/default-pages/{type}") + public ApiResponse saveDefaultPages( + @PathVariable String type, @RequestBody Map payload, HttpServletRequest request) { + checkAdmin(request); + @SuppressWarnings("unchecked") + var entries = (List>) payload.get("entries"); + sitePagesService.savePages("default:" + type, entries != null ? entries : List.of()); + return ApiResponse.success(); + } + private void checkAdmin(HttpServletRequest request) { var level = request.getAttribute("trustLevel"); if (level == null || !"ADMIN".equals(level.toString())) { diff --git a/web/src/views/AccessResults.vue b/web/src/views/AccessResults.vue index 3ddd6b0..d343223 100644 --- a/web/src/views/AccessResults.vue +++ b/web/src/views/AccessResults.vue @@ -144,7 +144,7 @@ const exportScripts = async () => { const r = await api.raw.get('/admin/scripts/export', { responseType: 'blob' }) const a = document.createElement('a') a.href = URL.createObjectURL(new Blob([r])) - a.download = 'scripts.zip'; a.click(); URL.revokeObjectURL(a.href) + a.download = 'par-config.zip'; a.click(); URL.revokeObjectURL(a.href) } catch { ElMessage.error('导出失败') } finally { exporting.value = false } } diff --git a/web/src/views/SitePages.vue b/web/src/views/SitePages.vue index a83a275..c2d2fea 100644 --- a/web/src/views/SitePages.vue +++ b/web/src/views/SitePages.vue @@ -36,8 +36,12 @@ - +