diff --git a/par-api/src/main/java/com/par/api/controller/AdapterController.java b/par-api/src/main/java/com/par/api/controller/AdapterController.java index 34b3d09..621df08 100644 --- a/par-api/src/main/java/com/par/api/controller/AdapterController.java +++ b/par-api/src/main/java/com/par/api/controller/AdapterController.java @@ -13,7 +13,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * 适配器解析控制器 @@ -77,6 +78,80 @@ public class AdapterController { return ApiResponse.success(result); } + /** + * 批量解析:用指定配置解析站点所有URL的最新访问结果 + */ + @PostMapping("/api/v1/admin/adapter/parse-batch") + public ApiResponse> parseBatch(@RequestBody Map payload) { + Long configId = toLong(payload.get("configId")); + String siteId = (String) payload.get("siteId"); + + if (configId == null || siteId == null) { + return ApiResponse.error(400, "configId and siteId are required"); + } + + // 加载配置 + SiteConfig siteConfig = siteConfigMapper.selectById(configId); + if (siteConfig == null) return ApiResponse.error(404, "配置不存在"); + + String configJson; + try { configJson = storageService.read(siteConfig.getStoragePath()); } + catch (Exception e) { return ApiResponse.error(500, "读取配置失败: " + e.getMessage()); } + if (configJson == null) return ApiResponse.error(404, "配置内容为空"); + + AdapterConfig adapterConfig; + try { adapterConfig = objectMapper.readValue(configJson, AdapterConfig.class); } + catch (Exception e) { return ApiResponse.error(400, "配置格式无效: " + e.getMessage()); } + + // 获取该站点所有结果,按URL分组取最新 + List allResults = accessResultMapper.selectBySiteId(siteId); + Map latestByUrl = allResults.stream() + .filter(r -> r.getUrl() != null && !r.getUrl().isBlank()) + .collect(Collectors.toMap( + AccessResult::getUrl, + r -> r, + (a, b) -> a.getCreatedAt().isAfter(b.getCreatedAt()) ? a : b)); + + // 批量解析 + Map response = new LinkedHashMap<>(); + response.put("configId", configId); + response.put("configVersion", siteConfig.getVersion()); + response.put("totalUrls", latestByUrl.size()); + response.put("totalResults", allResults.size()); + + List> pages = new ArrayList<>(); + for (Map.Entry entry : latestByUrl.entrySet()) { + String url = entry.getKey(); + AccessResult ar = entry.getValue(); + Map item = new LinkedHashMap<>(); + item.put("url", url); + item.put("accessResultId", ar.getId()); + item.put("statusCode", ar.getStatusCode()); + item.put("responseTimeMs", ar.getResponseTimeMs()); + item.put("createdAt", ar.getCreatedAt()); + item.put("totalCount", allResults.stream().filter(r -> url.equals(r.getUrl())).count()); + item.put("validCount", allResults.stream().filter(r -> url.equals(r.getUrl()) && Boolean.TRUE.equals(r.getIsValid())).count()); + item.put("invalidCount", allResults.stream().filter(r -> url.equals(r.getUrl()) && Boolean.FALSE.equals(r.getIsValid())).count()); + item.put("uncheckedCount", allResults.stream().filter(r -> url.equals(r.getUrl()) && r.getIsValid() == null).count()); + + // 解析 HTML + try { + String html = storageService.read(ar.getStoragePath()); + if (html != null) { + Map parsed = adapterEngine.extract(adapterConfig, html, url); + item.put("parsed", parsed); + } + } catch (Exception e) { + item.put("parseError", e.getMessage()); + } + + pages.add(item); + } + + response.put("pages", pages); + return ApiResponse.success(response); + } + private Long toLong(Object val) { if (val instanceof Number n) return n.longValue(); if (val instanceof String s) { diff --git a/web/src/api/index.js b/web/src/api/index.js index d9f1916..84ecfcd 100644 --- a/web/src/api/index.js +++ b/web/src/api/index.js @@ -88,6 +88,7 @@ export default { }, adapter: { parse: (configId, accessResultId) => api.post('/admin/adapter/parse', { configId, accessResultId }), + parseBatch: (configId, siteId) => api.post('/admin/adapter/parse-batch', { configId, siteId }), }, health: () => api.get('/health', { baseURL: '' }) } diff --git a/web/src/views/AccessResultCheck.vue b/web/src/views/AccessResultCheck.vue index f9ef82c..a2c7258 100644 --- a/web/src/views/AccessResultCheck.vue +++ b/web/src/views/AccessResultCheck.vue @@ -1,473 +1,314 @@