统计:增加商品统计
This commit is contained in:
parent
6de17c38fd
commit
4a007f0c98
|
@ -0,0 +1,34 @@
|
|||
CREATE TABLE product_statistics
|
||||
(
|
||||
id bigint AUTO_INCREMENT COMMENT '编号,主键自增' PRIMARY KEY,
|
||||
time date NOT NULL COMMENT '统计日期',
|
||||
spu_id bigint NOT NULL COMMENT '商品SPU编号',
|
||||
browse_count int DEFAULT 0 NOT NULL COMMENT '浏览量',
|
||||
browse_user_count int DEFAULT 0 NOT NULL COMMENT '访客量',
|
||||
favorite_count int DEFAULT 0 NOT NULL COMMENT '收藏数量',
|
||||
cart_count int DEFAULT 0 NOT NULL COMMENT '加购数量',
|
||||
order_count int DEFAULT 0 NOT NULL COMMENT '下单件数',
|
||||
order_pay_count int DEFAULT 0 NOT NULL COMMENT '支付件数',
|
||||
order_pay_price int DEFAULT 0 NOT NULL COMMENT '支付金额,单位:分',
|
||||
after_sale_count int DEFAULT 0 NOT NULL COMMENT '退款件数',
|
||||
after_sale_refund_price int DEFAULT 0 NOT NULL COMMENT '退款金额,单位:分',
|
||||
browse_convert_percent int DEFAULT 0 NOT NULL COMMENT '访客支付转化率(百分比)',
|
||||
creator varchar(64) DEFAULT '' NULL COMMENT '创建者',
|
||||
create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||
updater varchar(64) DEFAULT '' NULL COMMENT '更新者',
|
||||
update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
deleted bit DEFAULT b'0' NOT NULL COMMENT '是否删除',
|
||||
tenant_id bigint DEFAULT 0 NOT NULL COMMENT '租户编号'
|
||||
)
|
||||
COMMENT '商品统计表';
|
||||
|
||||
CREATE INDEX idx_time
|
||||
ON product_statistics (time);
|
||||
|
||||
CREATE INDEX idx_spu_id
|
||||
ON product_statistics (spu_id);
|
||||
|
||||
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES ('商品统计', '', 2, 6, 2358, 'product', 'fa:product-hunt', 'statistics/product/index', 'ProductStatistics', 0, true, true, true, '', '2023-12-15 18:54:28', '', '2023-12-15 18:54:33', false);
|
||||
SELECT @parentId1 := LAST_INSERT_ID();
|
||||
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES ('商品统计查询', 'statistics:product:query', 3, 1, @parentId, '', '', '', null, 0, true, true, true, '', '2023-09-30 03:22:40', '', '2023-09-30 03:22:40', false);
|
||||
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES ('商品统计导出', 'statistics:product:export', 3, 2, @parentId, '', '', '', null, 0, true, true, true, '', '2023-09-30 03:22:40', '', '2023-09-30 03:22:40', false);
|
|
@ -0,0 +1,19 @@
|
|||
package cn.iocoder.yudao.framework.common.pojo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "可排序的分页参数")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class SortablePageParam extends PageParam {
|
||||
|
||||
@Schema(description = "排序字段")
|
||||
private List<SortingField> sortingFields;
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Bean 工具类
|
||||
|
@ -27,11 +28,19 @@ public class BeanUtils {
|
|||
return CollectionUtils.convertList(source, s -> toBean(s, targetType));
|
||||
}
|
||||
|
||||
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
|
||||
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
|
||||
return toBean(source, targetType, null);
|
||||
}
|
||||
|
||||
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType, Consumer<T> peek) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return new PageResult<>(toBean(source.getList(), targetType), source.getTotal());
|
||||
List<T> list = toBean(source.getList(), targetType);
|
||||
if (peek != null) {
|
||||
list.forEach(peek);
|
||||
}
|
||||
return new PageResult<>(list, source.getTotal());
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.mybatis.core.mapper;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
|
@ -27,7 +29,15 @@ import java.util.List;
|
|||
*/
|
||||
public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
||||
|
||||
default PageResult<T> selectPage(SortablePageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
|
||||
return selectPage(pageParam, pageParam.getSortingFields(), queryWrapper);
|
||||
}
|
||||
|
||||
default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
|
||||
return selectPage(pageParam, null, queryWrapper);
|
||||
}
|
||||
|
||||
default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) {
|
||||
// 特殊:不分页,直接查询全部
|
||||
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
|
||||
List<T> list = selectList(queryWrapper);
|
||||
|
@ -35,7 +45,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
|||
}
|
||||
|
||||
// MyBatis Plus 查询
|
||||
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
|
||||
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam, sortingFields);
|
||||
selectPage(mpPage, queryWrapper);
|
||||
// 转换返回
|
||||
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.lang.func.LambdaUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
|
@ -11,6 +16,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||
import net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -45,8 +51,8 @@ public class MyBatisUtils {
|
|||
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
|
||||
*
|
||||
* @param interceptor 链
|
||||
* @param inner 拦截器
|
||||
* @param index 位置
|
||||
* @param inner 拦截器
|
||||
* @param index 位置
|
||||
*/
|
||||
public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) {
|
||||
List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());
|
||||
|
@ -73,9 +79,9 @@ public class MyBatisUtils {
|
|||
/**
|
||||
* 构建 Column 对象
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param tableName 表名
|
||||
* @param tableAlias 别名
|
||||
* @param column 字段名
|
||||
* @param column 字段名
|
||||
* @return Column 对象
|
||||
*/
|
||||
public static Column buildColumn(String tableName, Alias tableAlias, String column) {
|
||||
|
@ -85,4 +91,46 @@ public class MyBatisUtils {
|
|||
return new Column(tableName + StringPool.DOT + column);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建排序字段(默认倒序)
|
||||
*
|
||||
* @param func 排序字段的 Lambda 表达式
|
||||
* @param <T> 排序字段所属的类型
|
||||
* @return 排序字段
|
||||
*/
|
||||
public static <T> SortingField buildSortingField(Func1<T, ?> func) {
|
||||
return buildSortingField(func, SortingField.ORDER_DESC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建排序字段
|
||||
*
|
||||
* @param func 排序字段的 Lambda 表达式
|
||||
* @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC}
|
||||
* @param <T> 排序字段所属的类型
|
||||
* @return 排序字段
|
||||
*/
|
||||
public static <T> SortingField buildSortingField(Func1<T, ?> func, String order) {
|
||||
Object[] orderTypes = {SortingField.ORDER_ASC, SortingField.ORDER_DESC};
|
||||
Assert.isTrue(ArrayUtil.contains(orderTypes, order), String.format("字段的排序类型只能是%s/%s", orderTypes));
|
||||
|
||||
String fieldName = LambdaUtil.getFieldName(func);
|
||||
return new SortingField(fieldName, order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建默认的排序字段
|
||||
* 如果排序字段为空,则设置排序字段;否则忽略
|
||||
*
|
||||
* @param sortablePageParam 排序分页查询参数
|
||||
* @param func 排序字段的 Lambda 表达式
|
||||
* @param <T> 排序字段所属的类型
|
||||
*/
|
||||
public static <T> void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1<T, ?> func) {
|
||||
if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) {
|
||||
sortablePageParam.setSortingFields(List.of(buildSortingField(func)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,40 +2,90 @@ 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), ProductSpuRespDTO::getId);
|
||||
// 拼接返回
|
||||
return success(BeanUtils.toBean(pageResult, ProductStatisticsRespVO.class,
|
||||
// 拼接商品信息
|
||||
item -> Optional.ofNullable(spuMap.get(item.getSpuId())).ifPresent(spu -> {
|
||||
item.setName(spu.getName());
|
||||
item.setPicUrl(spu.getPicUrl());
|
||||
})));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
@ -67,13 +67,11 @@ 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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
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.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 org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
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 entity 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createProductStatistics(ProductStatisticsDO entity);
|
||||
|
||||
/**
|
||||
* 获得商品统计排行榜分页
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package cn.iocoder.yudao.module.statistics.service.product;
|
||||
|
||||
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.mybatis.core.util.MyBatisUtils;
|
||||
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 jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 商品统计 Service 实现类
|
||||
*
|
||||
* @author owen
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class ProductStatisticsServiceImpl implements ProductStatisticsService {
|
||||
|
||||
@Resource
|
||||
private ProductStatisticsMapper productStatisticsMapper;
|
||||
|
||||
@Override
|
||||
public Long createProductStatistics(ProductStatisticsDO entity) {
|
||||
// 计算 访客支付转化率(百分比)
|
||||
if (entity.getBrowseUserCount() != null && ObjUtil.notEqual(entity.getBrowseUserCount(), 0)) {
|
||||
entity.setBrowseConvertPercent(100 * entity.getOrderPayCount() / entity.getBrowseUserCount());
|
||||
}
|
||||
// 插入
|
||||
productStatisticsMapper.insert(entity);
|
||||
// 返回
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ProductStatisticsDO> getProductStatisticsRankPage(ProductStatisticsReqVO reqVO, SortablePageParam pageParam) {
|
||||
// 默认浏览量倒序
|
||||
MyBatisUtils.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);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@ public interface TradeStatisticsService {
|
|||
*
|
||||
* @return 统计数据对照
|
||||
*/
|
||||
DataComparisonRespVO<TradeTrendSummaryRespVO> getTradeTrendSummaryComparison(
|
||||
DataComparisonRespVO<TradeTrendSummaryRespVO> getTradeStatisticsAnalyse(
|
||||
LocalDateTime beginTime, LocalDateTime endTime);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue