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 088052f..561fe69 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 @@ -405,61 +405,77 @@ public class AdminController { // ==================== 脚本批量导入导出 ==================== /** - * 导出所有站点脚本 JSON:{siteId: scriptContent} + * 导出所有站点脚本为 ZIP:每个站点一个 .groovy 文件 */ @GetMapping("/scripts/export") public ResponseEntity exportScripts(HttpServletRequest request) { checkAdmin(request); - Map all = new LinkedHashMap<>(); - for (SiteConfig sc : siteConfigMapper.selectList( + List configs = siteConfigMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(SiteConfig::getDeleted, 0) .eq(SiteConfig::getIsLatest, true) - .isNotNull(SiteConfig::getScriptStoragePath))) { - try { - String script = storageService.read(sc.getScriptStoragePath()); - if (script != null && !script.isBlank()) all.put(sc.getSiteId(), script); - } catch (Exception ignored) {} + .isNotNull(SiteConfig::getScriptStoragePath)); + try { + var baos = new java.io.ByteArrayOutputStream(); + try (var zos = new java.util.zip.ZipOutputStream(baos)) { + for (SiteConfig sc : configs) { + 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(); + } + } + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=scripts.zip") + .contentType(MediaType.parseMediaType("application/zip")) + .body(baos.toByteArray()); + } catch (Exception e) { + log.error("导出脚本失败", e); + return ResponseEntity.status(500).build(); } - byte[] json = new com.fasterxml.jackson.databind.ObjectMapper() - .writerWithDefaultPrettyPrinter().writeValueAsBytes(all); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=scripts-export.json") - .contentType(MediaType.APPLICATION_JSON) - .body(json); } /** - * 导入脚本:{siteId: scriptContent},保存到本地存储 + * 导入脚本 ZIP:每个 .groovy 文件名 = siteId,保存到本地 */ @PostMapping("/scripts/import") public ApiResponse> importScripts( - @RequestBody Map payload, + @RequestParam("file") org.springframework.web.multipart.MultipartFile file, HttpServletRequest request) { checkAdmin(request); int ok = 0, fail = 0; - for (Map.Entry e : payload.entrySet()) { - String siteId = e.getKey(); - String script = e.getValue(); - if (script == null || script.isBlank()) continue; - try { - int nextVersion = 1; - List approved = 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 (!approved.isEmpty()) { - try { nextVersion = Integer.parseInt(approved.get(0).getVersion()) + 1; } - catch (NumberFormatException ex) {} + 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; + 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) {} + } + configService.submitConfig(siteId, String.valueOf(nextVersion), script, null); + ok++; + } catch (Exception ex) { + log.error("导入脚本失败 siteId={}: {}", siteId, ex.getMessage()); + fail++; } - configService.submitConfig(siteId, String.valueOf(nextVersion), script, null); - ok++; - } catch (Exception ex) { - log.error("导入脚本失败 siteId={}: {}", siteId, ex.getMessage()); - fail++; + zis.closeEntry(); } + } catch (Exception e) { + log.error("解压导入ZIP失败", e); + return ApiResponse.error(500, "导入失败: " + e.getMessage()); } return ApiResponse.success(Map.of("ok", ok, "fail", fail)); }