feat: 适配器解析引擎,用 CSS 选择器从提交的 HTML 中自动提取数据进行校验

This commit is contained in:
mediabot-pt
2026-07-01 01:25:16 +08:00
parent e27a5b59ed
commit e56f1d4b92
9 changed files with 439 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
package com.par.api.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.par.core.adapter.AdapterConfig;
import com.par.core.adapter.AdapterEngine;
import com.par.core.dto.ApiResponse;
import com.par.core.entity.AccessResult;
import com.par.core.entity.SiteConfig;
import com.par.core.mapper.AccessResultMapper;
import com.par.core.mapper.SiteConfigMapper;
import com.par.core.storage.CachedStorageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 适配器解析控制器
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class AdapterController {
private final AdapterEngine adapterEngine;
private final SiteConfigMapper siteConfigMapper;
private final AccessResultMapper accessResultMapper;
private final CachedStorageService storageService;
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 用指定适配配置解析指定访问结果
*/
@PostMapping("/api/v1/admin/adapter/parse")
public ApiResponse<Map<String, Object>> parse(
@RequestBody Map<String, Object> payload) {
Long configId = toLong(payload.get("configId"));
Long accessResultId = toLong(payload.get("accessResultId"));
if (configId == null || accessResultId == null) {
return ApiResponse.error(400, "configId and accessResultId 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()); }
// 加载访问结果 HTML
AccessResult accessResult = accessResultMapper.selectById(accessResultId);
if (accessResult == null) return ApiResponse.error(404, "访问结果不存在");
String html;
try { html = storageService.read(accessResult.getStoragePath()); }
catch (Exception e) { return ApiResponse.error(500, "读取访问结果失败: " + e.getMessage()); }
if (html == null) return ApiResponse.error(404, "访问结果内容为空");
// 执行解析
Map<String, Object> result;
try {
result = adapterEngine.extract(adapterConfig, html, accessResult.getUrl());
} catch (Exception e) {
log.error("Adapter parse error", e);
return ApiResponse.error(500, "解析异常: " + e.getMessage());
}
return ApiResponse.success(result);
}
private Long toLong(Object val) {
if (val instanceof Number n) return n.longValue();
if (val instanceof String s) {
try { return Long.parseLong(s); } catch (NumberFormatException ignored) {}
}
return null;
}
}