Commit 58c64827 authored by 何进财's avatar 何进财

Merge branch 'feat/reportData' into 'master'

feat: 提供第三方查询oss数据报表的接口

See merge request !71
parents b80959ed 5abdcc8c
...@@ -118,6 +118,12 @@ ...@@ -118,6 +118,12 @@
<version>1.0.2</version> <version>1.0.2</version>
</dependency> </dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.18.1</version>
</dependency>
<!-- mybatis的分页插件 --> <!-- mybatis的分页插件 -->
<dependency> <dependency>
<groupId>com.github.pagehelper</groupId> <groupId>com.github.pagehelper</groupId>
......
package com.afanticar.afantiopenapi.controller;
import com.afanticar.afantiopenapi.model.BaseResponse;
import com.afanticar.afantiopenapi.model.dto.CommonPageInfoDTO;
import com.afanticar.afantiopenapi.model.dto.DataReportRequestDTO;
import com.afanticar.afantiopenapi.model.dto.ReportDataInfoDTO;
import com.afanticar.afantiopenapi.service.DataReportService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
* @author hejincai
* @since 2025/4/23 16:35
**/
@Api("阿凡提数据报表接口")
@RestController
@RequestMapping("/info")
@Slf4j
@Validated
public class DataReportController extends BaseController {
@Resource
private DataReportService dataReportService;
@PostMapping("/report")
@ApiOperation("获取报表数据")
public BaseResponse<CommonPageInfoDTO<ReportDataInfoDTO>> report(HttpServletRequest request, @RequestBody @Valid DataReportRequestDTO requestBody) {
String clientId = (String) request.getAttribute("clientId");
return BaseController.success(dataReportService.report(clientId, requestBody));
}
}
package com.afanticar.afantiopenapi.mapper;
import com.afanticar.afantiopenapi.model.dto.DataReportRequestDTO;
import com.afanticar.afantiopenapi.model.entity.DwsAfantiAdbDataOssRecordEntity;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author hejincai
* @since 2025/4/24 10:00
*/
@Mapper
@DS("afanti-bi-app")
public interface DwsAfantiAdbDataOssRecordMapper extends BaseMapper<DwsAfantiAdbDataOssRecordEntity> {
/**
* 分页查询oss数据报表
* @author hejincai
* @since 2025/4/24 11:54
* @return com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.afanticar.afantiopenapi.model.entity.DwsAfantiAdbDataOssRecordEntity>
* @param page 分页信息
* @param param 查询参数
*/
Page<DwsAfantiAdbDataOssRecordEntity> page(Page<?> page, @Param("param") DataReportRequestDTO param);
}
package com.afanticar.afantiopenapi.mapper;
import com.afanticar.afantiopenapi.model.entity.OpenApiClientPrincipalRelationEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author hejincai
* @since 2025/4/24 9:47
*/
@Mapper
public interface OpenApiClientPrincipalRelationMapper extends BaseMapper<OpenApiClientPrincipalRelationEntity> {
/**
* 通过客户端id查询主体关系
* @author hejincai
* @since 2025/4/24 11:54
* @return java.util.List<com.afanticar.afantiopenapi.model.entity.OpenApiClientPrincipalRelationEntity>
* @param clientId 客户端id
*/
List<OpenApiClientPrincipalRelationEntity> selectByClientId(@Param("clientId") String clientId);
}
package com.afanticar.afantiopenapi.model.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author hejincai
* @since 2025/4/23 16:47
**/
@Data
@ApiModel("公共分页信息")
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class CommonPageInfoDTO<T> {
@ApiModelProperty("分页信息")
private PageInfoDTO pageInfo;
@ApiModelProperty("数据列表")
private List<T> rows;
}
package com.afanticar.afantiopenapi.model.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
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.util.List;
/**
* @author hejincai
* @since 2025/4/23 16:41
**/
@Data
@ApiModel(description = "数据报表请求入参")
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class DataReportRequestDTO {
@ApiModelProperty("开始时间。格式为:yyyy-MM-dd。例如2024-01-01")
@NotBlank(message = "开始时间不可为空")
private String startTime;
@ApiModelProperty("结束时间。格式为:yyyy-MM-dd。例如2024-01-01")
@NotBlank(message = "结束时间不可为空")
private String endTime;
@ApiModelProperty("平台类型 public-公共数据 douyin-抖音 kuaishou-快手xiaohongshu-小红书 shipinhao-视频号")
@NotEmpty(message = "平台类型不可为空,可选类型 public-公共数据 douyin-抖音 kuaishou-快手xiaohongshu-小红书 shipinhao-视频号 ")
private List<String> platformList;
@ApiModelProperty("数据报表类型 author_info-主播信息 aweme_info-视频信息 live_info-直播member_info-门店信息 note_info-小红书信息")
@NotEmpty(message = "平台类型不可为空,可选类型 author_info-主播信息 aweme_info-视频信息 live_info-直播 member_info-门店信息 note_info-小红书信息")
private List<String> dataTypeList;
@ApiModelProperty("页码。默认为1")
private Integer page = 1;
@ApiModelProperty("页面大小。最大100,默认100")
private Integer size = 100;
@ApiModelProperty(value = "主体id", hidden = true)
private String principalId;
}
package com.afanticar.afantiopenapi.model.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author hejincai
* @since 2025/4/23 16:39
**/
@Data
@ApiModel("分页信息")
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PageInfoDTO {
@ApiModelProperty("当前页")
private Integer page;
@ApiModelProperty("页面大小,即每页展示的数据量")
private Integer size;
@ApiModelProperty("总数量")
private Integer total;
@ApiModelProperty("总页数")
private Integer totalPage;
}
package com.afanticar.afantiopenapi.model.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* @author hejincai
* @since 2025/4/23 16:49
**/
@Data
@ApiModel("数据报表信息")
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class ReportDataInfoDTO {
@ApiModelProperty("主键")
private String id;
@ApiModelProperty("统计日期,格式为:yyyy-MM-dd。例如2024-01-01")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate statisticsDay;
@ApiModelProperty("平台类型 public-公共数据 douyin-抖音 kuaishou-快手xiaohongshu-小红书 shipinhao-视频号")
private String platform;
@ApiModelProperty("数据报表类型 author_info-主播信息 aweme_info-视频信息 live_info-直播member_info-门店信息note_info 笔记信息")
private String dataType;
@ApiModelProperty("文件地址")
private String fileUrl;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime ctime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime mtime;
}
package com.afanticar.afantiopenapi.model.entity;
import com.alibaba.fastjson.annotation.JSONType;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author hejincai
* @since 2025/4/24 9:54
**/
@Data
@TableName("dws_afanti_adb_data_oss_record")
@JsonNaming(value = com.fasterxml.jackson.databind.PropertyNamingStrategy.SnakeCaseStrategy.class)
@JSONType(naming = com.alibaba.fastjson.PropertyNamingStrategy.SnakeCase)
public class DwsAfantiAdbDataOssRecordEntity {
@ApiModelProperty("主体id")
private String principalId;
@ApiModelProperty("统计的日期")
private LocalDateTime statisticsDay;
@ApiModelProperty("平台类型 public-公共数据 douyin-抖音 kuaishou-快手xiaohongshu-小红书 shipinhao-视频号")
private String platform;
@ApiModelProperty("数据报表类型 author_info-主播信息 aweme_info-视频信息 live_info-直播member_info-门店信息 note_info 笔记信息")
private String dataType;
@ApiModelProperty("文件地址")
private String ossUrl;
@ApiModelProperty("文件名称")
private String fileName;
@ApiModelProperty("创建时间")
private LocalDateTime ctime;
@ApiModelProperty("修改时间")
private LocalDateTime mtime;
}
package com.afanticar.afantiopenapi.model.entity;
import com.alibaba.fastjson.annotation.JSONType;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author hejincai
* @since 2025/4/24 9:32
**/
@Data
@TableName("open_api_client_principal_relation")
@JsonNaming(value = com.fasterxml.jackson.databind.PropertyNamingStrategy.SnakeCaseStrategy.class)
@JSONType(naming = com.alibaba.fastjson.PropertyNamingStrategy.SnakeCase)
public class OpenApiClientPrincipalRelationEntity {
@ApiModelProperty("主键")
private String id;
@ApiModelProperty("客户端id")
private String clientId;
@ApiModelProperty("主体id")
private String principalId;
@ApiModelProperty("删除状态")
private Integer isDeleted;
@ApiModelProperty("创建时间")
private LocalDateTime ctime;
@ApiModelProperty("修改时间")
private LocalDateTime mtime;
}
package com.afanticar.afantiopenapi.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.afanticar.afantiopenapi.mapper.DwsAfantiAdbDataOssRecordMapper;
import com.afanticar.afantiopenapi.mapper.OpenApiClientPrincipalRelationMapper;
import com.afanticar.afantiopenapi.model.dto.CommonPageInfoDTO;
import com.afanticar.afantiopenapi.model.dto.DataReportRequestDTO;
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.exception.ApiException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author hejincai
* @since 2025/4/24 9:41
**/
@Service
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;
}
}
...@@ -30,7 +30,7 @@ spring: ...@@ -30,7 +30,7 @@ spring:
# 阿里云accesskey # 阿里云accesskey
aws: aws:
enabled: true enabled: true
role: MqFullAccess role: ${spring.profiles.active}MqFullAccess
backend: alicloud backend: alicloud
access-key-property: spring.rocket-mq.accessKey access-key-property: spring.rocket-mq.accessKey
secret-key-property: spring.rocket-mq.secretKey secret-key-property: spring.rocket-mq.secretKey
......
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.afanticar.afantiopenapi.mapper.DwsAfantiAdbDataOssRecordMapper">
<select id="page" resultType="com.afanticar.afantiopenapi.model.entity.DwsAfantiAdbDataOssRecordEntity">
select * from dws_afanti_adb_data_oss_record where
statistics_day between #{param.startTime} and #{param.endTime}
and platform in <foreach collection="param.platformList" open="(" close=")" separator="," item="item">
#{item}
</foreach>
and data_type in <foreach collection="param.dataTypeList" open="(" close=")" separator="," item="item">
#{item}
</foreach>
and principal_id = #{param.principalId}
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.afanticar.afantiopenapi.mapper.OpenApiClientPrincipalRelationMapper">
<select id="selectByClientId"
resultType="com.afanticar.afantiopenapi.model.entity.OpenApiClientPrincipalRelationEntity">
select * from open_api_client_principal_relation where client_id = #{clientId} and is_deleted = 0
</select>
</mapper>
\ No newline at end of file
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