Commit 949dee05 authored by hejincai's avatar hejincai

feat: 完善时间校验的规则

parent 5abdcc8c
......@@ -3,9 +3,12 @@ package com.afanticar.afantiopenapi.config;
import com.afanticar.afantiopenapi.constant.ExceptionEnum;
import com.afanticar.afantiopenapi.controller.BaseController;
import com.afanticar.afantiopenapi.model.BaseResponse;
import com.afanticar.common.core.api.ResultCodeEnum;
import com.afanticar.common.core.exception.ApiException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
......@@ -32,6 +35,18 @@ public class GlobalExceptionHandler {
return handleError(ExceptionEnum.ERROR, e);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public BaseResponse processHttpMessageNotReadableException(HttpMessageNotReadableException e) {
LOGGER.error("HttpMessageNotReadableException 调用异常 => :{}", ExceptionUtils.getStackTrace(e));
return BaseController.error(ResultCodeEnum.VALIDATE_FAILED.getCode().toString(), e.getMessage());
}
@ExceptionHandler(ApiException.class)
public BaseResponse processApiException(ApiException e) {
LOGGER.error("api 调用异常 => :{}", ExceptionUtils.getStackTrace(e));
return BaseController.error(e.getCode().toString(), e.getMessage());
}
private BaseResponse<Object> handleError(int code, String message, Exception ex) {
return BaseController.error(code + "", message);
}
......
......@@ -6,8 +6,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.time.LocalDate;
import java.util.List;
/**
......@@ -20,12 +20,10 @@ import java.util.List;
public class DataReportRequestDTO {
@ApiModelProperty("开始时间。格式为:yyyy-MM-dd。例如2024-01-01")
@NotBlank(message = "开始时间不可为空")
private String startTime;
private LocalDate startTime;
@ApiModelProperty("结束时间。格式为:yyyy-MM-dd。例如2024-01-01")
@NotBlank(message = "结束时间不可为空")
private String endTime;
private LocalDate endTime;
@ApiModelProperty("平台类型 public-公共数据 douyin-抖音 kuaishou-快手xiaohongshu-小红书 shipinhao-视频号")
@NotEmpty(message = "平台类型不可为空,可选类型 public-公共数据 douyin-抖音 kuaishou-快手xiaohongshu-小红书 shipinhao-视频号 ")
......
......@@ -11,12 +11,15 @@ import com.afanticar.afantiopenapi.model.dto.PageInfoDTO;
import com.afanticar.afantiopenapi.model.dto.ReportDataInfoDTO;
import com.afanticar.afantiopenapi.model.entity.DwsAfantiAdbDataOssRecordEntity;
import com.afanticar.afantiopenapi.model.entity.OpenApiClientPrincipalRelationEntity;
import com.afanticar.common.core.api.ResultCodeEnum;
import com.afanticar.common.core.exception.ApiException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
......@@ -26,8 +29,10 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -36,104 +41,134 @@ import java.util.regex.Pattern;
* @since 2025/4/24 9:41
**/
@Service
@Slf4j
public class DataReportService {
@Resource
private OpenApiClientPrincipalRelationMapper openApiClientPrincipalRelationMapper;
@Resource
private DwsAfantiAdbDataOssRecordMapper dwsAfantiAdbDataOssRecordMapper;
@Value("${spring.rocket-mq.accessKey}")
private String ossAccessKey;
@Value("${spring.rocket-mq.secretKey}")
private String ossAccessSecret;
private OSS oss;
@PostConstruct
public void init() {
String ossEndpoint = "oss-cn-hangzhou.aliyuncs.com";
oss = new OSSClientBuilder().build(ossEndpoint, ossAccessKey, ossAccessSecret);
}
public CommonPageInfoDTO<ReportDataInfoDTO> report(String clientId, DataReportRequestDTO requestBody) {
List<OpenApiClientPrincipalRelationEntity> openApiClientPrincipalEntityList = openApiClientPrincipalRelationMapper.selectByClientId(clientId);
if (CollUtil.isEmpty(openApiClientPrincipalEntityList)) {
throw new ApiException("客户端未配置品牌,请联系管理员");
}
OpenApiClientPrincipalRelationEntity openApiClientPrincipalRelationEntity = openApiClientPrincipalEntityList.get(0);
String principalId = openApiClientPrincipalRelationEntity.getPrincipalId();
requestBody.setPrincipalId(principalId);
Page<DwsAfantiAdbDataOssRecordEntity> page = dwsAfantiAdbDataOssRecordMapper.page(new Page<>(requestBody.getPage(), requestBody.getSize()), requestBody);
return convertToCommonPageInfo(page);
}
@SneakyThrows
private String generatePresignedUrl(String ossUrl) {
// 生成一个带过期时间的url
String[] ossObjInfo = parseOSSUrl(ossUrl);
Instant expireInstant = Instant.now().plusSeconds(86400);
URL url = oss.generatePresignedUrl(ossObjInfo[0], ossObjInfo[1], Date.from(expireInstant));
return url.toString();
}
public static String[] parseOSSUrl(String ossUrl) throws URISyntaxException {
// 处理URL编码的特殊字符(如%2F)
// 使用正则表达式匹配bucket和object
Pattern pattern = Pattern.compile("https?://([^.]+)\\.oss(-[^.]+)?\\.([^/]+)/(.+)");
Matcher matcher = pattern.matcher(ossUrl);
if (matcher.find()) {
String bucket = matcher.group(1);
// 从匹配结果中提取object,并去掉查询参数
String fullObjectPath = matcher.group(4);
String object = fullObjectPath.split("\\?")[0];
return new String[]{bucket, object};
}
// 如果正则匹配失败,尝试URI解析
URI uri = new URI(ossUrl);
String host = uri.getHost();
if (host != null && host.endsWith(".aliyuncs.com")) {
String bucket = host.split("\\.")[0];
String object = uri.getPath().substring(1); // 去掉开头的/
return new String[]{bucket, object};
}
throw new IllegalArgumentException("无法从URL中解析出OSS bucket和object: " + ossUrl);
}
private CommonPageInfoDTO<ReportDataInfoDTO> convertToCommonPageInfo(Page<DwsAfantiAdbDataOssRecordEntity> page) {
CommonPageInfoDTO<ReportDataInfoDTO> commonPageInfo = new CommonPageInfoDTO<>();
PageInfoDTO pageInfoDTO = new PageInfoDTO();
pageInfoDTO.setPage((int) page.getCurrent());
pageInfoDTO.setSize((int) page.getSize());
pageInfoDTO.setTotal((int) page.getTotal());
pageInfoDTO.setTotalPage((int) page.getPages());
commonPageInfo.setPageInfo(pageInfoDTO);
List<DwsAfantiAdbDataOssRecordEntity> entityList = page.getRecords();
List<ReportDataInfoDTO> list = Lists.newArrayList();
for (DwsAfantiAdbDataOssRecordEntity entity : entityList) {
ReportDataInfoDTO reportDataInfo = new ReportDataInfoDTO();
reportDataInfo.setStatisticsDay(entity.getStatisticsDay().toLocalDate());
reportDataInfo.setPlatform(entity.getPlatform());
reportDataInfo.setDataType(entity.getDataType());
reportDataInfo.setFileUrl(generatePresignedUrl(entity.getOssUrl()));
reportDataInfo.setCtime(entity.getCtime());
reportDataInfo.setMtime(entity.getMtime());
reportDataInfo.setId(StrUtil.format("{}:{}:{}:{}", entity.getPrincipalId(),
DateUtil.format(entity.getStatisticsDay(), "yyyy-MM-dd"),
entity.getPlatform(), entity.getDataType()));
list.add(reportDataInfo);
}
commonPageInfo.setRows(list);
return commonPageInfo;
}
@Resource
private OpenApiClientPrincipalRelationMapper openApiClientPrincipalRelationMapper;
@Resource
private DwsAfantiAdbDataOssRecordMapper dwsAfantiAdbDataOssRecordMapper;
@Resource
private ObjectMapper objectMapper;
@Value("${spring.rocket-mq.accessKey}")
private String ossAccessKey;
@Value("${spring.rocket-mq.secretKey}")
private String ossAccessSecret;
private OSS oss;
@PostConstruct
public void init() {
String ossEndpoint = "oss-cn-hangzhou.aliyuncs.com";
oss = new OSSClientBuilder().build(ossEndpoint, ossAccessKey, ossAccessSecret);
}
@SneakyThrows
public CommonPageInfoDTO<ReportDataInfoDTO> report(String clientId, DataReportRequestDTO requestBody) {
log.info("请求数据报表入参,client_id:{}, 请求参数:{}", clientId, objectMapper.writeValueAsString(requestBody));
handleTimeRange(requestBody);
List<OpenApiClientPrincipalRelationEntity> openApiClientPrincipalEntityList = openApiClientPrincipalRelationMapper.selectByClientId(clientId);
if (CollUtil.isEmpty(openApiClientPrincipalEntityList)) {
throw new ApiException("客户端未配置品牌,请联系管理员");
}
OpenApiClientPrincipalRelationEntity openApiClientPrincipalRelationEntity = openApiClientPrincipalEntityList.get(0);
String principalId = openApiClientPrincipalRelationEntity.getPrincipalId();
requestBody.setPrincipalId(principalId);
Page<DwsAfantiAdbDataOssRecordEntity> page = dwsAfantiAdbDataOssRecordMapper.page(new Page<>(requestBody.getPage(), requestBody.getSize()), requestBody);
return convertToCommonPageInfo(page);
}
private void handleTimeRange(DataReportRequestDTO requestBody) {
LocalDate yesterday = LocalDate.now().minusDays(1);
if (Objects.nonNull(requestBody.getStartTime())) {
LocalDate sevenDaysAgo = LocalDate.now().minusDays(7);
if (sevenDaysAgo.isAfter(requestBody.getStartTime())) {
throw new ApiException(ResultCodeEnum.VALIDATE_FAILED.getCode(), "开始时间不能早于前7天");
}
}
if (Objects.isNull(requestBody.getStartTime()) && Objects.isNull(requestBody.getEndTime())) {
requestBody.setStartTime(yesterday);
requestBody.setEndTime(yesterday);
} else if (Objects.isNull(requestBody.getStartTime()) && Objects.nonNull(requestBody.getEndTime())) {
requestBody.setStartTime(requestBody.getEndTime());
} else if (Objects.isNull(requestBody.getEndTime()) && Objects.nonNull(requestBody.getStartTime())) {
requestBody.setEndTime(requestBody.getStartTime());
} else {
if (requestBody.getEndTime().isBefore(requestBody.getStartTime())) {
throw new ApiException(ResultCodeEnum.VALIDATE_FAILED.getCode(), "开始时间不能晚于结束时间");
}
}
}
@SneakyThrows
private String generatePresignedUrl(String ossUrl) {
// 生成一个带过期时间的url
String[] ossObjInfo = parseOSSUrl(ossUrl);
Instant expireInstant = Instant.now().plusSeconds(86400);
URL url = oss.generatePresignedUrl(ossObjInfo[0], ossObjInfo[1], Date.from(expireInstant));
return url.toString();
}
public static String[] parseOSSUrl(String ossUrl) throws URISyntaxException {
// 处理URL编码的特殊字符(如%2F)
// 使用正则表达式匹配bucket和object
Pattern pattern = Pattern.compile("https?://([^.]+)\\.oss(-[^.]+)?\\.([^/]+)/(.+)");
Matcher matcher = pattern.matcher(ossUrl);
if (matcher.find()) {
String bucket = matcher.group(1);
// 从匹配结果中提取object,并去掉查询参数
String fullObjectPath = matcher.group(4);
String object = fullObjectPath.split("\\?")[0];
return new String[]{bucket, object};
}
// 如果正则匹配失败,尝试URI解析
URI uri = new URI(ossUrl);
String host = uri.getHost();
if (host != null && host.endsWith(".aliyuncs.com")) {
String bucket = host.split("\\.")[0];
String object = uri.getPath().substring(1); // 去掉开头的/
return new String[]{bucket, object};
}
throw new IllegalArgumentException("无法从URL中解析出OSS bucket和object: " + ossUrl);
}
private CommonPageInfoDTO<ReportDataInfoDTO> convertToCommonPageInfo(Page<DwsAfantiAdbDataOssRecordEntity> page) {
CommonPageInfoDTO<ReportDataInfoDTO> commonPageInfo = new CommonPageInfoDTO<>();
PageInfoDTO pageInfoDTO = new PageInfoDTO();
pageInfoDTO.setPage((int) page.getCurrent());
pageInfoDTO.setSize((int) page.getSize());
pageInfoDTO.setTotal((int) page.getTotal());
pageInfoDTO.setTotalPage((int) page.getPages());
commonPageInfo.setPageInfo(pageInfoDTO);
List<DwsAfantiAdbDataOssRecordEntity> entityList = page.getRecords();
List<ReportDataInfoDTO> list = Lists.newArrayList();
for (DwsAfantiAdbDataOssRecordEntity entity : entityList) {
ReportDataInfoDTO reportDataInfo = new ReportDataInfoDTO();
reportDataInfo.setStatisticsDay(entity.getStatisticsDay().toLocalDate());
reportDataInfo.setPlatform(entity.getPlatform());
reportDataInfo.setDataType(entity.getDataType());
reportDataInfo.setFileUrl(generatePresignedUrl(entity.getOssUrl()));
reportDataInfo.setCtime(entity.getCtime());
reportDataInfo.setMtime(entity.getMtime());
reportDataInfo.setId(StrUtil.format("{}:{}:{}:{}", entity.getPrincipalId(),
DateUtil.format(entity.getStatisticsDay(), "yyyy-MM-dd"),
entity.getPlatform(), entity.getDataType()));
list.add(reportDataInfo);
}
commonPageInfo.setRows(list);
return commonPageInfo;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment