浏览代码

new: 新增统计模块-用户分析

lwh 3 周之前
父节点
当前提交
dfb72b71d8
共有 38 个文件被更改,包括 3201 次插入1 次删除
  1. 10 0
      pig-common/pig-common-bom/pom.xml
  2. 5 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/ServiceNameConstants.java
  3. 23 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/valid/ValueRange.java
  4. 31 0
      pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/valid/ValueRangeValidator.java
  5. 0 1
      pig-marketing/pig-marketing-biz/pom.xml
  6. 61 0
      pig-statistics/pig-statistics-api/pom.xml
  7. 50 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/GetNewUserRetentionDTO.java
  8. 60 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/GetNewUserTrendDTO.java
  9. 45 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/GetVersionDistributionDTO.java
  10. 72 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageActiveDetailDTO.java
  11. 39 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageAllVersionDetailDTO.java
  12. 70 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageNewUserTrendDetailDTO.java
  13. 63 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageRetentionDetailDTO.java
  14. 57 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageSingleVersionDetailDTO.java
  15. 57 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageVersionDistributionDetailDTO.java
  16. 57 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/UserAnalyseQueryBaseDTO.java
  17. 29 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/ActiveUserComposeVO.java
  18. 35 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetActiveUserBaseVO.java
  19. 34 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetNewUserRetentionVO.java
  20. 34 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetNewUserTrendVO.java
  21. 34 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetVersionDistributionVO.java
  22. 68 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageActiveDetailVO.java
  23. 70 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageAllVersionDetailVO.java
  24. 39 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageLaunchDetailVO.java
  25. 39 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageNewUserTrendDetailVO.java
  26. 33 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageRetentionDetailVO.java
  27. 51 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageSingleVersionDetailVO.java
  28. 39 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageVersionDistributionDetailVO.java
  29. 42 0
      pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/TrendBaseVO.java
  30. 150 0
      pig-statistics/pig-statistics-biz/pom.xml
  31. 26 0
      pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/PigStatisticsApplication.java
  32. 195 0
      pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/controller/UserAnalyseController.java
  33. 132 0
      pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/service/UserAnalyseService.java
  34. 1321 0
      pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/service/impl/UserAnalyseServiceImpl.java
  35. 23 0
      pig-statistics/pig-statistics-biz/src/main/resources/application.yml
  36. 69 0
      pig-statistics/pig-statistics-biz/src/main/resources/logback-spring.xml
  37. 37 0
      pig-statistics/pom.xml
  38. 1 0
      pom.xml

+ 10 - 0
pig-common/pig-common-bom/pom.xml

@@ -99,6 +99,16 @@
                 <artifactId>pig-upms-api</artifactId>
                 <version>${revision}</version>
             </dependency>
+			<dependency>
+				<groupId>com.pig4cloud</groupId>
+				<artifactId>pig-marketing-api</artifactId>
+				<version>${revision}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.pig4cloud</groupId>
+				<artifactId>pig-statistics-api</artifactId>
+				<version>${revision}</version>
+			</dependency>
             <dependency>
                 <groupId>com.mysql</groupId>
                 <artifactId>mysql-connector-j</artifactId>

+ 5 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/ServiceNameConstants.java

@@ -37,4 +37,9 @@ public interface ServiceNameConstants {
 	 */
 	String MARKETING_SERVICE = "pig-marketing-biz";
 
+	/**
+	 * 统计模块
+	 */
+	String STATISTICS_SERVICE = "pig-statistics-biz";
+
 }

+ 23 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/valid/ValueRange.java

@@ -0,0 +1,23 @@
+package com.pig4cloud.pig.marketing.api.valid;
+
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = ValueRangeValidator.class)
+public @interface ValueRange {
+    // 允许的值
+    int[] values();
+    
+    // 校验失败提示信息
+    String message() default "值不在允许的范围内";
+    
+    Class<?>[] groups() default {};
+    
+    Class<? extends Payload>[] payload() default {};
+}

+ 31 - 0
pig-marketing/pig-marketing-api/src/main/java/com/pig4cloud/pig/marketing/api/valid/ValueRangeValidator.java

@@ -0,0 +1,31 @@
+package com.pig4cloud.pig.marketing.api.valid;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ValueRangeValidator implements ConstraintValidator<ValueRange, Integer> {
+    private Set<Integer> allowedValues;
+    
+    @Override
+    public void initialize(ValueRange constraintAnnotation) {
+        // 初始化允许的值集合
+        int[] values = constraintAnnotation.values();
+        allowedValues = new HashSet<>();
+        for (int value : values) {
+            allowedValues.add(value);
+        }
+    }
+    
+    @Override
+    public boolean isValid(Integer value, ConstraintValidatorContext context) {
+        // 如果值为null,由@NotNull注解来处理,这里不做判断
+        if (value == null) {
+            return true;
+        }
+        // 检查值是否在允许的范围内
+        return allowedValues.contains(value);
+    }
+}

+ 0 - 1
pig-marketing/pig-marketing-biz/pom.xml

@@ -34,7 +34,6 @@
 		<dependency>
 			<groupId>com.pig4cloud</groupId>
 			<artifactId>pig-marketing-api</artifactId>
-			<version>${revision}</version>
 		</dependency>
 		<!--UPMS接口模块-->
 		<dependency>

+ 61 - 0
pig-statistics/pig-statistics-api/pom.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.pig4cloud</groupId>
+		<artifactId>pig-statistics</artifactId>
+		<version>${revision}</version>
+	</parent>
+
+	<artifactId>pig-statistics-api</artifactId>
+
+	<packaging>jar</packaging>
+
+	<description>pig 统计公共api模块</description>
+
+	<dependencies>
+		<!--core 工具类-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-core</artifactId>
+		</dependency>
+		<!--feign 注解依赖-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-feign</artifactId>
+		</dependency>
+		<!--mybatis 依赖-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-mybatis</artifactId>
+		</dependency>
+		<!-- excel 导入导出 -->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-excel</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+			<version>4.0.1</version>
+			<scope>compile</scope>
+		</dependency>
+	</dependencies>
+</project>

+ 50 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/GetNewUserRetentionDTO.java

@@ -0,0 +1,50 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 查询次日留存率入参
+ */
+@Data
+@Schema(description = "查询次日留存率入参")
+public class GetNewUserRetentionDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 渠道
+	 */
+	@Schema(description = "渠道", example = "[\"App Store\"]")
+	private List<String> channel;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+}

+ 60 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/GetNewUserTrendDTO.java

@@ -0,0 +1,60 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 获取新增趋势入参
+ */
+
+@Data
+@Schema(description = "获取新增趋势入参")
+public class GetNewUserTrendDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 渠道
+	 */
+	@Schema(description = "渠道", example = "[\"App Store\"]")
+	private List<String> channel;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+	/**
+	 * 时间单位
+	 */
+	@NotBlank(message = "时间单位不能为空")
+	@Schema(description = "时间单位,hour-小时,day-天,week-周,month-月", example = "day")
+	private String timeUnit;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+
+}

+ 45 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/GetVersionDistributionDTO.java

@@ -0,0 +1,45 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 获取版本用户来源入参
+ */
+@Data
+@Schema(description = "获取版本用户来源入参")
+public class GetVersionDistributionDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 查询条件名称
+	 */
+	@Schema(description = "查询条件名称")
+	private String conditionName;
+
+	/**
+	 * 查询条件值
+	 */
+	@Schema(description = "查询条件值")
+	private String conditionValue;
+
+	/**
+	 * 时间单位
+	 */
+	@Schema(description = "时间单位")
+	private String timeUnit;
+
+	/**
+	 * 类型
+	 */
+	@Schema(description = "类型,upgradeChannel-升级用户渠道",example = "upgradeChannel")
+	private String type;
+}

+ 72 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageActiveDetailDTO.java

@@ -0,0 +1,72 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 分页查询活跃详情入参
+ */
+
+@Data
+@Schema(description = "分页查询活跃详情入参")
+public class PageActiveDetailDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 渠道
+	 */
+	@Schema(description = "渠道", example = "[\"App Store\"]")
+	private List<String> channel;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+	/**
+	 * 时间单位
+	 */
+	@NotBlank(message = "时间单位不能为空")
+	@Schema(description = "时间单位,hour-小时,day-天,week-周,month-月", example = "day")
+	private String timeUnit;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+
+}

+ 39 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageAllVersionDetailDTO.java

@@ -0,0 +1,39 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询全部版本详情入参
+ */
+@Data
+@Schema(description = "分页查询全部版本详情入参")
+public class PageAllVersionDetailDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+
+	/**
+	 * 查询日期
+	 */
+	@Schema(description = "查询日期,0-今日,1-昨日")
+	private Integer queryDate;
+}

+ 70 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageNewUserTrendDetailDTO.java

@@ -0,0 +1,70 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 获取新增趋势详情
+ */
+@Data
+@Schema(description = "获取新增趋势详情入参")
+public class PageNewUserTrendDetailDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 渠道
+	 */
+	@Schema(description = "渠道", example = "[\"App Store\"]")
+	private List<String> channel;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+	/**
+	 * 时间单位
+	 */
+	@NotBlank(message = "时间单位不能为空")
+	@Schema(description = "时间单位,hour-小时,day-天,week-周,month-月", example = "day")
+	private String timeUnit;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+}

+ 63 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageRetentionDetailDTO.java

@@ -0,0 +1,63 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 分页查询次日留存率详情
+ */
+@Data
+@Schema(description = "分页查询次日留存率详情入参")
+public class PageRetentionDetailDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 渠道
+	 */
+	@Schema(description = "渠道", example = "[\"App Store\"]")
+	private List<String> channel;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+
+}

+ 57 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageSingleVersionDetailDTO.java

@@ -0,0 +1,57 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询单版本详情入参
+ */
+@Data
+@Schema(description = "分页查询单版本详情入参")
+public class PageSingleVersionDetailDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+}

+ 57 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/PageVersionDistributionDetailDTO.java

@@ -0,0 +1,57 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询版本用户来源详情
+ */
+@Data
+@Schema(description = "分页查询版本用户来源详情")
+public class PageVersionDistributionDetailDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 查询条件名称
+	 */
+	@Schema(description = "查询条件名称")
+	private String conditionName;
+
+	/**
+	 * 查询条件值
+	 */
+	@Schema(description = "查询条件值")
+	private String conditionValue;
+
+	/**
+	 * 时间单位
+	 */
+	@Schema(description = "时间单位")
+	private String timeUnit;
+
+	/**
+	 * 类型
+	 */
+	@Schema(description = "类型,upgradeChannel-升级用户渠道",example = "upgradeChannel")
+	private String type;
+
+	/**
+	 * 每页显示条数,默认 10
+	 */
+	@Schema(description = "每页显示条数")
+	private long size = 10;
+
+	/**
+	 * 当前页
+	 */
+	@Schema(description = "当前页")
+	private long current = 1;
+}

+ 57 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/dto/user/UserAnalyseQueryBaseDTO.java

@@ -0,0 +1,57 @@
+package com.pig4cloud.pig.statistics.api.dto.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 用户分析基本查询条件
+ */
+@Data
+public class UserAnalyseQueryBaseDTO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 渠道
+	 */
+	@Schema(description = "渠道", example = "[\"App Store\"]")
+	private List<String> channel;
+
+	/**
+	 * 开始时间
+	 */
+	@NotNull(message = "开始时间不能为空")
+	@Schema(description = "开始时间", example = "2025-07-29")
+	private LocalDate fromDate;
+
+	/**
+	 * 结束时间
+	 */
+	@NotNull(message = "结束时间不能为空")
+	@Schema(description = "结束时间", example = "2025-08-05")
+	private LocalDate toDate;
+
+	/**
+	 * 时间单位
+	 */
+	@NotBlank(message = "时间单位不能为空")
+	@Schema(description = "时间单位,hour-小时,day-天,week-周,month-月", example = "day")
+	private String timeUnit;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本", example = "[\"1.0.0\"]")
+	private List<String> version;
+}

+ 29 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/ActiveUserComposeVO.java

@@ -0,0 +1,29 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 活跃用户组成
+ */
+@Data
+public class ActiveUserComposeVO {
+
+	/**
+	 * 名称
+	 */
+	@Schema(description = "名称", example = "新增用户")
+	private String name;
+
+	/**
+	 * 数量
+	 */
+	@Schema(description = "数量")
+	private List<Double> data;
+
+}

+ 35 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetActiveUserBaseVO.java

@@ -0,0 +1,35 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 获取活跃构成出参
+ */
+
+@Data
+@Schema(description = "获取活跃构成出参")
+public class GetActiveUserBaseVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private List<String> dates;
+
+	/**
+	 * 数据
+	 */
+	@Schema(description = "数据")
+	private List<ActiveUserComposeVO> items;
+}

+ 34 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetNewUserRetentionVO.java

@@ -0,0 +1,34 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 查询次日留存率出参
+ */
+@Data
+@Schema(description = "查询次日留存率出参")
+public class GetNewUserRetentionVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private List<String> dates;
+
+	/**
+	 * 数量
+	 */
+	@Schema(description = "数量")
+	private List<Double> retention;
+}

+ 34 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetNewUserTrendVO.java

@@ -0,0 +1,34 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 获取新增趋势出参
+ */
+@Data
+@Schema(description = "获取新增趋势出参")
+public class GetNewUserTrendVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private List<String> dates;
+
+	/**
+	 * 数量
+	 */
+	@Schema(description = "数量")
+	private List<TrendBaseVO> items;
+}

+ 34 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/GetVersionDistributionVO.java

@@ -0,0 +1,34 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 获取版本用户来源出参
+ */
+@Data
+@Schema(description = "获取版本用户来源出参")
+public class GetVersionDistributionVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 名称
+	 */
+	@Schema(description = "名称")
+	private String name;
+
+	/**
+	 * 数量
+	 */
+	@Schema(description = "数量")
+	private Integer value;
+
+}

+ 68 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageActiveDetailVO.java

@@ -0,0 +1,68 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: TODO
+ */
+
+@Data
+@Schema(description = "分页查询活跃详情出参")
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class PageActiveDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private String date;
+
+	/**
+	 * 活跃用户数
+	 */
+	@Schema(description = "活跃用户数")
+	private Integer activeUser;
+
+	/**
+	 * 新用户占比
+	 */
+	@Schema(description = "新用户占比")
+	private Double newUserRate;
+
+
+	/***********************************单位为天时
+	 * DAU/过去7日活跃用户
+	 */
+	@Schema(description = "DAU/过去7日活跃用户")
+	private Double wauRate;
+
+	/***********************************单位为天时
+	 * DAU/过去30日活跃用户
+	 */
+	@Schema(description = "DAU/过去30日活跃用户")
+	private Double mauRate;
+
+	/***********************************单位为周时
+	 * 用户周活跃率
+	 */
+	@Schema(description = "用户周活跃率")
+	private Double weekActiveUserRate;
+
+
+	/***********************************单位为月时
+	 * 用户月活跃率
+	 */
+	@Schema(description = "用户月活跃率")
+	private Double monthActiveUserRate;
+}

+ 70 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageAllVersionDetailVO.java

@@ -0,0 +1,70 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询全部版本详情出参
+ */
+@Data
+@Schema(description = "分页查询全部版本详情出参")
+public class PageAllVersionDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 活跃用户占比
+	 */
+	@Schema(description = "活跃用户占比")
+	private Double activeUserRate;
+
+	/**
+	 * 活跃用户
+	 */
+	@Schema(description = "活跃用户")
+	private Integer activeUser;
+
+	/**
+	 * 新增用户
+	 */
+	@Schema(description = "新增用户")
+	private Integer newUser;
+
+	/**
+	 * 累计用户
+	 */
+	@Schema(description = "累计用户")
+	private Integer totalUser;
+
+	/**
+	 * 累计用户占比
+	 */
+	@Schema(description = "累计用户占比")
+	private Double totalUserRate;
+
+	/**
+	 * 升级用户
+	 */
+	@Schema(description = "升级用户")
+	private Integer upgradeUser;
+
+	/**
+	 * 启动次数
+	 */
+	@Schema(description = "启动次数")
+	private Integer launch;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本")
+	private String version;
+
+}

+ 39 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageLaunchDetailVO.java

@@ -0,0 +1,39 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询启动次数详情出参
+ */
+@Data
+@Schema(description = "分页查询启动次数详情出参")
+public class PageLaunchDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private String date;
+
+	/**
+	 * 启动次数占比
+	 */
+	@Schema(description = "启动次数占比")
+	private Double launchRate;
+
+	/**
+	 * 启动次数
+	 */
+	@Schema(description = "启动次数")
+	private Integer launch;
+}

+ 39 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageNewUserTrendDetailVO.java

@@ -0,0 +1,39 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 分页查询新增趋势详情出参
+ */
+@Data
+@Schema(description = "分页查询新增趋势详情出参")
+public class PageNewUserTrendDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private String date;
+
+	/**
+	 * 新增用户数
+	 */
+	@Schema(description = "新增用户数")
+	private Integer newUser;
+
+	/**
+	 * 新增用户占比
+	 */
+	@Schema(description = "新增用户占比")
+	private Double newUserRate;
+}

+ 33 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageRetentionDetailVO.java

@@ -0,0 +1,33 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 分页查询次日留存率详情出参
+ */
+@Data
+@Schema(description = "分页查询次日留存率详情出参")
+public class PageRetentionDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private String date;
+
+	/**
+	 * 留存率
+	 */
+	@Schema(description = "留存率")
+	private Double retention;
+}

+ 51 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageSingleVersionDetailVO.java

@@ -0,0 +1,51 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询单版本详情出参
+ */
+@Data
+@Schema(description = "分页查询单版本详情出参")
+public class PageSingleVersionDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 日期
+	 */
+	@Schema(description = "日期")
+	private String date;
+
+	/**
+	 * 活跃用户
+	 */
+	@Schema(description = "活跃用户")
+	private Integer activeUser;
+
+	/**
+	 * 新增用户
+	 */
+	@Schema(description = "新增用户")
+	private Integer newUser;
+
+	/**
+	 * 升级用户
+	 */
+	@Schema(description = "升级用户")
+	private Integer upgradeUser;
+
+	/**
+	 * 启动次数
+	 */
+	@Schema(description = "启动次数")
+	private Integer launch;
+}

+ 39 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageVersionDistributionDetailVO.java

@@ -0,0 +1,39 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-07
+ * @description: 分页查询版本用户来源详情
+ */
+@Data
+@Schema(description = "分页查询版本用户来源详情")
+public class PageVersionDistributionDetailVO implements Serializable {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 占比率
+	 */
+	@Schema(description = "占比率")
+	private Double rate;
+
+	/**
+	 * 名称
+	 */
+	@Schema(description = "名称")
+	private String name;
+
+	/**
+	 * 数量
+	 */
+	@Schema(description = "数量")
+	private Integer value;
+}

+ 42 - 0
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/TrendBaseVO.java

@@ -0,0 +1,42 @@
+package com.pig4cloud.pig.statistics.api.vo.user;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-08
+ * @description: 趋势数据出参
+ */
+@Data
+@Schema(description = "趋势数据出参")
+public class TrendBaseVO implements Serializable {
+
+	/**
+	 * 数量
+	 */
+	@Schema(description = "数量")
+	private List<Integer> data;
+
+	/**
+	 * 名称
+	 */
+	@Schema(description = "名称")
+	private String name;
+
+	/**
+	 * 版本
+	 */
+	@Schema(description = "版本")
+	private String version;
+
+	/**
+	 * key
+	 */
+	@Schema(description = "key")
+	private String key;
+}

+ 150 - 0
pig-statistics/pig-statistics-biz/pom.xml

@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.pig4cloud</groupId>
+		<artifactId>pig-statistics</artifactId>
+		<version>${revision}</version>
+	</parent>
+
+	<artifactId>pig-statistics-biz</artifactId>
+
+	<packaging>jar</packaging>
+
+	<description>pig 统计业务处理模块</description>
+
+	<dependencies>
+		<!--statistics api、model 模块-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-statistics-api</artifactId>
+		</dependency>
+		<!--UPMS接口模块-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-upms-api</artifactId>
+		</dependency>
+		<!--文件管理-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-oss</artifactId>
+		</dependency>
+		<!--feign 调用-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-feign</artifactId>
+		</dependency>
+		<!--安全模块-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-security</artifactId>
+		</dependency>
+		<!--日志处理-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-log</artifactId>
+		</dependency>
+		<!--接口文档-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-swagger</artifactId>
+		</dependency>
+		<!-- orm 模块-->
+		<dependency>
+			<groupId>com.baomidou</groupId>
+			<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.mysql</groupId>
+			<artifactId>mysql-connector-j</artifactId>
+		</dependency>
+		<!--注册中心客户端-->
+		<dependency>
+			<groupId>com.alibaba.cloud</groupId>
+			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+		</dependency>
+		<!--配置中心客户端-->
+		<dependency>
+			<groupId>com.alibaba.cloud</groupId>
+			<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+		</dependency>
+		<!-- 短信下发 -->
+		<dependency>
+			<groupId>org.dromara.sms4j</groupId>
+			<artifactId>sms4j-spring-boot-starter</artifactId>
+		</dependency>
+		<!--xss 过滤-->
+		<dependency>
+			<groupId>com.pig4cloud</groupId>
+			<artifactId>pig-common-xss</artifactId>
+		</dependency>
+		<!--undertow容器-->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-undertow</artifactId>
+		</dependency>
+	</dependencies>
+
+	<profiles>
+		<profile>
+			<id>boot</id>
+		</profile>
+		<profile>
+			<id>cloud</id>
+			<activation>
+				<!-- 默认环境 -->
+				<activeByDefault>true</activeByDefault>
+			</activation>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.springframework.boot</groupId>
+						<artifactId>spring-boot-maven-plugin</artifactId>
+					</plugin>
+					<plugin>
+						<groupId>io.fabric8</groupId>
+						<artifactId>docker-maven-plugin</artifactId>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+	</profiles>
+
+	<build>
+		<resources>
+			<resource>
+				<directory>src/main/resources</directory>
+				<filtering>true</filtering>
+				<excludes>
+					<exclude>**/*.xlsx</exclude>
+					<exclude>**/*.xls</exclude>
+				</excludes>
+			</resource>
+			<resource>
+				<directory>src/main/resources</directory>
+				<filtering>false</filtering>
+				<includes>
+					<include>**/*.xlsx</include>
+					<include>**/*.xls</include>
+				</includes>
+			</resource>
+		</resources>
+	</build>
+</project>

+ 26 - 0
pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/PigStatisticsApplication.java

@@ -0,0 +1,26 @@
+package com.pig4cloud.pig.statistics;
+
+
+import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;
+import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;
+import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-05
+ * @description: 统计模块
+ */
+@EnablePigDoc(value = "stats")
+@EnablePigFeignClients
+@EnablePigResourceServer
+@EnableDiscoveryClient
+@SpringBootApplication
+public class PigStatisticsApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(PigStatisticsApplication.class, args);
+	}
+}

+ 195 - 0
pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/controller/UserAnalyseController.java

@@ -0,0 +1,195 @@
+package com.pig4cloud.pig.statistics.controller;
+
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.pig.statistics.api.dto.user.*;
+import com.pig4cloud.pig.statistics.api.vo.user.*;
+import com.pig4cloud.pig.statistics.service.UserAnalyseService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 用户分析
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/user")
+@Tag(description = "userAnalyse", name = "用户分析")
+@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
+public class UserAnalyseController {
+
+	private final UserAnalyseService userAnalyseService;
+
+/***************************************** 新增用户 *****************************************/
+
+	@PostMapping("/new/trend")
+	@Operation(summary = "查询新增趋势")
+	public R getNewUserTrend(@Valid @RequestBody GetNewUserTrendDTO reqDto) {
+		GetNewUserTrendVO result = userAnalyseService.getNewUserTrend(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/new/detail")
+	@Operation(summary = "分页查询新增趋势详情")
+	public R pageNewUserTrendDetail(@Valid @RequestBody PageNewUserTrendDetailDTO reqDto) {
+		Page result = userAnalyseService.pageNewUserTrendDetail(reqDto);
+		return R.ok(result);
+	}
+
+	@GetMapping("/new/trend/export")
+	@Operation(summary = "导出新增趋势")
+	public R exportNewUserTrend() {
+		return R.ok();
+	}
+
+	@PostMapping("/new/retention")
+	@Operation(summary = "查询次日留存率")
+	public R getNewUserRetention(@Valid @RequestBody GetNewUserRetentionDTO reqDto) {
+		GetNewUserRetentionVO result = userAnalyseService.getNewUserRetention(reqDto);
+		return R.ok(result);
+	}
+	@PostMapping("/retention/detail")
+	@Operation(summary = "分页查询次日留存率详情")
+	public R pageNewUserRetentionDetail(@Valid @RequestBody PageRetentionDetailDTO reqDto) {
+		Page result = userAnalyseService.pageNewUserRetentionDetail(reqDto);
+		return R.ok(result);
+	}
+
+	@GetMapping("/new/retention/export")
+	@Operation(summary = "导出次日留存率")
+	public R exportNewUserRetention() {
+		return R.ok();
+	}
+
+/***************************************** 活跃用户 *****************************************/
+
+	@PostMapping("/active/trend")
+	@Operation(summary = "查询活跃趋势")
+	public R getActiveTrend(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		GetNewUserTrendVO result = userAnalyseService.getActiveTrend(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/active/compose")
+	@Operation(summary = "查询活跃构成")
+	public R getActiveCompose(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		GetActiveUserBaseVO result = userAnalyseService.getActiveCompose(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/active/viscosity")
+	@Operation(summary = "查询活跃粘度")
+	public R getActiveViscosity(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		GetActiveUserBaseVO result = userAnalyseService.getActiveViscosity(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/active/weekrate")
+	@Operation(summary = "查询周活跃率")
+	public R getActiveWeekrate(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		GetActiveUserBaseVO result = userAnalyseService.getActiveWeekrate(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/active/monthrate")
+	@Operation(summary = "查询月活跃率")
+	public R getActiveMonthrate(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		GetActiveUserBaseVO result = userAnalyseService.getActiveMonthrate(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/active/detail")
+	@Operation(summary = "分页查询活跃详情")
+	public R pageActiveDetail(@Valid @RequestBody PageActiveDetailDTO reqDto) {
+		Page result = userAnalyseService.pageActiveDetail(reqDto);
+		return R.ok(result);
+	}
+
+	@GetMapping("/active/export")
+	@Operation(summary = "导出活跃详情")
+	public R exportActiveDetail() {
+		return R.ok();
+	}
+
+/***************************************** 启动次数 *****************************************/
+	@PostMapping("/launch/trend")
+	@Operation(summary = "查询启动次数趋势")
+	public R getLaunchTrend(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		GetNewUserTrendVO result = userAnalyseService.getLaunchTrend(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/launch/detail")
+	@Operation(summary = "分页查询启动次数详情")
+	public R pageLaunchDetail(@Valid @RequestBody PageActiveDetailDTO reqDto) {
+		Page result = userAnalyseService.pageLaunchDetail(reqDto);
+		return R.ok(result);
+	}
+
+	@GetMapping("/launch/export")
+	@Operation(summary = "导出启动次数详情")
+	public R exportLaunchDetail() {
+		return R.ok();
+	}
+
+/***************************************** 版本分布 *****************************************/
+
+	@PostMapping("/version/detail")
+	@Operation(summary = "分页查询全部版本详情")
+	public R pageAllVersionDetail(@Valid @RequestBody PageAllVersionDetailDTO reqDto) {
+		Page result = userAnalyseService.pageAllVersionDetail(reqDto);
+		return R.ok(result);
+	}
+
+	@GetMapping("/version/export")
+	@Operation(summary = "导出全部版本详情")
+	public R exportAllVersionDetail() {
+		return R.ok();
+	}
+
+	@PostMapping("/version/single/detail")
+	@Operation(summary = "分页查询单版本详情")
+	public R pageSingleVersionDetail(@Valid @RequestBody PageSingleVersionDetailDTO reqDto) {
+		Page result = userAnalyseService.pageSingleVersionDetail(reqDto);
+		return R.ok(result);
+	}
+
+	@GetMapping("/version/single/export")
+	@Operation(summary = "导出单版本详情")
+	public R exportSingleVersionDetail() {
+		return R.ok();
+	}
+
+	@PostMapping("/version/distribution")
+	@Operation(summary = "查询版本用户来源")
+	public R getVersionDistribution(@Valid @RequestBody GetVersionDistributionDTO reqDto) {
+		List<GetVersionDistributionVO> result = userAnalyseService.getVersionDistribution(reqDto);
+		return R.ok(result);
+	}
+
+	@PostMapping("/version/distribution/detail")
+	@Operation(summary = "分页查询版本用户来源详情")
+	public R pageVersionDistributionDetail(@Valid @RequestBody PageVersionDistributionDetailDTO reqDto) {
+		Page result = userAnalyseService.pageVersionDistributionDetail(reqDto);
+		return R.ok(result);
+	}
+
+
+	@GetMapping("/version/distribution/export")
+	@Operation(summary = "导出版本用户来源详情")
+	public R exportVersionDistributionDetail() {
+		return R.ok();
+	}
+
+}

+ 132 - 0
pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/service/UserAnalyseService.java

@@ -0,0 +1,132 @@
+package com.pig4cloud.pig.statistics.service;
+
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.statistics.api.dto.user.*;
+import com.pig4cloud.pig.statistics.api.vo.user.*;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 用户分析Service
+ */
+
+public interface UserAnalyseService {
+
+	/**
+	 * 获取新增趋势
+	 * @param reqDto 请求参数
+	 * @return GetNewUserTrendVO
+	 */
+	GetNewUserTrendVO getNewUserTrend(@Valid GetNewUserTrendDTO reqDto);
+
+	/**
+	 * 分页查询新增趋势详情
+	 * @param reqDto 请求参数
+	 * @return Page
+	 */
+	Page pageNewUserTrendDetail(@Valid PageNewUserTrendDetailDTO reqDto);
+
+	/**
+	 * 查询次日留存率
+	 * @param reqDto 查询次日留存率入参
+	 * @return GetNewUserRetentionVO
+	 */
+	GetNewUserRetentionVO getNewUserRetention(@Valid GetNewUserRetentionDTO reqDto);
+
+	/**
+	 * 分页查询次日留存率详情
+	 * @param reqDto 分页查询次日留存率详情入参
+	 * @return Page
+	 */
+	Page pageNewUserRetentionDetail(@Valid PageRetentionDetailDTO reqDto);
+
+
+	/************************************** 活跃用户 *****************************************
+	 * 获取活跃趋势
+	 * @param reqDto 入参
+	 * @return GetNewUserTrendVO
+	 */
+	GetNewUserTrendVO getActiveTrend(@Valid UserAnalyseQueryBaseDTO reqDto);
+
+	/**
+	 * 获取活跃构成
+	 * @param reqDto 入参
+	 * @return GetActiveComposeVO
+	 */
+	GetActiveUserBaseVO getActiveCompose(@Valid UserAnalyseQueryBaseDTO reqDto);
+
+	/**
+	 * 获取活跃粘度
+	 * @param reqDto 入参
+	 * @return GetActiveUserBaseVO
+	 */
+	GetActiveUserBaseVO getActiveViscosity(@Valid UserAnalyseQueryBaseDTO reqDto);
+
+	/**
+	 * 查询周活跃率
+	 * @param reqDto 入参
+	 * @return GetActiveUserBaseVO
+	 */
+	GetActiveUserBaseVO getActiveWeekrate(@Valid UserAnalyseQueryBaseDTO reqDto);
+
+
+	/**
+	 * 查询月活跃率
+	 * @param reqDto 入参
+	 * @return GetActiveUserBaseVO
+	 */
+	GetActiveUserBaseVO getActiveMonthrate(@Valid UserAnalyseQueryBaseDTO reqDto);
+
+	/**
+	 * 分页查询活跃详情
+	 * @param reqDto 入参
+	 * @return Page
+	 */
+	Page pageActiveDetail(@Valid PageActiveDetailDTO reqDto);
+
+	/************************************** 启动次数 *****************************************
+	 * 查询启动次数趋势
+	 * @param reqDto 入参
+	 * @return GetNewUserTrendVO
+	 */
+	GetNewUserTrendVO getLaunchTrend(@Valid UserAnalyseQueryBaseDTO reqDto);
+
+	/**
+	 * 分页查询启动次数详情
+	 * @param reqDto 入参
+	 * @return Page
+	 */
+	Page pageLaunchDetail(@Valid PageActiveDetailDTO reqDto);
+
+	/************************************** 版本分布 *****************************************
+	 * 分页查询全部版本详情
+	 * @param reqDto 入参
+	 * @return Page
+	 */
+	Page pageAllVersionDetail(@Valid PageAllVersionDetailDTO reqDto);
+
+	/**
+	 * 分页查询单版本详情
+	 * @param reqDto 入参
+	 * @return Page
+	 */
+	Page pageSingleVersionDetail(@Valid PageSingleVersionDetailDTO reqDto);
+
+	/**
+	 * 查询版本用户来源
+	 * @param reqDto 入参
+	 * @return GetVersionDistributionVO
+	 */
+	List<GetVersionDistributionVO> getVersionDistribution(@Valid GetVersionDistributionDTO reqDto);
+
+	/**
+	 * 分页查询版本用户来源详情
+	 * @param reqDto 入参
+	 * @return Page
+	 */
+	Page pageVersionDistributionDetail(@Valid PageVersionDistributionDetailDTO reqDto);
+}

+ 1321 - 0
pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/service/impl/UserAnalyseServiceImpl.java

@@ -0,0 +1,1321 @@
+package com.pig4cloud.pig.statistics.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.statistics.api.dto.user.*;
+import com.pig4cloud.pig.statistics.api.vo.user.*;
+import com.pig4cloud.pig.statistics.service.UserAnalyseService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * @author: lwh
+ * @date: 2025-08-06
+ * @description: 用户分析Service实现类
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class UserAnalyseServiceImpl implements UserAnalyseService {
+
+
+	/**
+	 * 获取新增趋势
+	 * @param reqDto 请求参数
+	 * @return GetNewUserTrendVO
+	 */
+	@Override
+	public GetNewUserTrendVO getNewUserTrend(GetNewUserTrendDTO reqDto) {
+		GetNewUserTrendVO result = new GetNewUserTrendVO();
+		List<String> dates = new ArrayList<>();
+		String timeUnit = reqDto.getTimeUnit();
+		LocalDate fromDate = reqDto.getFromDate();
+		LocalDate toDate = reqDto.getToDate();
+
+		// 生成日期列表
+		switch (timeUnit) {
+			case "hour":
+				DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00");
+				LocalDateTime hourStart = fromDate.atStartOfDay();
+				LocalDateTime hourEnd = toDate.atTime(23, 0);
+				while (!hourStart.isAfter(hourEnd)) {
+					dates.add(hourStart.format(hourFormatter));
+					hourStart = hourStart.plusHours(1);
+				}
+				break;
+			case "day":
+				DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				LocalDate dayStart = fromDate;
+				while (!dayStart.isAfter(toDate)) {
+					dates.add(dayStart.format(dayFormatter));
+					dayStart = dayStart.plusDays(1);
+				}
+				break;
+			case "week":
+				DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				// 计算起始日期后的第一个周日
+				LocalDate currentWeek = fromDate;
+				DayOfWeek dayOfWeek = currentWeek.getDayOfWeek();
+				int daysToSunday = 7 - dayOfWeek.getValue(); // 周日的DayOfWeek值为7
+				LocalDate firstSunday = currentWeek.plusDays(daysToSunday);
+
+				while (!firstSunday.isAfter(toDate)) {
+					dates.add(firstSunday.format(weekFormatter));
+					firstSunday = firstSunday.plusWeeks(1);
+				}
+				break;
+			case "month":
+				DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				// 计算范围内的每月1号
+				LocalDate currentMonthFirstDay = LocalDate.of(fromDate.getYear(), fromDate.getMonth(), 1);
+				// 如果当月1号在起始日期之前,则取下个月1号
+				if (currentMonthFirstDay.isBefore(fromDate)) {
+					currentMonthFirstDay = currentMonthFirstDay.plusMonths(1);
+				}
+
+				while (!currentMonthFirstDay.isAfter(toDate)) {
+					dates.add(currentMonthFirstDay.format(monthFormatter));
+					currentMonthFirstDay = currentMonthFirstDay.plusMonths(1);
+				}
+				break;
+			default:
+				// 默认按天处理
+				DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				LocalDate defaultStart = fromDate;
+				while (!defaultStart.isAfter(toDate)) {
+					dates.add(defaultStart.format(defaultFormatter));
+					defaultStart = defaultStart.plusDays(1);
+				}
+		}
+
+		// 生成items数据
+		List<TrendBaseVO> items = new ArrayList<>();
+		List<String> versions = reqDto.getVersion();
+		// 若未指定版本,使用默认版本列表
+		if (versions == null || versions.isEmpty()) {
+			versions = Arrays.asList("1.0", "5.1");
+		}
+
+		Random random = new Random();
+		for (String version : versions) {
+			TrendBaseVO trend = new TrendBaseVO();
+			trend.setName("新增用户");
+			trend.setVersion(version);
+			trend.setKey("newUser");
+
+			// 生成与日期数量匹配的随机数据
+			List<Integer> data = new ArrayList<>(dates.size());
+			for (int i = 0; i < dates.size(); i++) {
+				// 生成100-1000之间的随机数,模拟用户数量
+				data.add(random.nextInt(901) + 100);
+			}
+			trend.setData(data);
+			items.add(trend);
+		}
+
+		result.setDates(dates);
+		result.setItems(items);
+		return result;
+	}
+
+	/**
+	 * 分页查询新增趋势详情
+	 * @param reqDto 请求参数
+	 * @return Page
+	 */
+	@Override
+	public Page<PageNewUserTrendDetailVO> pageNewUserTrendDetail(PageNewUserTrendDetailDTO reqDto) {
+		// 创建分页对象
+		Page<PageNewUserTrendDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageNewUserTrendDetailVO> result = new ArrayList<>();
+		List<String> timeLabels = new ArrayList<>();
+		String timeUnit = reqDto.getTimeUnit();
+		LocalDate startDate = reqDto.getFromDate();
+		LocalDate endDate = reqDto.getToDate();
+		Random random = new Random();
+
+		// 1. 根据时间单位生成对应的时间标签列表
+		switch (timeUnit) {
+			case "hour":
+				// 按小时生成:2025-08-05 23:00
+				LocalDateTime startHour = startDate.atStartOfDay();
+				LocalDateTime endHour = endDate.atTime(23, 0);
+				LocalDateTime currentHour = startHour;
+				DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+
+				while (!currentHour.isAfter(endHour)) {
+					timeLabels.add(currentHour.format(hourFormatter));
+					currentHour = currentHour.plusHours(1);
+				}
+				break;
+
+			case "day":
+				// 按天生成:2025-08-03
+				LocalDate currentDay = startDate;
+				DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+				while (!currentDay.isAfter(endDate)) {
+					timeLabels.add(currentDay.format(dayFormatter));
+					currentDay = currentDay.plusDays(1);
+				}
+				break;
+
+			case "week":
+				// 按周生成:2025/07/27~2025/08/02
+				LocalDate currentWeekStart = startDate;
+				DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
+
+				while (!currentWeekStart.isAfter(endDate)) {
+					LocalDate currentWeekEnd = currentWeekStart.plusDays(6);
+					if (currentWeekEnd.isAfter(endDate)) {
+						currentWeekEnd = endDate;
+					}
+					timeLabels.add(currentWeekStart.format(weekFormatter) + "~" + currentWeekEnd.format(weekFormatter));
+					currentWeekStart = currentWeekStart.plusWeeks(1);
+				}
+				break;
+
+			case "month":
+				// 按月生成:2025/07/01~2025/07/31
+				LocalDate currentMonthStart = startDate;
+				DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
+
+				while (!currentMonthStart.isAfter(endDate)) {
+					LocalDate currentMonthEnd = currentMonthStart.plusMonths(1).minusDays(1);
+					if (currentMonthEnd.isAfter(endDate)) {
+						currentMonthEnd = endDate;
+					}
+					timeLabels.add(currentMonthStart.withDayOfMonth(1).format(monthFormatter) + "~" + currentMonthEnd.format(monthFormatter));
+					currentMonthStart = currentMonthStart.plusMonths(1);
+				}
+				break;
+
+			default:
+				throw new IllegalArgumentException("不支持的时间单位: " + timeUnit);
+		}
+
+		// 2. 计算总记录数并设置到分页对象
+		long total = timeLabels.size();
+		page.setTotal(total);
+
+		// 3. 计算分页范围
+		long startIndex = (reqDto.getCurrent() - 1) * reqDto.getSize();
+		long endIndex = Math.min(startIndex + reqDto.getSize(), total);
+
+		// 4. 生成当前页的模拟数据
+		if (startIndex < total) {
+			double baseRate = 0.05; // 基础占比基数(5%)
+
+			for (long i = startIndex; i < endIndex; i++) {
+				PageNewUserTrendDetailVO vo = new PageNewUserTrendDetailVO();
+				vo.setDate(timeLabels.get((int) i));
+
+				// 生成新增用户数(随时间增长并带有随机波动)
+				int baseUsers = 100 + (int) (i * 5);
+				int randomUsers = random.nextInt(60) - 20; // 随机波动(-20至39)
+				vo.setNewUser(Math.max(10, baseUsers + randomUsers));
+
+				// 生成新增用户占比(保留两位小数)
+				double rateFluctuation = (random.nextDouble() - 0.5) * 0.03;
+				double userRate = baseRate + rateFluctuation;
+				vo.setNewUserRate(Math.round(userRate * 100) / 100.0);
+
+				result.add(vo);
+			}
+		}
+
+		// 5. 设置分页数据并返回
+		page.setRecords(result);
+		return page;
+	}
+
+	/**
+	 * 查询次日留存率
+	 * @param reqDto 获取次日留存率入参
+	 * @return GetNewUserRetentionVO
+	 */
+	@Override
+	public GetNewUserRetentionVO getNewUserRetention(GetNewUserRetentionDTO reqDto) {
+		GetNewUserRetentionVO vo = new GetNewUserRetentionVO();
+		List<String> dates = new ArrayList<>();
+		List<Double> counts = new ArrayList<>();
+
+		// 计算日期范围并生成模拟数据
+		LocalDate start = reqDto.getFromDate();
+		LocalDate end = reqDto.getToDate();
+		long days = ChronoUnit.DAYS.between(start, end) + 1;
+
+		Random random = new Random();
+		for (int i = 0; i < days; i++) {
+			dates.add(start.plusDays(i).toString());
+			// 生成5%-30%之间的留存率,保留两位小数
+			double rate = 5 + random.nextDouble() * 25;
+			// 使用BigDecimal处理小数位数
+			BigDecimal bd = new BigDecimal(rate).setScale(2, RoundingMode.HALF_UP);
+			counts.add(bd.doubleValue());
+		}
+
+		vo.setDates(dates);
+		vo.setRetention(counts);
+		return vo;
+	}
+
+	/**
+	 * 分页查询次日留存率详情
+	 * @param reqDto 分页查询次日留存率详情入参
+	 * @return Page
+	 */
+	@Override
+	public Page<PageRetentionDetailVO> pageNewUserRetentionDetail(PageRetentionDetailDTO reqDto) {
+		// 创建分页对象
+		Page<PageRetentionDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageRetentionDetailVO> result = new ArrayList<>();
+
+		// 1. 生成日期列表(根据时间范围)
+		List<String> dates = new ArrayList<>();
+		LocalDate currentDate = reqDto.getFromDate();
+		LocalDate endDate = reqDto.getToDate();
+
+		// 格式化日期的工具
+		DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+		// 生成从开始日期到结束日期的所有日期
+		while (!currentDate.isAfter(endDate)) {
+			dates.add(currentDate.format(dateFormatter));
+			currentDate = currentDate.plusDays(1);
+		}
+
+		// 2. 设置总记录数
+		long total = dates.size();
+		page.setTotal(total);
+
+		// 3. 计算分页范围
+		long startIndex = (reqDto.getCurrent() - 1) * reqDto.getSize();
+		long endIndex = Math.min(startIndex + reqDto.getSize(), total);
+
+		// 4. 生成当前页的模拟数据
+		if (startIndex < total) {
+			Random random = new Random();
+
+			for (long i = startIndex; i < endIndex; i++) {
+				PageRetentionDetailVO vo = new PageRetentionDetailVO();
+
+				// 设置日期
+				vo.setDate(dates.get((int) i));
+
+				// 生成留存率(5%-30%之间的随机数,保留两位小数)
+				double baseRate = 5 + random.nextDouble() * 25; // 5% ~ 30%
+				// 使用四舍五入保留两位小数
+				double retention = Math.round(baseRate * 100) / 100.0;
+				vo.setRetention(retention);
+
+				result.add(vo);
+			}
+		}
+
+		// 5. 设置分页数据
+		page.setRecords(result);
+		return page;
+	}
+
+	/************************************** 活跃用户 *****************************************
+	 * 查询活跃趋势
+	 * @param reqDto 查询活跃趋势入参
+	 * @return GetNewUserTrendVO
+	 */
+	@Override
+	public GetNewUserTrendVO getActiveTrend(UserAnalyseQueryBaseDTO reqDto) {
+		GetNewUserTrendVO result = new GetNewUserTrendVO();
+		List<String> dates = new ArrayList<>();
+		String timeUnit = reqDto.getTimeUnit();
+		LocalDate fromDate = reqDto.getFromDate();
+		LocalDate toDate = reqDto.getToDate();
+
+		// 生成日期列表
+		switch (timeUnit) {
+			case "hour":
+				DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00");
+				LocalDateTime hourStart = fromDate.atStartOfDay();
+				LocalDateTime hourEnd = toDate.atTime(23, 0);
+				while (!hourStart.isAfter(hourEnd)) {
+					dates.add(hourStart.format(hourFormatter));
+					hourStart = hourStart.plusHours(1);
+				}
+				break;
+			case "day":
+				DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				LocalDate dayStart = fromDate;
+				while (!dayStart.isAfter(toDate)) {
+					dates.add(dayStart.format(dayFormatter));
+					dayStart = dayStart.plusDays(1);
+				}
+				break;
+			case "week":
+				DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				// 计算起始日期后的第一个周日
+				LocalDate currentWeek = fromDate;
+				DayOfWeek dayOfWeek = currentWeek.getDayOfWeek();
+				int daysToSunday = 7 - dayOfWeek.getValue(); // 周日的DayOfWeek值为7
+				LocalDate firstSunday = currentWeek.plusDays(daysToSunday);
+
+				while (!firstSunday.isAfter(toDate)) {
+					dates.add(firstSunday.format(weekFormatter));
+					firstSunday = firstSunday.plusWeeks(1);
+				}
+				break;
+			case "month":
+				DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				// 计算范围内的每月1号
+				LocalDate currentMonthFirstDay = LocalDate.of(fromDate.getYear(), fromDate.getMonth(), 1);
+				// 如果当月1号在起始日期之前,则取下个月1号
+				if (currentMonthFirstDay.isBefore(fromDate)) {
+					currentMonthFirstDay = currentMonthFirstDay.plusMonths(1);
+				}
+
+				while (!currentMonthFirstDay.isAfter(toDate)) {
+					dates.add(currentMonthFirstDay.format(monthFormatter));
+					currentMonthFirstDay = currentMonthFirstDay.plusMonths(1);
+				}
+				break;
+			default:
+				// 默认按天处理
+				DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				LocalDate defaultStart = fromDate;
+				while (!defaultStart.isAfter(toDate)) {
+					dates.add(defaultStart.format(defaultFormatter));
+					defaultStart = defaultStart.plusDays(1);
+				}
+		}
+
+		// 生成items数据
+		List<TrendBaseVO> items = new ArrayList<>();
+		List<String> versions = reqDto.getVersion();
+		// 若未指定版本,使用默认版本列表
+		if (versions == null || versions.isEmpty()) {
+			versions = Arrays.asList("1.0", "5.1");
+		}
+
+		Random random = new Random();
+		for (String version : versions) {
+			TrendBaseVO trend = new TrendBaseVO();
+			trend.setName("活跃用户");
+			trend.setVersion(version);
+			trend.setKey("activeUser");
+
+			// 生成与日期数量匹配的随机数据
+			List<Integer> data = new ArrayList<>(dates.size());
+			for (int i = 0; i < dates.size(); i++) {
+				// 生成100-1000之间的随机数,模拟用户数量
+				data.add(random.nextInt(901) + 100);
+			}
+			trend.setData(data);
+			items.add(trend);
+		}
+
+		result.setDates(dates);
+		result.setItems(items);
+		return result;
+	}
+
+	/**
+	 * 查询活跃构成
+	 * @param reqDto 查询活跃构成入参
+	 * @return GetActiveComposeVO
+	 */
+	@Override
+	public GetActiveUserBaseVO getActiveCompose(UserAnalyseQueryBaseDTO reqDto) {
+		// 创建返回对象
+		GetActiveUserBaseVO result = new GetActiveUserBaseVO();
+		Random random = new Random();
+
+		// 1. 生成日期列表
+		List<String> dates = new ArrayList<>();
+		LocalDate startDate = reqDto.getFromDate();
+		LocalDate endDate = reqDto.getToDate();
+		LocalDateTime currentDateTime = LocalDateTime.of(startDate, LocalTime.MIN);
+		LocalDateTime endDateTime = LocalDateTime.of(endDate, LocalTime.MAX);
+
+		// 根据时间单位生成对应日期
+		while (!currentDateTime.isAfter(endDateTime)) {
+			String dateStr;
+			switch (reqDto.getTimeUnit()) {
+				case "hour":
+					dateStr = currentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
+					currentDateTime = currentDateTime.plusHours(1);
+					break;
+				case "day":
+					dateStr = currentDateTime.toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
+					currentDateTime = currentDateTime.plusDays(1);
+					break;
+				case "week":
+					LocalDate weekStart = currentDateTime.toLocalDate();
+					LocalDate weekEnd = weekStart.plusDays(6);
+					if (weekEnd.isAfter(endDate)) {
+						weekEnd = endDate;
+					}
+					dateStr = weekStart.format(DateTimeFormatter.ISO_LOCAL_DATE) + "~" +
+							weekEnd.format(DateTimeFormatter.ISO_LOCAL_DATE);
+					currentDateTime = currentDateTime.plusWeeks(1);
+					break;
+				case "month":
+					LocalDate monthStart = currentDateTime.toLocalDate();
+					LocalDate monthEnd = monthStart.plusMonths(1).minusDays(1);
+					if (monthEnd.isAfter(endDate)) {
+						monthEnd = endDate;
+					}
+					dateStr = monthStart.withDayOfMonth(1).format(DateTimeFormatter.ISO_LOCAL_DATE) + "~" +
+							monthEnd.format(DateTimeFormatter.ISO_LOCAL_DATE);
+					currentDateTime = currentDateTime.plusMonths(1);
+					break;
+				default:
+					dateStr = currentDateTime.toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
+					currentDateTime = currentDateTime.plusDays(1);
+			}
+			dates.add(dateStr);
+		}
+		result.setDates(dates);
+
+		// 2. 生成各项数据(调整为Double类型列表)
+		List<ActiveUserComposeVO> items = new ArrayList<>();
+		int dataSize = dates.size();
+
+		// 活跃用户数据(整数转为Double)
+		ActiveUserComposeVO activeUser = new ActiveUserComposeVO();
+		activeUser.setName("活跃用户");
+		List<Double> activeData = new ArrayList<>();
+		for (int i = 0; i < dataSize; i++) {
+			int value = 30000 + random.nextInt(5000) + (i * 50);
+			activeData.add((double) value); // 整数转Double
+		}
+		activeUser.setData(activeData);
+		items.add(activeUser);
+
+		// 新增用户数据(整数转为Double)
+		ActiveUserComposeVO newUser = new ActiveUserComposeVO();
+		newUser.setName("新增用户");
+		List<Double> newUserData = new ArrayList<>();
+		for (int i = 0; i < dataSize; i++) {
+			int value = activeData.get(i).intValue() - 100 - random.nextInt(300);
+			newUserData.add((double) value); // 整数转Double
+		}
+		newUser.setData(newUserData);
+		items.add(newUser);
+
+		// 新增用户占比数据(保持Double类型)
+		ActiveUserComposeVO newUserRate = new ActiveUserComposeVO();
+		newUserRate.setName("新增用户占比");
+		List<Double> rateData = new ArrayList<>();
+		for (int i = 0; i < dataSize; i++) {
+			double rate = (newUserData.get(i) / activeData.get(i)) * 100;
+			rateData.add(Math.round(rate * 100) / 100.0); // 保留两位小数
+		}
+		newUserRate.setData(rateData);
+		items.add(newUserRate);
+
+		// 老用户数据(计算后转为Double)
+		ActiveUserComposeVO oldUser = new ActiveUserComposeVO();
+		oldUser.setName("老用户");
+		List<Double> oldUserData = new ArrayList<>();
+		for (int i = 0; i < dataSize; i++) {
+			double value = activeData.get(i) - newUserData.get(i);
+			oldUserData.add(value); // 直接为Double类型
+		}
+		oldUser.setData(oldUserData);
+		items.add(oldUser);
+
+		result.setItems(items);
+		return result;
+	}
+
+	/**
+	 * 获取活跃粘度
+	 * @param reqDto 获取活跃粘度入参
+	 * @return GetActiveUserBaseVO
+	 */
+	@Override
+	public GetActiveUserBaseVO getActiveViscosity(UserAnalyseQueryBaseDTO reqDto) {
+		GetActiveUserBaseVO result = new GetActiveUserBaseVO();
+
+		// 构建日期列表(可根据 reqDto 的 fromDate 和 toDate 动态生成,此处为示例固定日期)
+		List<String> dates = Arrays.asList(
+				"2025-07-29", "2025-07-30", "2025-07-31",
+				"2025-08-01", "2025-08-02", "2025-08-03",
+				"2025-08-04", "2025-08-05"
+		);
+		result.setDates(dates);
+
+		// 构建活跃用户组成数据
+		List<ActiveUserComposeVO> items = new ArrayList<>();
+
+		// 日活数据
+		ActiveUserComposeVO activeUserItem = new ActiveUserComposeVO();
+		activeUserItem.setName("日活");
+		activeUserItem.setData(Arrays.asList(1523.0, 1489.0, 1602.0, 1576.0, 987.0, 923.0, 1756.0, 1632.0));
+		items.add(activeUserItem);
+
+		// 过去7日活跃用户数据
+		ActiveUserComposeVO wauItem = new ActiveUserComposeVO();
+		wauItem.setName("过去7日活跃用户");
+		wauItem.setData(Arrays.asList(8234.0, 8156.0, 8321.0, 8289.0, 8356.0, 8412.0, 8531.0, 8498.0));
+		items.add(wauItem);
+
+		// DAU/过去7日活跃用户比率
+		ActiveUserComposeVO wauRateItem = new ActiveUserComposeVO();
+		wauRateItem.setName("DAU/过去7日活跃用户");
+		wauRateItem.setData(Arrays.asList(18.5, 18.25, 19.25, 19.01, 11.81, 10.97, 20.58, 19.21));
+		items.add(wauRateItem);
+
+		// 过去30日活跃用户数据
+		ActiveUserComposeVO mauItem = new ActiveUserComposeVO();
+		mauItem.setName("过去30日活跃用户");
+		mauItem.setData(Arrays.asList(35621.0, 35489.0, 35723.0, 35698.0, 34987.0, 34215.0, 35123.0, 36012.0));
+		items.add(mauItem);
+
+		// DAU/过去30日活跃用户比率
+		ActiveUserComposeVO mauRateItem = new ActiveUserComposeVO();
+		mauRateItem.setName("DAU/过去30日活跃用户");
+		mauRateItem.setData(Arrays.asList(4.28, 4.19, 4.48, 4.42, 2.82, 2.70, 5.00, 4.53));
+		items.add(mauRateItem);
+
+		result.setItems(items);
+		return result;
+	}
+
+	/**
+	 * 获取周活跃率
+	 * @param reqDto 获取周活跃率入参
+	 * @return GetActiveUserBaseVO
+	 */
+	@Override
+	public GetActiveUserBaseVO getActiveWeekrate(UserAnalyseQueryBaseDTO reqDto) {
+		GetActiveUserBaseVO result = new GetActiveUserBaseVO();
+		Random random = new Random();
+		DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+		// 1. 生成周日期列表(每周一)
+		List<String> dates = new ArrayList<>();
+		LocalDate currentDate = reqDto.getFromDate();
+		// 调整到当周周一
+		currentDate = currentDate.minusDays(currentDate.getDayOfWeek().getValue() - 1);
+
+		while (!currentDate.isAfter(reqDto.getToDate())) {
+			dates.add(currentDate.format(dateFormatter));
+			currentDate = currentDate.plusWeeks(1);
+		}
+		result.setDates(dates);
+		int dataSize = dates.size();
+
+		// 2. 生成周累积用户数据(240万起,每周递增0.2%-0.4%)
+		List<Double> weekTotalUserList = new ArrayList<>();
+		double baseTotal = 2400000;
+		for (int i = 0; i < dataSize; i++) {
+			baseTotal += baseTotal * (0.002 + random.nextDouble() * 0.002);
+			// 四舍五入取整(内部实现四舍五入逻辑)
+			weekTotalUserList.add((double) Math.round(baseTotal));
+		}
+
+		// 3. 生成周活跃用户数据(周累积的0.28%-0.33%)
+		List<Double> weekActiveUserList = new ArrayList<>();
+		for (double total : weekTotalUserList) {
+			double active = total * (0.0028 + random.nextDouble() * 0.0005);
+			weekActiveUserList.add((double) Math.round(active)); // 四舍五入取整
+		}
+
+		// 4. 生成周活跃用户率(保留两位小数)
+		List<Double> weekActiveRateList = new ArrayList<>();
+		for (int i = 0; i < dataSize; i++) {
+			double rate = (weekActiveUserList.get(i) / weekTotalUserList.get(i)) * 100;
+			// 四舍五入保留两位小数:先乘100取整再除以100
+			weekActiveRateList.add(Math.round(rate * 100) / 100.0);
+		}
+
+		// 5. 组装返回数据
+		List<ActiveUserComposeVO> items = new ArrayList<>();
+
+		// 周累积用户
+		ActiveUserComposeVO totalItem = new ActiveUserComposeVO();
+		totalItem.setName("周累积用户");
+		totalItem.setData(weekTotalUserList);
+		items.add(totalItem);
+
+		// 周活跃用户
+		ActiveUserComposeVO activeItem = new ActiveUserComposeVO();
+		activeItem.setName("周活跃用户");
+		activeItem.setData(weekActiveUserList);
+		items.add(activeItem);
+
+		// 周活跃用户率
+		ActiveUserComposeVO rateItem = new ActiveUserComposeVO();
+		rateItem.setName("周活跃用户率");
+		rateItem.setData(weekActiveRateList);
+		items.add(rateItem);
+
+		result.setItems(items);
+		return result;
+	}
+
+	/**
+	 * 获取月活跃率
+	 * @param reqDto 获取月活跃率入参
+	 * @return GetActiveUserBaseVO
+	 */
+	@Override
+	public GetActiveUserBaseVO getActiveMonthrate(UserAnalyseQueryBaseDTO reqDto) {
+		// 创建返回对象
+		GetActiveUserBaseVO result = new GetActiveUserBaseVO();
+		Random random = new Random();
+		DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+		// 1. 生成月日期列表(每月1日)
+		List<String> dates = new ArrayList<>();
+		LocalDate currentDate = reqDto.getFromDate();
+
+		// 调整到当月1日
+		currentDate = currentDate.withDayOfMonth(1);
+
+		// 按月生成日期直到结束日期
+		while (!currentDate.isAfter(reqDto.getToDate())) {
+			dates.add(currentDate.format(dateFormatter));
+			currentDate = currentDate.plusMonths(1);
+		}
+		result.setDates(dates);
+		int dataSize = dates.size();
+
+		// 2. 生成月累积用户数据(基础值250万,每月递增约1.2%-1.8%)
+		List<Double> monthTotalUserList = new ArrayList<>();
+		double baseTotal = 2500000; // 基础用户量
+		for (int i = 0; i < dataSize; i++) {
+			// 每月增加1.2%-1.8%的随机增长
+			double growth = baseTotal * (0.012 + random.nextDouble() * 0.006);
+			baseTotal += growth;
+			monthTotalUserList.add((double) Math.round(baseTotal)); // 取整数
+		}
+
+		// 3. 生成月活跃用户数据(月累积用户的1.2%-1.4%)
+		List<Double> monthActiveUserList = new ArrayList<>();
+		for (double total : monthTotalUserList) {
+			double activeRate = 0.012 + random.nextDouble() * 0.002; // 1.2% - 1.4%
+			monthActiveUserList.add((double) Math.round(total * activeRate)); // 取整数
+		}
+
+		// 4. 生成月活跃用户率(月活跃/月累积,保留两位小数)
+		List<Double> monthActiveRateList = new ArrayList<>();
+		for (int i = 0; i < dataSize; i++) {
+			double rate = (monthActiveUserList.get(i) / monthTotalUserList.get(i)) * 100;
+			monthActiveRateList.add(Math.round(rate * 100) / 100.0); // 保留两位小数
+		}
+
+		// 5. 组装items数据
+		List<ActiveUserComposeVO> items = new ArrayList<>();
+
+		// 月累积用户
+		ActiveUserComposeVO totalUserItem = new ActiveUserComposeVO();
+		totalUserItem.setName("月累积用户");
+		totalUserItem.setData(monthTotalUserList);
+		items.add(totalUserItem);
+
+		// 月活跃用户
+		ActiveUserComposeVO activeUserItem = new ActiveUserComposeVO();
+		activeUserItem.setName("月活跃用户");
+		activeUserItem.setData(monthActiveUserList);
+		items.add(activeUserItem);
+
+		// 月活跃用户率
+		ActiveUserComposeVO rateItem = new ActiveUserComposeVO();
+		rateItem.setName("月活跃用户率");
+		rateItem.setData(monthActiveRateList);
+		items.add(rateItem);
+
+		result.setItems(items);
+		return result;
+	}
+
+	/**
+	 * 分页查询活跃详情
+	 * @param reqDto 分页查询活跃详情入参
+	 * @return Page
+	 */
+	@Override
+	public Page pageActiveDetail(PageActiveDetailDTO reqDto) {
+		Page<PageActiveDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageActiveDetailVO> result = new ArrayList<>();
+		String timeUnit = reqDto.getTimeUnit();
+		LocalDate fromDate = reqDto.getFromDate();
+		LocalDate toDate = reqDto.getToDate();
+
+		// 根据时间单位生成对应模拟数据
+		if ("day".equals(timeUnit)) {
+			// 生成按天的模拟数据
+			LocalDate currentDate = fromDate;
+			while (!currentDate.isAfter(toDate) && result.size() < reqDto.getSize()) {
+				PageActiveDetailVO vo = new PageActiveDetailVO();
+				vo.setDate(currentDate.toString());
+				vo.setActiveUser(1300 + new Random().nextInt(200));
+				vo.setMauRate(3.5 + new Random().nextDouble() * 1.5);
+				vo.setWauRate(15 + new Random().nextDouble() * 5);
+				vo.setNewUserRate(96 + new Random().nextDouble() * 2);
+				result.add(vo);
+				currentDate = currentDate.plusDays(1);
+			}
+		} else if ("week".equals(timeUnit)) {
+			// 生成按周的模拟数据
+			LocalDate currentDate = fromDate;
+			while (!currentDate.isAfter(toDate) && result.size() < reqDto.getSize()) {
+				LocalDate endOfWeek = currentDate.plusDays(6);
+				if (endOfWeek.isAfter(toDate)) {
+					endOfWeek = toDate;
+				}
+				PageActiveDetailVO vo = new PageActiveDetailVO();
+				vo.setDate(String.format("%s~%s",
+						currentDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")),
+						endOfWeek.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))));
+				vo.setActiveUser(7500 + new Random().nextInt(500));
+				vo.setWeekActiveUserRate(0.25 + new Random().nextDouble() * 0.1);
+				vo.setNewUserRate(98.5 + new Random().nextDouble() * 0.5);
+				result.add(vo);
+				currentDate = currentDate.plusWeeks(1);
+			}
+		} else if ("month".equals(timeUnit)) {
+			// 生成按月的模拟数据
+			LocalDate currentDate = fromDate;
+			while (!currentDate.isAfter(toDate) && result.size() < reqDto.getSize()) {
+				LocalDate endOfMonth = currentDate.plusMonths(1).minusDays(1);
+				if (endOfMonth.isAfter(toDate)) {
+					endOfMonth = toDate;
+				}
+				PageActiveDetailVO vo = new PageActiveDetailVO();
+				vo.setDate(String.format("%s~%s",
+						currentDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")),
+						endOfMonth.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))));
+				vo.setActiveUser(30000 + new Random().nextInt(5000));
+				vo.setNewUserRate(99.4 + new Random().nextDouble() * 0.2);
+				vo.setMonthActiveUserRate(1.2 + new Random().nextDouble() * 0.2);
+				result.add(vo);
+				currentDate = currentDate.plusMonths(1);
+			}
+		}
+
+		page.setRecords(result);
+		// 模拟总条数,实际场景应根据查询条件计算
+		page.setTotal(result.size() > 0 ? 100 : 0);
+		return page;
+	}
+
+	/************************************** 启动次数 *****************************************
+	 * 查询启动次数趋势
+	 * @param reqDto 查询启动次数趋势入参
+	 * @return GetNewUserTrendVO
+	 */
+	@Override
+	public GetNewUserTrendVO getLaunchTrend(UserAnalyseQueryBaseDTO reqDto) {
+		GetNewUserTrendVO result = new GetNewUserTrendVO();
+		List<String> dates = new ArrayList<>();
+		String timeUnit = reqDto.getTimeUnit();
+		LocalDate fromDate = reqDto.getFromDate();
+		LocalDate toDate = reqDto.getToDate();
+
+		// 生成日期列表
+		switch (timeUnit) {
+			case "hour":
+				DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00");
+				LocalDateTime hourStart = fromDate.atStartOfDay();
+				LocalDateTime hourEnd = toDate.atTime(23, 0);
+				while (!hourStart.isAfter(hourEnd)) {
+					dates.add(hourStart.format(hourFormatter));
+					hourStart = hourStart.plusHours(1);
+				}
+				break;
+			case "day":
+				DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				LocalDate dayStart = fromDate;
+				while (!dayStart.isAfter(toDate)) {
+					dates.add(dayStart.format(dayFormatter));
+					dayStart = dayStart.plusDays(1);
+				}
+				break;
+			case "week":
+				DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				// 计算起始日期后的第一个周日
+				LocalDate currentWeek = fromDate;
+				DayOfWeek dayOfWeek = currentWeek.getDayOfWeek();
+				int daysToSunday = 7 - dayOfWeek.getValue(); // 周日的DayOfWeek值为7
+				LocalDate firstSunday = currentWeek.plusDays(daysToSunday);
+
+				while (!firstSunday.isAfter(toDate)) {
+					dates.add(firstSunday.format(weekFormatter));
+					firstSunday = firstSunday.plusWeeks(1);
+				}
+				break;
+			case "month":
+				DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				// 计算范围内的每月1号
+				LocalDate currentMonthFirstDay = LocalDate.of(fromDate.getYear(), fromDate.getMonth(), 1);
+				// 如果当月1号在起始日期之前,则取下个月1号
+				if (currentMonthFirstDay.isBefore(fromDate)) {
+					currentMonthFirstDay = currentMonthFirstDay.plusMonths(1);
+				}
+
+				while (!currentMonthFirstDay.isAfter(toDate)) {
+					dates.add(currentMonthFirstDay.format(monthFormatter));
+					currentMonthFirstDay = currentMonthFirstDay.plusMonths(1);
+				}
+				break;
+			default:
+				// 默认按天处理
+				DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+				LocalDate defaultStart = fromDate;
+				while (!defaultStart.isAfter(toDate)) {
+					dates.add(defaultStart.format(defaultFormatter));
+					defaultStart = defaultStart.plusDays(1);
+				}
+		}
+
+		// 生成items数据
+		List<TrendBaseVO> items = new ArrayList<>();
+		List<String> versions = reqDto.getVersion();
+		// 若未指定版本,使用默认版本列表
+		if (versions == null || versions.isEmpty()) {
+			versions = Arrays.asList("1.0", "5.1");
+		}
+
+		Random random = new Random();
+		for (String version : versions) {
+			TrendBaseVO trend = new TrendBaseVO();
+			trend.setName("启动次数");
+			trend.setVersion(version);
+			trend.setKey("launch");
+
+			// 生成与日期数量匹配的随机数据
+			List<Integer> data = new ArrayList<>(dates.size());
+			for (int i = 0; i < dates.size(); i++) {
+				// 生成100-1000之间的随机数,模拟用户数量
+				data.add(random.nextInt(901) + 100);
+			}
+			trend.setData(data);
+			items.add(trend);
+		}
+
+		result.setDates(dates);
+		result.setItems(items);
+		return result;
+	}
+	/**
+	 * 分页查询启动次数详情
+	 * @param reqDto 分页查询启动次数详情入参
+	 * @return Page
+	 */
+	@Override
+	public Page<PageLaunchDetailVO> pageLaunchDetail(PageActiveDetailDTO reqDto) {
+		// 初始化分页对象
+		Page<PageLaunchDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageLaunchDetailVO> result = new ArrayList<>();
+		Random random = new Random();
+
+		// 日期格式化器
+		DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00");
+		DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+		DateTimeFormatter weekMonthFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
+
+		// 获取入参参数
+		String timeUnit = reqDto.getTimeUnit();
+		LocalDate fromDate = reqDto.getFromDate();
+		LocalDate toDate = reqDto.getToDate();
+
+		// 根据时间单位生成模拟数据
+		switch (timeUnit) {
+			case "hour":
+				LocalDateTime currentHour = LocalDateTime.of(fromDate, LocalTime.MIN);
+				LocalDateTime endHour = LocalDateTime.of(toDate, LocalTime.MAX);
+				while (!currentHour.isAfter(endHour)) {
+					PageLaunchDetailVO vo = new PageLaunchDetailVO();
+					vo.setDate(currentHour.format(hourFormatter));
+					// 保留两位小数:生成0-100的随机数后四舍五入保留两位
+					double hourRate = random.nextDouble() * 100;
+					vo.setLaunchRate(Math.round(hourRate * 100) / 100.0);
+					vo.setLaunch(random.nextInt(10000));
+					result.add(vo);
+					currentHour = currentHour.plusHours(1);
+				}
+				break;
+
+			case "day":
+				LocalDate currentDay = fromDate;
+				while (!currentDay.isAfter(toDate)) {
+					PageLaunchDetailVO vo = new PageLaunchDetailVO();
+					vo.setDate(currentDay.format(dayFormatter));
+					// 保留两位小数
+					double dayRate = random.nextDouble() * 100;
+					vo.setLaunchRate(Math.round(dayRate * 100) / 100.0);
+					vo.setLaunch(random.nextInt(40000) + 929);
+					result.add(vo);
+					currentDay = currentDay.plusDays(1);
+				}
+				break;
+
+			case "week":
+				LocalDate weekStart = fromDate.minusDays(fromDate.getDayOfWeek().getValue() - 1);
+				while (!weekStart.isAfter(toDate)) {
+					LocalDate weekEnd = weekStart.plusDays(6);
+					if (weekEnd.isAfter(toDate)) {
+						weekEnd = toDate;
+					}
+					PageLaunchDetailVO vo = new PageLaunchDetailVO();
+					vo.setDate(weekStart.format(weekMonthFormatter) + "~" + weekEnd.format(weekMonthFormatter));
+					// 保留两位小数
+					double weekRate = random.nextDouble() * 50;
+					vo.setLaunchRate(Math.round(weekRate * 100) / 100.0);
+					vo.setLaunch(random.nextInt(10000) + 298);
+					result.add(vo);
+					weekStart = weekEnd.plusDays(1);
+				}
+				break;
+
+			case "month":
+				LocalDate monthStart = fromDate.withDayOfMonth(1);
+				while (!monthStart.isAfter(toDate)) {
+					LocalDate monthEnd = monthStart.plusMonths(1).minusDays(1);
+					if (monthEnd.isAfter(toDate)) {
+						monthEnd = toDate;
+					}
+					PageLaunchDetailVO vo = new PageLaunchDetailVO();
+					vo.setDate(monthStart.format(weekMonthFormatter) + "~" + monthEnd.format(weekMonthFormatter));
+					// 保留两位小数
+					double monthRate = random.nextDouble() * 60;
+					vo.setLaunchRate(Math.round(monthRate * 100) / 100.0);
+					vo.setLaunch(random.nextInt(30000) + 3955);
+					result.add(vo);
+					monthStart = monthEnd.plusDays(1);
+				}
+				break;
+		}
+
+		// 处理分页
+		int total = result.size();
+		int fromIndex = (int) ((reqDto.getCurrent() - 1) * reqDto.getSize());
+		int toIndex = Math.min(fromIndex + (int) reqDto.getSize(), total);
+		if (fromIndex >= 0 && fromIndex < total) {
+			result = result.subList(fromIndex, toIndex);
+		} else {
+			result = new ArrayList<>();
+		}
+
+		page.setRecords(result);
+		page.setTotal(total);
+		return page;
+	}
+
+	/************************************** 版本分布 *****************************************
+	 * 分页查询全部版本详情
+	 * @param reqDto 分页查询全部版本详情入参
+	 * @return Page
+	 */
+	@Override
+	public Page<PageAllVersionDetailVO> pageAllVersionDetail(PageAllVersionDetailDTO reqDto) {
+		// 初始化分页对象
+		Page<PageAllVersionDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageAllVersionDetailVO> result = new ArrayList<>();
+		Random random = new Random();
+
+		// 模拟版本列表(可根据实际业务版本调整)
+		List<String> versions = new ArrayList<>();
+		versions.add("1.0");
+		versions.add("2.5");
+		versions.add("3.2.1");
+		versions.add("4.0");
+		versions.add("5.1");
+		versions.add("6.0.2");
+		versions.add("6.3.0");
+		versions.add("7.1.0");
+
+		// 生成总数据(超出分页范围,用于模拟分页效果)
+		int totalDataSize = 20; // 模拟20条总数据
+		for (int i = 0; i < totalDataSize; i++) {
+			PageAllVersionDetailVO vo = new PageAllVersionDetailVO();
+			String version = versions.get(random.nextInt(versions.size()));
+
+			// 基础用户量(根据版本新旧设置不同量级)
+			int baseTotalUser;
+			if (version.startsWith("1.") || version.startsWith("2.")) {
+				baseTotalUser = 500000 + random.nextInt(500000); // 老版本用户量较大
+			} else if (version.startsWith("6.") || version.startsWith("7.")) {
+				baseTotalUser = 10000 + random.nextInt(50000); // 新版本用户量较小
+			} else {
+				baseTotalUser = 100000 + random.nextInt(400000); // 中间版本
+			}
+
+			// 活跃用户(总用户的0.01%-0.1%)
+			int activeUser = (int) (baseTotalUser * (0.0001 + random.nextDouble() * 0.0009));
+			activeUser = Math.max(activeUser, 1); // 确保至少1个活跃用户
+
+			// 新用户(活跃用户的80%-120%)
+			int newUser = (int) (activeUser * (0.8 + random.nextDouble() * 0.4));
+
+			// 升级用户(新用户的0%-5%)
+			int upgradeUser = (int) (newUser * random.nextDouble() * 0.05);
+
+			// 启动次数(活跃用户的50-200倍)
+			int launch = activeUser * (50 + random.nextInt(151));
+
+			// 活跃用户率(2%-60%,保留两位小数)
+			double activeUserRate = Math.round((2 + random.nextDouble() * 58) * 100) / 100.0;
+
+			// 总用户占比(1%-60%,保留两位小数)
+			double totalUserRate = Math.round((1 + random.nextDouble() * 59) * 100) / 100.0;
+
+			// 设置属性
+			vo.setVersion(version);
+			vo.setTotalUser(baseTotalUser);
+			vo.setActiveUser(activeUser);
+			vo.setNewUser(newUser);
+			vo.setUpgradeUser(upgradeUser);
+			vo.setLaunch(launch);
+			vo.setActiveUserRate(activeUserRate);
+			vo.setTotalUserRate(totalUserRate);
+
+			result.add(vo);
+		}
+
+		// 处理分页逻辑
+		int total = result.size();
+		int fromIndex = (int) ((reqDto.getCurrent() - 1) * reqDto.getSize());
+		int toIndex = Math.min(fromIndex + (int) reqDto.getSize(), total);
+
+		if (fromIndex >= 0 && fromIndex < total) {
+			result = result.subList(fromIndex, toIndex);
+		} else {
+			result = new ArrayList<>();
+		}
+
+		page.setRecords(result);
+		page.setTotal(total);
+		return page;
+	}
+
+	/**
+	 * 分页查询单版本详情
+	 * @param reqDto 入参
+	 * @return Page
+	 */
+	@Override
+	public Page<PageSingleVersionDetailVO> pageSingleVersionDetail(PageSingleVersionDetailDTO reqDto) {
+		// 初始化分页对象
+		Page<PageSingleVersionDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageSingleVersionDetailVO> result = new ArrayList<>();
+		Random random = new Random();
+
+		// 获取入参中的时间范围和版本信息
+		LocalDate fromDate = reqDto.getFromDate();
+		LocalDate toDate = reqDto.getToDate();
+		String version = reqDto.getVersion().get(0); // 假设DTO中有获取版本的方法
+
+		// 日期格式化器
+		DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+		// 生成时间范围内的所有日期数据
+		LocalDate currentDate = fromDate;
+		while (!currentDate.isAfter(toDate)) {
+			PageSingleVersionDetailVO vo = new PageSingleVersionDetailVO();
+
+			// 设置日期
+			vo.setDate(currentDate.format(dateFormatter));
+
+			// 基础用户数(10-50之间随机)
+			int baseUser = 10 + random.nextInt(41);
+
+			// 活跃用户
+			vo.setActiveUser(baseUser);
+
+			// 新用户(80%-100%的活跃用户)
+			int newUser = (int) (baseUser * (0.8 + random.nextDouble() * 0.2));
+			vo.setNewUser(newUser);
+
+			// 升级用户(0-5之间随机,且不超过活跃用户)
+			int upgradeUser = random.nextInt(Math.min(6, baseUser + 1));
+			vo.setUpgradeUser(upgradeUser);
+
+			// 启动次数(活跃用户的1-3倍)
+			int launch = baseUser * (1 + random.nextInt(3));
+			vo.setLaunch(launch);
+
+			result.add(vo);
+			currentDate = currentDate.plusDays(1);
+		}
+
+		// 处理分页逻辑
+		int total = result.size();
+		int fromIndex = (int) ((reqDto.getCurrent() - 1) * reqDto.getSize());
+		int toIndex = Math.min(fromIndex + (int) reqDto.getSize(), total);
+
+		if (fromIndex >= 0 && fromIndex < total) {
+			result = result.subList(fromIndex, toIndex);
+		} else {
+			result = new ArrayList<>();
+		}
+
+		page.setRecords(result);
+		page.setTotal(total);
+		return page;
+	}
+
+	/**
+	 * 查询版本用户来源
+	 * @param reqDto 入参
+	 * @return GetVersionDistributionVO
+	 */
+	@Override
+	public List<GetVersionDistributionVO> getVersionDistribution(GetVersionDistributionDTO reqDto) {
+		List<GetVersionDistributionVO> result = new ArrayList<>();
+
+		// 根据不同类型模拟不同来源数据
+		if ("upgradeChannel".equals(reqDto.getType())) {
+			GetVersionDistributionVO vo1 = new GetVersionDistributionVO();
+			vo1.setName("应用宝");
+			vo1.setValue(120);
+			result.add(vo1);
+
+			GetVersionDistributionVO vo2 = new GetVersionDistributionVO();
+			vo2.setName("华为应用市场");
+			vo2.setValue(95);
+			result.add(vo2);
+
+			GetVersionDistributionVO vo3 = new GetVersionDistributionVO();
+			vo3.setName("小米应用商店");
+			vo3.setValue(80);
+			result.add(vo3);
+
+			GetVersionDistributionVO vo4 = new GetVersionDistributionVO();
+			vo4.setName("苹果App Store");
+			vo4.setValue(75);
+			result.add(vo4);
+
+			GetVersionDistributionVO vo5 = new GetVersionDistributionVO();
+			vo5.setName("百度手机助手");
+			vo5.setValue(40);
+			result.add(vo5);
+		} else {
+			// 其他类型的默认模拟数据
+			GetVersionDistributionVO vo1 = new GetVersionDistributionVO();
+			vo1.setName("未知渠道1");
+			vo1.setValue(50);
+			result.add(vo1);
+
+			GetVersionDistributionVO vo2 = new GetVersionDistributionVO();
+			vo2.setName("未知渠道2");
+			vo2.setValue(30);
+			result.add(vo2);
+		}
+
+		return result;
+	}
+
+	/**
+	 * 分页查询版本用户来源详情
+	 * @param reqDto 入参
+	 * @return Page<PageVersionDistributionDetailVO>
+	 */
+	@Override
+	public Page<PageVersionDistributionDetailVO> pageVersionDistributionDetail(PageVersionDistributionDetailDTO reqDto) {
+		Page<PageVersionDistributionDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
+		List<PageVersionDistributionDetailVO> result = new ArrayList<>();
+		Random random = new Random();
+		String type = reqDto.getType();
+
+		// 根据类型生成15条数据
+		if ("upgradeChannel".equals(type)) {
+			// 升级渠道类型
+			String[] channelNames = {
+					"应用宝", "华为应用市场", "小米应用商店", "苹果App Store", "百度手机助手",
+					"360手机助手", "vivo应用商店", "OPPO软件商店", "腾讯应用宝", "豌豆荚",
+					"三星应用商店", "魅族应用市场", "谷歌Play商店", "应用汇", "机锋市场"
+			};
+			for (String name : channelNames) {
+				PageVersionDistributionDetailVO vo = new PageVersionDistributionDetailVO();
+				vo.setName(name);
+				// 生成10-200之间的随机整数value
+				int value = 10 + random.nextInt(191);
+				vo.setValue(value);
+				// 生成0.1-30.0之间的随机数并保留两位小数
+				double rate = Math.round((0.1 + random.nextDouble() * 29.9) * 100) / 100.0;
+				vo.setRate(rate);
+				result.add(vo);
+			}
+		} else if ("upgradeVersion".equals(type)) {
+			// 升级版本类型
+			String[] versionNames = {
+					"1.0.0→2.0.0", "1.1.0→2.0.0", "1.2.0→2.0.0", "1.3.0→2.0.0", "1.4.0→2.0.0",
+					"1.5.0→2.0.0", "1.6.0→2.0.0", "1.7.0→2.0.0", "1.8.0→2.0.0", "1.9.0→2.0.0",
+					"2.0.0→2.1.0", "2.0.0→2.2.0", "2.0.0→3.0.0", "2.1.0→3.0.0", "2.2.0→3.0.0"
+			};
+			for (String name : versionNames) {
+				PageVersionDistributionDetailVO vo = new PageVersionDistributionDetailVO();
+				vo.setName(name);
+				// 生成50-500之间的随机整数value
+				int value = 50 + random.nextInt(451);
+				vo.setValue(value);
+				// 生成5.0-60.0之间的随机数并保留两位小数
+				double rate = Math.round((5.0 + random.nextDouble() * 55.0) * 100) / 100.0;
+				vo.setRate(rate);
+				result.add(vo);
+			}
+		} else if ("newUserChannel".equals(type)) {
+			// 新用户渠道类型
+			String[] newUserNames = {
+					"应用市场推广", "社交媒体分享", "官网下载", "朋友推荐", "搜索引擎",
+					"信息流广告", "短视频推广", "直播带货", "线下活动", "合作引流",
+					"短信邀请", "二维码扫描", "公众号文章", "小程序跳转", "地推活动"
+			};
+			for (String name : newUserNames) {
+				PageVersionDistributionDetailVO vo = new PageVersionDistributionDetailVO();
+				vo.setName(name);
+				// 生成100-1000之间的随机整数value
+				int value = 100 + random.nextInt(901);
+				vo.setValue(value);
+				// 生成1.0-40.0之间的随机数并保留两位小数
+				double rate = Math.round((1.0 + random.nextDouble() * 39.0) * 100) / 100.0;
+				vo.setRate(rate);
+				result.add(vo);
+			}
+		} else {
+			// 其他类型默认数据
+			for (int i = 1; i <= 15; i++) {
+				PageVersionDistributionDetailVO vo = new PageVersionDistributionDetailVO();
+				vo.setName("未知类型" + i);
+				vo.setValue(20 + random.nextInt(181));
+				vo.setRate(Math.round((0.5 + random.nextDouble() * 20.0) * 100) / 100.0);
+				result.add(vo);
+			}
+		}
+
+		// 处理分页逻辑
+		long current = reqDto.getCurrent();
+		long size = reqDto.getSize();
+		long total = result.size();
+		int start = (int) ((current - 1) * size);
+		int end = (int) Math.min(start + size, total);
+
+		if (start < result.size() && start >= 0) {
+			result = result.subList(start, end);
+		} else {
+			result = new ArrayList<>();
+		}
+
+		page.setRecords(result);
+		page.setTotal(total);
+		return page;
+	}
+
+}

+ 23 - 0
pig-statistics/pig-statistics-biz/src/main/resources/application.yml

@@ -0,0 +1,23 @@
+server:
+  port: 17000
+
+spring:
+  application:
+    name: @artifactId@
+  cloud:
+    nacos:
+      username: @nacos.username@
+      password: @nacos.password@
+      discovery:
+        namespace: @nacos.namespace@
+        server-addr: @nacos.address@
+        group: DEFAULT_GROUP
+      config:
+        namespace: ${spring.cloud.nacos.discovery.namespace}
+        server-addr: ${spring.cloud.nacos.discovery.server-addr}
+        group: DEFAULT_GROUP
+        refresh-enabled: true
+  config:
+    import:
+      - nacos:application.yml
+      - nacos:${spring.application.name}.yml

+ 69 - 0
pig-statistics/pig-statistics-biz/src/main/resources/logback-spring.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
+    <properties>
+        <log-path>/Users/lengleng</log-path>
+    </properties>
+    1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
+    2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
+-->
+<configuration debug="false" scan="false">
+    <property name="log.path" value="logs/${project.artifactId}"/>
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+	<property name="FILE_LOG_PATTERN" value=":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n"/>
+	<!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" class="org.springframework.boot.logging.logback.ColorConverter"/>
+    <conversionRule conversionWord="wex"
+                    class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+    <conversionRule conversionWord="wEx"
+                    class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+    <!-- Console log output -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- Log file debug output -->
+    <appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/debug.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- Log file error output -->
+    <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/error.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+    </appender>
+
+    <!--nacos 心跳 INFO 屏蔽-->
+    <logger name="com.alibaba.nacos" level="OFF">
+        <appender-ref ref="error"/>
+    </logger>
+
+    <!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
+    <root level="debug">
+        <appender-ref ref="console"/>
+        <appender-ref ref="debug"/>
+        <appender-ref ref="error"/>
+    </root>
+</configuration>

+ 37 - 0
pig-statistics/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.pig4cloud</groupId>
+		<artifactId>pig</artifactId>
+		<version>${revision}</version>
+	</parent>
+
+	<artifactId>pig-statistics</artifactId>
+
+	<description>pig 统计模块</description>
+
+	<packaging>pom</packaging>
+
+	<modules>
+		<module>pig-statistics-api</module>
+		<module>pig-statistics-biz</module>
+	</modules>
+</project>

+ 1 - 0
pom.xml

@@ -110,6 +110,7 @@
 		<module>pig-common</module>
 		<module>pig-visual</module>
 		<module>pig-marketing</module>
+		<module>pig-statistics</module>
 	</modules>
 
 	<dependencyManagement>