refactor: 访问结果内容改用七牛云/本地存储,与 config 文件存储方式一致

This commit is contained in:
mediabot-pt
2026-06-30 22:41:15 +08:00
parent 83e150b8c9
commit 0568923ce9
7 changed files with 50 additions and 22 deletions

View File

@@ -74,15 +74,15 @@ public class AccessResultController {
} }
/** /**
* 获取单个访问结果详情(含完整内容) * 获取单个访问结果详情(含完整内容,从存储读取)
*/ */
@GetMapping("/api/v1/admin/access-results/{id}") @GetMapping("/api/v1/admin/access-results/{id}")
public ApiResponse<AccessResultDTO> getById(@PathVariable("id") Long id) { public ApiResponse<AccessResultDTO> getById(@PathVariable("id") Long id) {
AccessResult result = accessResultService.getById(id); AccessResultDTO dto = accessResultService.getDetailById(id);
if (result == null) { if (dto == null) {
return ApiResponse.error(404, "访问结果不存在"); return ApiResponse.error(404, "访问结果不存在");
} }
return ApiResponse.success(accessResultService.toDTO(result)); return ApiResponse.success(dto);
} }
/** /**

View File

@@ -15,6 +15,7 @@ public class AccessResultDTO {
private String configVersion; private String configVersion;
private String clientId; private String clientId;
private String contentType; private String contentType;
private String storagePath;
private String content; private String content;
private String url; private String url;
private Integer statusCode; private Integer statusCode;

View File

@@ -27,8 +27,8 @@ public class AccessResult {
@TableField("content_type") @TableField("content_type")
private String contentType; private String contentType;
@TableField("content") @TableField("storage_path")
private String content; private String storagePath;
@TableField("url") @TableField("url")
private String url; private String url;

View File

@@ -11,26 +11,26 @@ import java.util.List;
public interface AccessResultService { public interface AccessResultService {
/** /**
* 提交访问结果(mediabot 调用) * 提交访问结果(mediabot 调用),内容存储到七牛云/本地
*/ */
AccessResult submit(String siteId, String configVersion, String clientId, AccessResult submit(String siteId, String configVersion, String clientId,
String contentType, String content, String url, String contentType, String content, String url,
Integer statusCode, Integer responseTimeMs, Long submitterId); Integer statusCode, Integer responseTimeMs, Long submitterId);
/** /**
* 获取所有访问结果 * 获取所有访问结果(不含内容)
*/ */
List<AccessResultDTO> listAll(); List<AccessResultDTO> listAll();
/** /**
* 获取指定站点的访问结果 * 获取指定站点的访问结果(不含内容)
*/ */
List<AccessResultDTO> listBySiteId(String siteId); List<AccessResultDTO> listBySiteId(String siteId);
/** /**
* 获取单个访问结果详情 * 获取单个访问结果(从存储读取内容)
*/ */
AccessResult getById(Long id); AccessResultDTO getDetailById(Long id);
/** /**
* 标记验证结果(有效/无效) * 标记验证结果(有效/无效)
@@ -38,7 +38,7 @@ public interface AccessResultService {
AccessResult validate(Long id, Boolean isValid, String note); AccessResult validate(Long id, Boolean isValid, String note);
/** /**
* 实体转 DTO * 实体转 DTO(不含内容)
*/ */
AccessResultDTO toDTO(AccessResult entity); AccessResultDTO toDTO(AccessResult entity);
} }

View File

@@ -4,11 +4,13 @@ import com.par.core.dto.AccessResultDTO;
import com.par.core.entity.AccessResult; import com.par.core.entity.AccessResult;
import com.par.core.mapper.AccessResultMapper; import com.par.core.mapper.AccessResultMapper;
import com.par.core.service.AccessResultService; import com.par.core.service.AccessResultService;
import com.par.core.storage.CachedStorageService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -17,24 +19,32 @@ import java.util.stream.Collectors;
public class AccessResultServiceImpl implements AccessResultService { public class AccessResultServiceImpl implements AccessResultService {
private final AccessResultMapper mapper; private final AccessResultMapper mapper;
private final CachedStorageService storageService;
@Override @Override
public AccessResult submit(String siteId, String configVersion, String clientId, public AccessResult submit(String siteId, String configVersion, String clientId,
String contentType, String content, String url, String contentType, String content, String url,
Integer statusCode, Integer responseTimeMs, Long submitterId) { Integer statusCode, Integer responseTimeMs, Long submitterId) {
String type = (contentType != null && !contentType.isBlank()) ? contentType : "html";
String ext = "json".equalsIgnoreCase(type) ? "json" : "html";
String storagePath = String.format("access-results/%s/%d_%s.%s",
siteId, System.currentTimeMillis(), UUID.randomUUID().toString().substring(0, 8), ext);
// 存储到抽象存储层(七牛云或本地)
storageService.store(storagePath, content);
AccessResult result = new AccessResult(); AccessResult result = new AccessResult();
result.setSiteId(siteId); result.setSiteId(siteId);
result.setConfigVersion(configVersion); result.setConfigVersion(configVersion);
result.setClientId(clientId); result.setClientId(clientId);
result.setContentType(contentType != null ? contentType : "html"); result.setContentType(type);
result.setContent(content); result.setStoragePath(storagePath);
result.setUrl(url); result.setUrl(url);
result.setStatusCode(statusCode); result.setStatusCode(statusCode);
result.setResponseTimeMs(responseTimeMs); result.setResponseTimeMs(responseTimeMs);
result.setSubmitterId(submitterId != null ? submitterId : 0L); result.setSubmitterId(submitterId != null ? submitterId : 0L);
mapper.insert(result); mapper.insert(result);
log.info("Access result submitted: siteId={}, version={}, clientId={}, id={}", log.info("Access result submitted: siteId={}, version={}, clientId={}, id={}", siteId, configVersion, clientId, result.getId());
siteId, configVersion, clientId, result.getId());
return result; return result;
} }
@@ -49,8 +59,18 @@ public class AccessResultServiceImpl implements AccessResultService {
} }
@Override @Override
public AccessResult getById(Long id) { public AccessResultDTO getDetailById(Long id) {
return mapper.selectById(id); AccessResult result = mapper.selectById(id);
if (result == null) return null;
AccessResultDTO dto = toDTO(result);
// 从存储读取内容
try {
dto.setContent(storageService.read(result.getStoragePath()));
} catch (Exception e) {
log.error("读取访问结果内容失败 id={} path={}: {}", id, result.getStoragePath(), e.getMessage());
dto.setContent("读取内容失败: " + e.getMessage());
}
return dto;
} }
@Override @Override
@@ -73,7 +93,7 @@ public class AccessResultServiceImpl implements AccessResultService {
dto.setConfigVersion(entity.getConfigVersion()); dto.setConfigVersion(entity.getConfigVersion());
dto.setClientId(entity.getClientId()); dto.setClientId(entity.getClientId());
dto.setContentType(entity.getContentType()); dto.setContentType(entity.getContentType());
dto.setContent(entity.getContent()); dto.setStoragePath(entity.getStoragePath());
dto.setUrl(entity.getUrl()); dto.setUrl(entity.getUrl());
dto.setStatusCode(entity.getStatusCode()); dto.setStatusCode(entity.getStatusCode());
dto.setResponseTimeMs(entity.getResponseTimeMs()); dto.setResponseTimeMs(entity.getResponseTimeMs());

View File

@@ -5,7 +5,7 @@ CREATE TABLE access_results (
config_version VARCHAR(20) COMMENT '使用的配置版本号', config_version VARCHAR(20) COMMENT '使用的配置版本号',
client_id VARCHAR(128) COMMENT '提交者标识(mediabot 客户端 ID)', client_id VARCHAR(128) COMMENT '提交者标识(mediabot 客户端 ID)',
content_type VARCHAR(10) NOT NULL DEFAULT 'html' COMMENT '数据类型:html / json', content_type VARCHAR(10) NOT NULL DEFAULT 'html' COMMENT '数据类型:html / json',
content MEDIUMTEXT NOT NULL COMMENT '访问站点返回的内容', storage_path VARCHAR(500) NOT NULL COMMENT '内容存储路径(七牛云/本地)',
url VARCHAR(500) COMMENT '访问的站点 URL', url VARCHAR(500) COMMENT '访问的站点 URL',
status_code INT COMMENT 'HTTP 状态码', status_code INT COMMENT 'HTTP 状态码',
response_time_ms INT COMMENT '响应耗时(毫秒)', response_time_ms INT COMMENT '响应耗时(毫秒)',

View File

@@ -157,9 +157,16 @@ const fetchAll = async () => {
finally { loading.value = false } finally { loading.value = false }
} }
const handleView = (row) => { const handleView = async (row) => {
detail.value = { ...row } // 详情需从服务端获取完整内容
detailVisible.value = true detailVisible.value = true
try {
const r = await api.accessResults.get(row.id)
detail.value = r.data || {}
} catch {
ElMessage.error('获取详情失败')
detail.value = { ...row }
}
} }
const handleValidate = async (row, isValid) => { const handleValidate = async (row, isValid) => {