feat: 脚本批量导出(JSON)和导入接口+清理重复import
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
package com.par.api.controller;
|
package com.par.api.controller;
|
||||||
|
|
||||||
|
import com.par.common.util.HmacUtil;
|
||||||
import com.par.core.dto.AccountDTO;
|
import com.par.core.dto.AccountDTO;
|
||||||
import com.par.core.dto.ApiResponse;
|
import com.par.core.dto.ApiResponse;
|
||||||
import com.par.core.dto.SiteConfigDTO;
|
import com.par.core.dto.SiteConfigDTO;
|
||||||
import com.par.core.entity.Account;
|
import com.par.core.entity.Account;
|
||||||
|
import com.par.core.entity.Psk;
|
||||||
import com.par.core.entity.SiteConfig;
|
import com.par.core.entity.SiteConfig;
|
||||||
import com.par.core.enums.ConfigStatus;
|
import com.par.core.enums.ConfigStatus;
|
||||||
import com.par.core.enums.TrustLevel;
|
import com.par.core.enums.TrustLevel;
|
||||||
@@ -11,21 +13,21 @@ import com.par.core.mapper.AccountMapper;
|
|||||||
import com.par.core.mapper.ConfigReviewMapper;
|
import com.par.core.mapper.ConfigReviewMapper;
|
||||||
import com.par.core.mapper.SiteConfigMapper;
|
import com.par.core.mapper.SiteConfigMapper;
|
||||||
import com.par.core.mapper.SiteMapper;
|
import com.par.core.mapper.SiteMapper;
|
||||||
import com.par.core.entity.Psk;
|
|
||||||
import com.par.core.service.AccountService;
|
import com.par.core.service.AccountService;
|
||||||
import com.par.core.service.ConfigService;
|
import com.par.core.service.ConfigService;
|
||||||
import com.par.core.service.PskService;
|
import com.par.core.service.PskService;
|
||||||
import com.par.core.storage.CachedStorageService;
|
import com.par.core.storage.CachedStorageService;
|
||||||
import com.par.common.util.HmacUtil;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员控制器
|
* 管理员控制器
|
||||||
@@ -400,6 +402,68 @@ public class AdminController {
|
|||||||
return ApiResponse.success();
|
return ApiResponse.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 脚本批量导入导出 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出所有站点脚本 JSON:{siteId: scriptContent}
|
||||||
|
*/
|
||||||
|
@GetMapping("/scripts/export")
|
||||||
|
public ResponseEntity<byte[]> exportScripts(HttpServletRequest request) {
|
||||||
|
checkAdmin(request);
|
||||||
|
Map<String, String> all = new LinkedHashMap<>();
|
||||||
|
for (SiteConfig sc : siteConfigMapper.selectList(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SiteConfig>()
|
||||||
|
.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) {}
|
||||||
|
}
|
||||||
|
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},保存到本地存储
|
||||||
|
*/
|
||||||
|
@PostMapping("/scripts/import")
|
||||||
|
public ApiResponse<Map<String, Object>> importScripts(
|
||||||
|
@RequestBody Map<String, String> payload,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
checkAdmin(request);
|
||||||
|
int ok = 0, fail = 0;
|
||||||
|
for (Map.Entry<String, String> e : payload.entrySet()) {
|
||||||
|
String siteId = e.getKey();
|
||||||
|
String script = e.getValue();
|
||||||
|
if (script == null || script.isBlank()) continue;
|
||||||
|
try {
|
||||||
|
int nextVersion = 1;
|
||||||
|
List<SiteConfig> approved = siteConfigMapper.selectList(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SiteConfig>()
|
||||||
|
.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) {}
|
||||||
|
}
|
||||||
|
configService.submitConfig(siteId, String.valueOf(nextVersion), script, null);
|
||||||
|
ok++;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("导入脚本失败 siteId={}: {}", siteId, ex.getMessage());
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ApiResponse.success(Map.of("ok", ok, "fail", fail));
|
||||||
|
}
|
||||||
|
|
||||||
private void checkAdmin(HttpServletRequest request) {
|
private void checkAdmin(HttpServletRequest request) {
|
||||||
TrustLevel level = (TrustLevel) request.getAttribute("trustLevel");
|
TrustLevel level = (TrustLevel) request.getAttribute("trustLevel");
|
||||||
if (level == null || level != TrustLevel.ADMIN) {
|
if (level == null || level != TrustLevel.ADMIN) {
|
||||||
|
|||||||
Reference in New Issue
Block a user