MALL-PRODUCT:同步 jdk21 boot 最新代码

This commit is contained in:
YunaiV
2024-01-19 21:36:50 +08:00
parent 2208eef8cf
commit f612b6ebdd
124 changed files with 1956 additions and 1705 deletions

View File

@@ -2,40 +2,86 @@ package cn.iocoder.yudao.module.statistics.controller.admin.product;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.statistics.dal.mysql.product.ProductSpuStatisticsDO;
import cn.iocoder.yudao.module.statistics.dal.mysql.product.ProductStatisticsDO;
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsReqVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.product.ProductStatisticsDO;
import cn.iocoder.yudao.module.statistics.service.product.ProductStatisticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - 商品统计")
@RestController
@RequestMapping("/statistics/product")
@Validated
@Slf4j
public class ProductStatisticsController {
// TODO @麦子:返回 ProductStatisticsComparisonResp 里面有两个字段,一个是选择的时间范围的合计结果,一个是对比的时间范围的合计结果;
// 例如说,选择时间范围是 2023-10-01 ~ 2023-10-02那么对比就是 2023-09-30再倒推 2 天;
public CommonResult<Object> getProductStatisticsComparison() {
return null;
@Resource
private ProductStatisticsService productStatisticsService;
@Resource
private ProductSpuApi productSpuApi;
@GetMapping("/analyse")
@Operation(summary = "获得商品统计分析")
@PreAuthorize("@ss.hasPermission('statistics:product:query')")
public CommonResult<DataComparisonRespVO<ProductStatisticsRespVO>> getProductStatisticsAnalyse(ProductStatisticsReqVO reqVO) {
return success(productStatisticsService.getProductStatisticsAnalyse(reqVO));
}
// TODO @麦子查询指定时间范围内的商品统计数据DO 到时需要改成 VO 哈
public CommonResult<List<ProductStatisticsDO>> getProductStatisticsList(
LocalDateTime[] times) {
return null;
@GetMapping("/list")
@Operation(summary = "获得商品统计明细(日期维度)")
@PreAuthorize("@ss.hasPermission('statistics:product:query')")
public CommonResult<List<ProductStatisticsRespVO>> getProductStatisticsList(ProductStatisticsReqVO reqVO) {
List<ProductStatisticsDO> list = productStatisticsService.getProductStatisticsList(reqVO);
return success(BeanUtils.toBean(list, ProductStatisticsRespVO.class));
}
// TODO @麦子:查询指定时间范围内的商品 SPU 统计数据DO 到时需要改成 VO 哈
// 入参是分页参数 + 时间范围 + 排序字段
public CommonResult<PageResult<ProductSpuStatisticsDO>> getProductSpuStatisticsPage() {
return null;
@GetMapping("/export-excel")
@Operation(summary = "导出获得商品统计明细 Excel日期维度")
@PreAuthorize("@ss.hasPermission('statistics:product:export')")
public void exportProductStatisticsExcel(ProductStatisticsReqVO reqVO, HttpServletResponse response) throws IOException {
List<ProductStatisticsDO> list = productStatisticsService.getProductStatisticsList(reqVO);
// 导出 Excel
List<ProductStatisticsRespVO> voList = BeanUtils.toBean(list, ProductStatisticsRespVO.class);
ExcelUtils.write(response, "商品状况.xls", "数据", ProductStatisticsRespVO.class, voList);
}
}
@GetMapping("/rank-page")
@Operation(summary = "获得商品统计排行榜分页(商品维度)")
@PreAuthorize("@ss.hasPermission('statistics:product:query')")
public CommonResult<PageResult<ProductStatisticsRespVO>> getProductStatisticsRankPage(@Valid ProductStatisticsReqVO reqVO,
@Valid SortablePageParam pageParam) {
PageResult<ProductStatisticsDO> pageResult = productStatisticsService.getProductStatisticsRankPage(reqVO, pageParam);
// 处理商品信息
Set<Long> spuIds = convertSet(pageResult.getList(), ProductStatisticsDO::getSpuId);
Map<Long, ProductSpuRespDTO> spuMap = convertMap(productSpuApi.getSpuList(spuIds).getCheckedData(), ProductSpuRespDTO::getId);
return success(BeanUtils.toBean(pageResult, ProductStatisticsRespVO.class,
item -> Optional.ofNullable(spuMap.get(item.getSpuId()))
.ifPresent(spu -> item.setName(spu.getName()).setPicUrl(spu.getPicUrl()))));
}
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.statistics.controller.admin.product.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 商品统计分析 Request VO")
@Data
@ToString(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class ProductStatisticsReqVO {
@Schema(description = "统计时间范围", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] times;
}

View File

@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.statistics.controller.admin.product.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 商品统计 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ProductStatisticsRespVO {
@Schema(description = "编号,主键自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "12393")
private Long id;
@Schema(description = "统计日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-12-16")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
@ExcelProperty("统计日期")
private LocalDate time;
@Schema(description = "商品SPU编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15114")
@ExcelProperty("商品SPU编号")
private Long spuId;
// region 商品信息
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品名称")
@ExcelProperty("商品名称")
private String name;
@Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "15114")
@ExcelProperty("商品封面图")
private String picUrl;
// endregion
@Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "17505")
@ExcelProperty("浏览量")
private Integer browseCount;
@Schema(description = "访客量", requiredMode = Schema.RequiredMode.REQUIRED, example = "11814")
@ExcelProperty("访客量")
private Integer browseUserCount;
@Schema(description = "收藏数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20950")
@ExcelProperty("收藏数量")
private Integer favoriteCount;
@Schema(description = "加购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "28493")
@ExcelProperty("加购数量")
private Integer cartCount;
@Schema(description = "下单件数", requiredMode = Schema.RequiredMode.REQUIRED, example = "18966")
@ExcelProperty("下单件数")
private Integer orderCount;
@Schema(description = "支付件数", requiredMode = Schema.RequiredMode.REQUIRED, example = "15142")
@ExcelProperty("支付件数")
private Integer orderPayCount;
@Schema(description = "支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "11595")
@ExcelProperty("支付金额,单位:分")
private Integer orderPayPrice;
@Schema(description = "退款件数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2591")
@ExcelProperty("退款件数")
private Integer afterSaleCount;
@Schema(description = "退款金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "21709")
@ExcelProperty("退款金额,单位:分")
private Integer afterSaleRefundPrice;
@Schema(description = "访客支付转化率(百分比)", requiredMode = Schema.RequiredMode.REQUIRED, example = "15")
private Integer browseConvertPercent;
}

View File

@@ -18,6 +18,9 @@ import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -25,9 +28,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.List;
@@ -49,7 +49,6 @@ public class TradeStatisticsController {
@Resource
private BrokerageStatisticsService brokerageStatisticsService;
// TODO 芋艿:已经 review
@GetMapping("/summary")
@Operation(summary = "获得交易统计")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")
@@ -67,17 +66,14 @@ public class TradeStatisticsController {
return success(TradeStatisticsConvert.INSTANCE.convert(yesterdayData, beforeYesterdayData, monthData, lastMonthData));
}
// TODO @疯狂:【晚点再改和讨论;等首页的接口出来】这个要不还是叫 analyse对比选中的时间段和上一个时间段类似 MemberStatisticsController 的 getMemberAnalyse
@GetMapping("/trend/summary")
@GetMapping("/analyse")
@Operation(summary = "获得交易状况统计")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")
public CommonResult<DataComparisonRespVO<TradeTrendSummaryRespVO>> getTradeTrendSummaryComparison(
TradeTrendReqVO reqVO) {
return success(tradeStatisticsService.getTradeTrendSummaryComparison(ArrayUtil.get(reqVO.getTimes(), 0),
public CommonResult<DataComparisonRespVO<TradeTrendSummaryRespVO>> getTradeStatisticsAnalyse(TradeTrendReqVO reqVO) {
return success(tradeStatisticsService.getTradeStatisticsAnalyse(ArrayUtil.get(reqVO.getTimes(), 0),
ArrayUtil.get(reqVO.getTimes(), 1)));
}
// TODO 芋艿:已经 review
@GetMapping("/list")
@Operation(summary = "获得交易状况明细")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")
@@ -87,7 +83,6 @@ public class TradeStatisticsController {
return success(TradeStatisticsConvert.INSTANCE.convertList(list));
}
// TODO 芋艿:已经 review
@GetMapping("/export-excel")
@Operation(summary = "导出获得交易状况明细 Excel")
@PreAuthorize("@ss.hasPermission('statistics:trade:export')")
@@ -100,7 +95,6 @@ public class TradeStatisticsController {
ExcelUtils.write(response, "交易状况.xls", "数据", TradeTrendSummaryExcelVO.class, data);
}
// TODO 芋艿:已经 review
@GetMapping("/order-count")
@Operation(summary = "获得交易订单数量")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")
@@ -118,7 +112,6 @@ public class TradeStatisticsController {
return success(TradeStatisticsConvert.INSTANCE.convert(undeliveredCount, pickUpCount, afterSaleApplyCount, auditingWithdrawCount));
}
// TODO 芋艿:已经 review
@GetMapping("/order-comparison")
@Operation(summary = "获得交易订单数量")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")
@@ -126,7 +119,6 @@ public class TradeStatisticsController {
return success(tradeOrderStatisticsService.getOrderComparison());
}
// TODO 芋艿:已经 review
@GetMapping("/order-count-trend")
@Operation(summary = "获得订单量趋势统计")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")

View File

@@ -12,7 +12,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Data
public class TradeTrendSummaryRespVO {
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-12-16")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate date;

View File

@@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.statistics.dal.dataobject.product;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDate;
/**
* 商品统计 DO
*
* @author owen
*/
@TableName("product_statistics")
@KeySequence("product_statistics_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductStatisticsDO extends BaseDO {
/**
* 编号,主键自增
*/
@TableId
private Long id;
/**
* 统计日期
*/
private LocalDate time;
/**
* 商品 SPU 编号
*/
private Long spuId;
/**
* 浏览量
*/
private Integer browseCount;
/**
* 访客量
*/
private Integer browseUserCount;
/**
* 收藏数量
*/
private Integer favoriteCount;
/**
* 加购数量
*/
private Integer cartCount;
/**
* 下单件数
*/
private Integer orderCount;
/**
* 支付件数
*/
private Integer orderPayCount;
/**
* 支付金额,单位:分
*/
private Integer orderPayPrice;
/**
* 退款件数
*/
private Integer afterSaleCount;
/**
* 退款金额,单位:分
*/
private Integer afterSaleRefundPrice;
/**
* 访客支付转化率(百分比)
*/
private Integer browseConvertPercent;
}

View File

@@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.statistics.dal.mysql.product;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsReqVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.product.ProductStatisticsDO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
/**
* 商品统计 Mapper
*
* @author owen
*/
@Mapper
public interface ProductStatisticsMapper extends BaseMapperX<ProductStatisticsDO> {
default PageResult<ProductStatisticsDO> selectPageGroupBySpuId(ProductStatisticsReqVO reqVO, SortablePageParam pageParam) {
return selectPage(pageParam, buildWrapper(reqVO)
.groupBy(ProductStatisticsDO::getSpuId)
.select(ProductStatisticsDO::getSpuId)
);
}
default List<ProductStatisticsDO> selectListByTimeBetween(ProductStatisticsReqVO reqVO) {
return selectList(buildWrapper(reqVO)
.groupBy(ProductStatisticsDO::getTime)
.select(ProductStatisticsDO::getTime));
}
default ProductStatisticsRespVO selectVoByTimeBetween(ProductStatisticsReqVO reqVO) {
return selectJoinOne(ProductStatisticsRespVO.class, buildWrapper(reqVO));
}
/**
* 构建 LambdaWrapper
*
* @param reqVO 查询参数
* @return LambdaWrapper
*/
private static MPJLambdaWrapperX<ProductStatisticsDO> buildWrapper(ProductStatisticsReqVO reqVO) {
return new MPJLambdaWrapperX<ProductStatisticsDO>()
.betweenIfPresent(ProductStatisticsDO::getTime, reqVO.getTimes())
.selectSum(ProductStatisticsDO::getBrowseCount)
.selectSum(ProductStatisticsDO::getBrowseUserCount)
.selectSum(ProductStatisticsDO::getFavoriteCount)
.selectSum(ProductStatisticsDO::getCartCount)
.selectSum(ProductStatisticsDO::getOrderCount)
.selectSum(ProductStatisticsDO::getOrderPayCount)
.selectSum(ProductStatisticsDO::getOrderPayPrice)
.selectSum(ProductStatisticsDO::getAfterSaleCount)
.selectSum(ProductStatisticsDO::getAfterSaleRefundPrice)
.selectAvg(ProductStatisticsDO::getBrowseConvertPercent);
}
/**
* 根据时间范围统计商品信息
*
* @param page 分页参数
* @param beginTime 起始时间
* @param endTime 截止时间
* @return 统计
*/
IPage<ProductStatisticsDO> selectStatisticsResultPageByTimeBetween(IPage<ProductStatisticsDO> page,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
default Long selectCountByTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
return selectCount(new LambdaQueryWrapperX<ProductStatisticsDO>().between(ProductStatisticsDO::getTime, beginTime, endTime));
}
}

View File

@@ -0,0 +1,10 @@
package cn.iocoder.yudao.module.statistics.framework.rpc.config;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {ProductSpuApi.class})
public class RpcConfiguration {
}

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.statistics.framework.rpc;

View File

@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.statistics.job.product;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.statistics.service.product.ProductStatisticsService;
import com.xxl.job.core.handler.annotation.XxlJob;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
// TODO 芋艿:缺个 Job 的配置;等和 Product 一起配置
/**
* 商品统计 Job
*
* @author owen
*/
@Component
public class ProductStatisticsJob {
@Resource
private ProductStatisticsService productStatisticsService;
/**
* 执行商品统计任务
*
* @param param 要统计的天数只能是正整数1 代表昨日数据
* @return 统计结果
*/
@XxlJob("productStatisticsJob")
@TenantJob
public String execute(String param) {
// 默认昨日
param = ObjUtil.defaultIfBlank(param, "1");
// 校验参数的合理性
if (!NumberUtil.isInteger(param)) {
throw new RuntimeException("商品统计任务的参数只能为是正整数");
}
Integer days = Convert.toInt(param, 0);
if (days < 1) {
throw new RuntimeException("商品统计任务的参数只能为是正整数");
}
String result = productStatisticsService.statisticsProduct(days);
return StrUtil.format("商品统计:\n{}", result);
}
}

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.statistics.service.pay;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.statistics.dal.mysql.pay.PayWalletStatisticsMapper;
import cn.iocoder.yudao.module.statistics.service.pay.bo.RechargeSummaryRespBO;
import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;

View File

@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.statistics.service.product;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsReqVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.product.ProductStatisticsDO;
import java.util.List;
/**
* 商品统计 Service 接口
*
* @author owen
*/
public interface ProductStatisticsService {
/**
* 获得商品统计排行榜分页
*
* @param reqVO 查询条件
* @param pageParam 分页排序查询
* @return 商品统计分页
*/
PageResult<ProductStatisticsDO> getProductStatisticsRankPage(ProductStatisticsReqVO reqVO, SortablePageParam pageParam);
/**
* 获得商品状况统计分析
*
* @param reqVO 查询条件
* @return 统计数据对照
*/
DataComparisonRespVO<ProductStatisticsRespVO> getProductStatisticsAnalyse(ProductStatisticsReqVO reqVO);
/**
* 获得商品状况明细
*
* @param reqVO 查询条件
* @return 统计数据对照
*/
List<ProductStatisticsDO> getProductStatisticsList(ProductStatisticsReqVO reqVO);
/**
* 统计指定天数的商品数据
*
* @return 统计结果
*/
String statisticsProduct(Integer days);
}

View File

@@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.statistics.service.product;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsReqVO;
import cn.iocoder.yudao.module.statistics.controller.admin.product.vo.ProductStatisticsRespVO;
import cn.iocoder.yudao.module.statistics.dal.dataobject.product.ProductStatisticsDO;
import cn.iocoder.yudao.module.statistics.dal.mysql.product.ProductStatisticsMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.validation.annotation.Validated;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 商品统计 Service 实现类
*
* @author owen
*/
@Service
@Validated
public class ProductStatisticsServiceImpl implements ProductStatisticsService {
@Resource
private ProductStatisticsMapper productStatisticsMapper;
@Override
public PageResult<ProductStatisticsDO> getProductStatisticsRankPage(ProductStatisticsReqVO reqVO, SortablePageParam pageParam) {
PageUtils.buildDefaultSortingField(pageParam, ProductStatisticsDO::getBrowseCount); // 默认浏览量倒序
return productStatisticsMapper.selectPageGroupBySpuId(reqVO, pageParam);
}
@Override
public DataComparisonRespVO<ProductStatisticsRespVO> getProductStatisticsAnalyse(ProductStatisticsReqVO reqVO) {
LocalDateTime beginTime = ArrayUtil.get(reqVO.getTimes(), 0);
LocalDateTime endTime = ArrayUtil.get(reqVO.getTimes(), 1);
// 统计数据
ProductStatisticsRespVO value = productStatisticsMapper.selectVoByTimeBetween(reqVO);
// 对照数据
LocalDateTime referenceBeginTime = beginTime.minus(Duration.between(beginTime, endTime));
ProductStatisticsReqVO referenceReqVO = new ProductStatisticsReqVO(new LocalDateTime[]{referenceBeginTime, beginTime});
ProductStatisticsRespVO reference = productStatisticsMapper.selectVoByTimeBetween(referenceReqVO);
return new DataComparisonRespVO<>(value, reference);
}
@Override
public List<ProductStatisticsDO> getProductStatisticsList(ProductStatisticsReqVO reqVO) {
return productStatisticsMapper.selectListByTimeBetween(reqVO);
}
@Override
public String statisticsProduct(Integer days) {
LocalDateTime today = LocalDateTime.now();
return IntStream.rangeClosed(1, days)
.mapToObj(day -> statisticsProduct(today.minusDays(day)))
.sorted()
.collect(Collectors.joining("\n"));
}
/**
* 统计商品数据
*
* @param date 需要统计的日期
* @return 统计结果
*/
private String statisticsProduct(LocalDateTime date) {
// 1. 处理统计时间范围
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date);
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(date);
String dateStr = DatePattern.NORM_DATE_FORMATTER.format(date);
// 2. 检查该日是否已经统计过
Long count = productStatisticsMapper.selectCountByTimeBetween(beginTime, endTime);
if (count != null && count > 0) {
return dateStr + " 数据已存在,如果需要重新统计,请先删除对应的数据";
}
StopWatch stopWatch = new StopWatch(dateStr);
stopWatch.start();
// 4. 分页统计,避免商品表数据较多时,出现超时问题
final int pageSize = 100;
for (int pageNo = 1; ; pageNo++) {
IPage<ProductStatisticsDO> page = productStatisticsMapper.selectStatisticsResultPageByTimeBetween(
Page.of(pageNo, pageSize, false), beginTime, endTime);
if (CollUtil.isEmpty(page.getRecords())) {
break;
}
// 4.1 计算访客支付转化率(百分比)
for (ProductStatisticsDO record : page.getRecords()) {
record.setTime(date.toLocalDate());
if (record.getBrowseUserCount() != null && ObjUtil.notEqual(record.getBrowseUserCount(), 0)) {
record.setBrowseConvertPercent(100 * record.getOrderPayCount() / record.getBrowseUserCount());
}
}
// 4.2 插入数据
productStatisticsMapper.insertBatch(page.getRecords());
}
return stopWatch.prettyPrint();
}
}

View File

@@ -20,7 +20,7 @@ public interface TradeStatisticsService {
*
* @return 统计数据对照
*/
DataComparisonRespVO<TradeTrendSummaryRespVO> getTradeTrendSummaryComparison(
DataComparisonRespVO<TradeTrendSummaryRespVO> getTradeStatisticsAnalyse(
LocalDateTime beginTime, LocalDateTime endTime);
/**

View File

@@ -60,7 +60,7 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
}
@Override
public DataComparisonRespVO<TradeTrendSummaryRespVO> getTradeTrendSummaryComparison(LocalDateTime beginTime,
public DataComparisonRespVO<TradeTrendSummaryRespVO> getTradeStatisticsAnalyse(LocalDateTime beginTime,
LocalDateTime endTime) {
// 统计数据
TradeTrendSummaryRespVO value = tradeStatisticsMapper.selectVoByTimeBetween(beginTime, endTime);
@@ -99,7 +99,7 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
// 1. 处理统计时间范围
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date);
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(date);
String dateStr = DatePattern.NORM_DATE_FORMAT.format(date);
String dateStr = DatePattern.NORM_DATE_FORMATTER.format(date);
// 2. 检查该日是否已经统计过
TradeStatisticsDO entity = tradeStatisticsMapper.selectByTimeBetween(beginTime, endTime);
if (entity != null) {

View File

@@ -0,0 +1,64 @@
<?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="cn.iocoder.yudao.module.statistics.dal.mysql.product.ProductStatisticsMapper">
<select id="selectStatisticsResultPageByTimeBetween"
resultType="cn.iocoder.yudao.module.statistics.dal.dataobject.product.ProductStatisticsDO">
SELECT spu.id AS spuId
-- 浏览量:一个用户可以有多次
, (SELECT COUNT(1)
FROM product_browse_history
WHERE spu_id = spu.id
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS browse_count
-- 访客量:按用户去重计数
, (SELECT COUNT(DISTINCT user_id)
FROM product_browse_history
WHERE spu_id = spu.id
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS browse_user_count
-- 收藏数量:按用户去重计数
, (SELECT COUNT(DISTINCT user_id)
FROM product_favorite
WHERE spu_id = spu.id
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS favorite_count
-- 加购数量:按用户去重计数
, (SELECT COUNT(DISTINCT user_id)
FROM trade_cart
WHERE spu_id = spu.id
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS cart_count
-- 下单件数
, (SELECT IFNULL(SUM(count), 0)
FROM trade_order_item
WHERE spu_id = spu.id
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS order_count
-- 支付件数
, (SELECT IFNULL(SUM(item.count), 0)
FROM trade_order_item item
JOIN trade_order o ON item.order_id = o.id
WHERE spu_id = spu.id
AND o.pay_status = TRUE
AND item.create_time BETWEEN #{beginTime} AND #{endTime}) AS order_pay_count
-- 支付金额
, (SELECT IFNULL(SUM(item.pay_price), 0)
FROM trade_order_item item
JOIN trade_order o ON item.order_id = o.id
WHERE spu_id = spu.id
AND o.pay_status = TRUE
AND item.create_time BETWEEN #{beginTime} AND #{endTime}) AS order_pay_price
-- 退款件数
, (SELECT IFNULL(SUM(count), 0)
FROM trade_after_sale
WHERE spu_id = spu.id
AND refund_time IS NOT NULL
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS after_sale_count
-- 退款金额
, (SELECT IFNULL(SUM(refund_price), 0)
FROM trade_after_sale
WHERE spu_id = spu.id
AND refund_time IS NOT NULL
AND create_time BETWEEN #{beginTime} AND #{endTime}) AS after_sale_refund_price
FROM product_spu spu
WHERE spu.deleted = FALSE
ORDER BY spu.id
</select>
</mapper>