ソースを参照

new: 导出相关接口

lwh 3 週間 前
コミット
b4670306a1

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

@@ -37,12 +37,14 @@ public class GetVersionDistributionDTO implements Serializable {
 	/**
 	 * 时间单位
 	 */
+	@NotBlank(message = "时间单位不能为空")
 	@Schema(description = "时间单位,昨天-day,过去7天-week,过去30天-month", example = "day")
 	private String timeUnit;
 
 	/**
 	 * 类型
 	 */
+	@NotBlank(message = "类型不能为空")
 	@Schema(description = "类型,upgradeChannel-升级用户渠道",example = "upgradeChannel")
 	private String type;
 }

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

@@ -1,6 +1,10 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -26,18 +30,24 @@ public class PageActiveDetailVO implements Serializable {
 	/**
 	 * 日期
 	 */
+	@ColumnWidth(20)
+	@ExcelProperty(value = "日期")
 	@Schema(description = "日期")
 	private String date;
 
 	/**
 	 * 活跃用户数
 	 */
+	@ColumnWidth(10)
+	 @ExcelProperty(value = "活跃用户数")
 	@Schema(description = "活跃用户数")
 	private Integer activeUser;
 
 	/**
 	 * 新用户占比
 	 */
+	@ColumnWidth(10)
+	@ExcelProperty(value = "新用户占比")
 	@Schema(description = "新用户占比")
 	private BigDecimal newUserRate;
 
@@ -45,18 +55,24 @@ public class PageActiveDetailVO implements Serializable {
 	/***********************************单位为天时
 	 * DAU/过去7日活跃用户
 	 */
+	@ColumnWidth(15)
+	@ExcelProperty(value = "DAU/过去7日活跃用户")
 	@Schema(description = "DAU/过去7日活跃用户")
 	private BigDecimal wauRate;
 
 	/***********************************单位为天时
 	 * DAU/过去30日活跃用户
 	 */
+	@ColumnWidth(15)
+	@ExcelProperty(value = "DAU/过去30日活跃用户")
 	@Schema(description = "DAU/过去30日活跃用户")
 	private BigDecimal mauRate;
 
 	/***********************************单位为周时
 	 * 用户周活跃率
 	 */
+	@ColumnWidth(10)
+	@ExcelProperty(value = "用户周活跃率")
 	@Schema(description = "用户周活跃率")
 	private BigDecimal weekActiveUserRate;
 
@@ -64,6 +80,8 @@ public class PageActiveDetailVO implements Serializable {
 	/***********************************单位为月时
 	 * 用户月活跃率
 	 */
+	@ColumnWidth(10)
+	@ExcelProperty(value = "用户月活跃率")
 	@Schema(description = "用户月活跃率")
 	private BigDecimal monthActiveUserRate;
 }

+ 39 - 21
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageAllVersionDetailVO.java

@@ -1,6 +1,8 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -21,51 +23,67 @@ public class PageAllVersionDetailVO implements Serializable {
 	private static final long serialVersionUID = 1L;
 
 	/**
-	 * 活跃用户占比
-	 */
-	@Schema(description = "活跃用户占比")
-	private BigDecimal activeUserRate;
-
-	/**
-	 * 活跃用户
+	 * 版本
 	 */
-	@Schema(description = "活跃用户")
-	private Integer activeUser;
+	@ColumnWidth(8)
+	@ExcelProperty(value = "版本")
+	@Schema(description = "版本")
+	private String version;
 
 	/**
-	 * 新增用户
+	 * 累计用户占比
 	 */
-	@Schema(description = "新增用户")
-	private Integer newUser;
+	@ColumnWidth(16)
+	@ExcelProperty(value = "截至今日版本累计用户占比")
+	@Schema(description = "累计用户占比")
+	private BigDecimal totalUserRate;
 
 	/**
 	 * 累计用户
 	 */
+	@ColumnWidth(11)
+	@ExcelProperty(value = "累计用户")
 	@Schema(description = "累计用户")
 	private Integer totalUser;
 
 	/**
-	 * 累计用户占比
+	 * 新增用户
 	 */
-	@Schema(description = "累计用户占比")
-	private BigDecimal totalUserRate;
+	@ColumnWidth(11)
+	@ExcelProperty(value = "新增用户")
+	@Schema(description = "新增用户")
+	private Integer newUser;
 
 	/**
-	 * 升级用户
+	 * 活跃用户
 	 */
-	@Schema(description = "升级用户")
-	private Integer upgradeUser;
+	@ColumnWidth(11)
+	@ExcelProperty(value = "活跃用户")
+	@Schema(description = "活跃用户")
+	private Integer activeUser;
+
+	/**
+	 * 活跃用户占比
+	 */
+	@ColumnWidth(10)
+	@ExcelProperty(value = "活跃用户占比")
+	@Schema(description = "活跃用户占比")
+	private BigDecimal activeUserRate;
 
 	/**
 	 * 启动次数
 	 */
+	@ColumnWidth(11)
+	@ExcelProperty(value = "启动次数")
 	@Schema(description = "启动次数")
 	private Integer launch;
 
 	/**
-	 * 版本
+	 * 升级用户
 	 */
-	@Schema(description = "版本")
-	private String version;
+	@ColumnWidth(11)
+	@ExcelProperty(value = "升级用户")
+	@Schema(description = "升级用户")
+	private Integer upgradeUser;
 
 }

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

@@ -1,6 +1,8 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -23,18 +25,24 @@ public class PageLaunchDetailVO implements Serializable {
 	/**
 	 * 日期
 	 */
+	@ColumnWidth(20)
+	@ExcelProperty(value = "日期")
 	@Schema(description = "日期")
 	private String date;
 
 	/**
 	 * 启动次数占比
 	 */
+	@ColumnWidth(16)
+	@ExcelProperty(value = "启动次数占比")
 	@Schema(description = "启动次数占比")
 	private BigDecimal launchRate;
 
 	/**
 	 * 启动次数
 	 */
+	@ColumnWidth(13)
+	@ExcelProperty(value = "启动次数")
 	@Schema(description = "启动次数")
 	private Integer launch;
 }

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

@@ -1,6 +1,8 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -23,18 +25,24 @@ public class PageNewUserTrendDetailVO implements Serializable {
 	/**
 	 * 日期
 	 */
+	@ColumnWidth(20)
+	@ExcelProperty("日期")
 	@Schema(description = "日期")
 	private String date;
 
 	/**
 	 * 新增用户数
 	 */
+	@ColumnWidth(20)
+	@ExcelProperty("新增用户数")
 	@Schema(description = "新增用户数")
 	private Integer newUser;
 
 	/**
 	 * 新增用户占比
 	 */
+	@ColumnWidth(20)
+	@ExcelProperty("新增用户占比")
 	@Schema(description = "新增用户占比")
 	private BigDecimal newUserRate;
 }

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

@@ -1,6 +1,8 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -23,12 +25,16 @@ public class PageRetentionDetailVO implements Serializable {
 	/**
 	 * 日期
 	 */
+	@ColumnWidth(12)
+	@ExcelProperty(value = "日期")
 	@Schema(description = "日期")
 	private String date;
 
 	/**
 	 * 留存率
 	 */
+	@ColumnWidth(10)
+	@ExcelProperty(value = "留存率")
 	@Schema(description = "留存率")
 	private BigDecimal retention;
 }

+ 21 - 8
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageSingleVersionDetailVO.java

@@ -1,6 +1,8 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -22,30 +24,41 @@ public class PageSingleVersionDetailVO implements Serializable {
 	/**
 	 * 日期
 	 */
+	@ColumnWidth(12)
+	@ExcelProperty(value = "日期")
 	@Schema(description = "日期")
 	private String date;
 
+	/**
+	 * 新增用户
+	 */
+	@ColumnWidth(11)
+	@ExcelProperty(value = "新增用户")
+	@Schema(description = "新增用户")
+	private Integer newUser;
+
 	/**
 	 * 活跃用户
 	 */
+	@ColumnWidth(11)
+	@ExcelProperty(value = "活跃用户")
 	@Schema(description = "活跃用户")
 	private Integer activeUser;
 
 	/**
-	 * 新增用户
+	 * 启动次数
 	 */
-	@Schema(description = "新增用户")
-	private Integer newUser;
+	@ColumnWidth(11)
+	@ExcelProperty(value = "启动次数")
+	@Schema(description = "启动次数")
+	private Integer launch;
 
 	/**
 	 * 升级用户
 	 */
+	@ColumnWidth(11)
+	@ExcelProperty(value = "升级用户")
 	@Schema(description = "升级用户")
 	private Integer upgradeUser;
 
-	/**
-	 * 启动次数
-	 */
-	@Schema(description = "启动次数")
-	private Integer launch;
 }

+ 11 - 5
pig-statistics/pig-statistics-api/src/main/java/com/pig4cloud/pig/statistics/api/vo/user/PageVersionDistributionDetailVO.java

@@ -1,6 +1,7 @@
 package com.pig4cloud.pig.statistics.api.vo.user;
 
 
+import cn.idev.excel.annotation.write.style.ColumnWidth;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -20,21 +21,26 @@ public class PageVersionDistributionDetailVO implements Serializable {
 	@Serial
 	private static final long serialVersionUID = 1L;
 
-	/**
-	 * 占比率
-	 */
-	@Schema(description = "占比率")
-	private BigDecimal rate;
 
 	/**
 	 * 名称
 	 */
+	@ColumnWidth(12)
 	@Schema(description = "名称")
 	private String name;
 
+
 	/**
 	 * 数量
 	 */
+	@ColumnWidth(11)
 	@Schema(description = "数量")
 	private Integer value;
+
+	/**
+	 * 占比率
+	 */
+	@ColumnWidth(11)
+	@Schema(description = "占比率")
+	private BigDecimal rate;
 }

+ 340 - 22
pig-statistics/pig-statistics-biz/src/main/java/com/pig4cloud/pig/statistics/controller/UserAnalyseController.java

@@ -1,20 +1,35 @@
 package com.pig4cloud.pig.statistics.controller;
 
 
+import cn.idev.excel.EasyExcel;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.common.core.exception.BusinessException;
 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 com.pig4cloud.plugin.excel.annotation.ResponseExcel;
+import com.pig4cloud.plugin.excel.annotation.Sheet;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.AllArgsConstructor;
+import org.springdoc.core.annotations.ParameterObject;
+import org.springframework.beans.BeanUtils;
 import org.springframework.http.HttpHeaders;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 
 /**
@@ -47,10 +62,39 @@ public class UserAnalyseController {
 		return R.ok(result);
 	}
 
-	@GetMapping("/new/trend/export")
-	@Operation(summary = "导出新增趋势")
-	public R exportNewUserTrend() {
-		return R.ok();
+	@ResponseExcel(name = "新增趋势详情")
+	@PostMapping("/new/trend/export")
+	@Operation(summary = "导出新增趋势详情")
+	public List<PageNewUserTrendDetailVO> exportNewUserTrend(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		// 初始化结果集
+		List<PageNewUserTrendDetailVO> allRecords = new ArrayList<>();
+		// 分页查询参数
+		PageUserAnalyseDTO pageQuery = new PageUserAnalyseDTO();
+		BeanUtils.copyProperties(reqDto, pageQuery);
+		// 每次查询的批次大小,根据实际内存情况调整
+		long batchSize = 1000;
+		pageQuery.setSize(batchSize);
+		pageQuery.setCurrent(1);
+
+		while (true) {
+			// 分批查询数据
+			Page<PageNewUserTrendDetailVO> pageResult = userAnalyseService.pageNewUserTrendDetail(pageQuery);
+			List<PageNewUserTrendDetailVO> records = pageResult.getRecords();
+			if (CollectionUtils.isEmpty(records)) {
+				// 没有更多数据,退出循环
+				break;
+			}
+			// 添加到结果集
+			allRecords.addAll(records);
+			// 如果当前页记录数小于批次大小,说明是最后一页
+			if (records.size() < batchSize) {
+				break;
+			}
+			// 准备查询下一页
+			pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+		}
+
+		return allRecords;
 	}
 
 	@PostMapping("/new/retention")
@@ -66,10 +110,39 @@ public class UserAnalyseController {
 		return R.ok(result);
 	}
 
-	@GetMapping("/new/retention/export")
-	@Operation(summary = "导出次日留存率")
-	public R exportNewUserRetention() {
-		return R.ok();
+	@ResponseExcel(name = "次日留存率详情")
+	@PostMapping("/new/retention/export")
+	@Operation(summary = "导出次日留存率详情")
+	public List<PageRetentionDetailVO> exportNewUserRetention(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		// 初始化结果集
+		List<PageRetentionDetailVO> allRecords = new ArrayList<>();
+		// 分页查询参数
+		PageUserAnalyseDTO pageQuery = new PageUserAnalyseDTO();
+		BeanUtils.copyProperties(reqDto, pageQuery);
+		// 每次查询的批次大小,根据实际内存情况调整
+		long batchSize = 1000;
+		pageQuery.setSize(batchSize);
+		pageQuery.setCurrent(1);
+
+		while (true) {
+			// 分批查询数据
+			Page<PageRetentionDetailVO> pageResult = userAnalyseService.pageNewUserRetentionDetail(pageQuery);
+			List<PageRetentionDetailVO> records = pageResult.getRecords();
+			if (CollectionUtils.isEmpty(records)) {
+				// 没有更多数据,退出循环
+				break;
+			}
+			// 添加到结果集
+			allRecords.addAll(records);
+			// 如果当前页记录数小于批次大小,说明是最后一页
+			if (records.size() < batchSize) {
+				break;
+			}
+			// 准备查询下一页
+			pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+		}
+
+		return allRecords;
 	}
 
 /***************************************** 活跃用户 *****************************************/
@@ -116,10 +189,64 @@ public class UserAnalyseController {
 		return R.ok(result);
 	}
 
-	@GetMapping("/active/export")
+	@PostMapping("/active/export")
 	@Operation(summary = "导出活跃详情")
-	public R exportActiveDetail() {
-		return R.ok();
+	public void exportActiveDetail(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto, HttpServletResponse response) throws Exception {
+		// 1. 根据时间单位确定需要排除的字段
+		List<String> excludeFields = new ArrayList<>();
+		String timeUnit = reqDto.getTimeUnit();
+		switch (timeUnit) {
+			case "day":
+				excludeFields.add("monthActiveUserRate");
+				excludeFields.add("weekActiveUserRate");
+				break;
+			case "week":
+				excludeFields.add("monthActiveUserRate");
+				excludeFields.add("mauRate");
+				excludeFields.add("wauRate");
+				break;
+			case "month":
+				excludeFields.add("weekActiveUserRate");
+				excludeFields.add("mauRate");
+				excludeFields.add("wauRate");
+				break;
+			default:
+				// 可根据需要处理默认情况
+				break;
+		}
+
+		// 2. 分页查询数据(保持原有逻辑)
+		List<PageActiveDetailVO> allRecords = new ArrayList<>();
+		PageUserAnalyseDTO pageQuery = new PageUserAnalyseDTO();
+		BeanUtils.copyProperties(reqDto, pageQuery);
+		long batchSize = 1000;
+		pageQuery.setSize(batchSize);
+		pageQuery.setCurrent(1);
+
+		while (true) {
+			Page<PageActiveDetailVO> pageResult = userAnalyseService.pageActiveDetail(pageQuery);
+			List<PageActiveDetailVO> records = pageResult.getRecords();
+			if (CollectionUtils.isEmpty(records)) {
+				break;
+			}
+			allRecords.addAll(records);
+			if (records.size() < batchSize) {
+				break;
+			}
+			pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+		}
+
+		// 3. 动态设置Excel响应和导出字段
+		response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+		response.setCharacterEncoding("utf-8");
+		String fileName = URLEncoder.encode("活跃详情", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
+		response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+		// 使用EasyExcel编程式导出,指定排除字段
+		EasyExcel.write(response.getOutputStream(), PageActiveDetailVO.class)
+				.excludeColumnFiledNames(excludeFields)
+				.sheet("活跃详情")
+				.doWrite(allRecords);
 	}
 
 /***************************************** 启动次数 *****************************************/
@@ -137,10 +264,39 @@ public class UserAnalyseController {
 		return R.ok(result);
 	}
 
-	@GetMapping("/launch/export")
+	@ResponseExcel(name = "启动次数详情")
+	@PostMapping("/launch/export")
 	@Operation(summary = "导出启动次数详情")
-	public R exportLaunchDetail() {
-		return R.ok();
+	public List<PageLaunchDetailVO> exportLaunchDetail(@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto) {
+		// 初始化结果集
+		List<PageLaunchDetailVO> allRecords = new ArrayList<>();
+		// 分页查询参数
+		PageUserAnalyseDTO pageQuery = new PageUserAnalyseDTO();
+		BeanUtils.copyProperties(reqDto, pageQuery);
+		// 每次查询的批次大小,根据实际内存情况调整
+		long batchSize = 1000;
+		pageQuery.setSize(batchSize);
+		pageQuery.setCurrent(1);
+
+		while (true) {
+			// 分批查询数据
+			Page<PageLaunchDetailVO> pageResult = userAnalyseService.pageLaunchDetail(pageQuery);
+			List<PageLaunchDetailVO> records = pageResult.getRecords();
+			if (CollectionUtils.isEmpty(records)) {
+				// 没有更多数据,退出循环
+				break;
+			}
+			// 添加到结果集
+			allRecords.addAll(records);
+			// 如果当前页记录数小于批次大小,说明是最后一页
+			if (records.size() < batchSize) {
+				break;
+			}
+			// 准备查询下一页
+			pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+		}
+
+		return allRecords;
 	}
 
 /***************************************** 版本分布 *****************************************/
@@ -152,10 +308,53 @@ public class UserAnalyseController {
 		return R.ok(result);
 	}
 
+	@ResponseExcel(name = "全部版本详情", sheets = {
+			@Sheet(sheetName = "今日"),
+			@Sheet(sheetName = "昨日")
+	})
 	@GetMapping("/version/export")
 	@Operation(summary = "导出全部版本详情")
-	public R exportAllVersionDetail() {
-		return R.ok();
+	public List<List<PageAllVersionDetailVO>> exportAllVersionDetail(
+			@Schema(description = "应用ID",example = "Fqs2CL9CUn7U1AqilSFXgb")
+			@ParameterObject String appId) {
+		if (appId == null || appId.isEmpty()){
+			throw new BusinessException("appId不能为空");
+		}
+
+		List<List<PageAllVersionDetailVO>> result = new ArrayList<>();
+		for (int i = 0; i < 2; i++) {
+			// 初始化结果集
+			List<PageAllVersionDetailVO> allRecords = new ArrayList<>();
+			// 分页查询参数
+			PageAllVersionDetailDTO pageQuery = new PageAllVersionDetailDTO();
+			pageQuery.setAppId(appId);
+			pageQuery.setQueryDate(i);
+			// 每次查询的批次大小,根据实际内存情况调整
+			long batchSize = 1000;
+			pageQuery.setSize(batchSize);
+			pageQuery.setCurrent(1);
+
+			while (true) {
+				// 分批查询数据
+				Page<PageAllVersionDetailVO> pageResult = userAnalyseService.pageAllVersionDetail(pageQuery);
+				List<PageAllVersionDetailVO> records = pageResult.getRecords();
+				if (CollectionUtils.isEmpty(records)) {
+					// 没有更多数据,退出循环
+					break;
+				}
+				// 添加到结果集
+				allRecords.addAll(records);
+				// 如果当前页记录数小于批次大小,说明是最后一页
+				if (records.size() < batchSize) {
+					break;
+				}
+				// 准备查询下一页
+				pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+			}
+			result.add(allRecords);
+		}
+
+		return result;
 	}
 
 	@PostMapping("/version/single/detail")
@@ -165,10 +364,54 @@ public class UserAnalyseController {
 		return R.ok(result);
 	}
 
-	@GetMapping("/version/single/export")
+	@PostMapping("/version/single/export")
 	@Operation(summary = "导出单版本详情")
-	public R exportSingleVersionDetail() {
-		return R.ok();
+	public void exportSingleVersionDetail(
+			@Valid @RequestBody UserAnalyseQueryBaseDTO reqDto,
+			HttpServletResponse response) throws Exception {
+
+		// 获取版本并验证
+		if (reqDto.getVersion() == null || reqDto.getVersion().isEmpty()){
+			throw new BusinessException("版本不能为空");
+		}
+		String version = reqDto.getVersion().get(0);
+		String sheetName = version + "版本详情"; // 动态生成表名
+
+		// 初始化结果集
+		List<PageSingleVersionDetailVO> allRecords = new ArrayList<>();
+		// 分页查询参数
+		PageUserAnalyseDTO pageQuery = new PageUserAnalyseDTO();
+		BeanUtils.copyProperties(reqDto, pageQuery);
+		long batchSize = 1000;
+		pageQuery.setSize(batchSize);
+		pageQuery.setCurrent(1);
+
+		// 分批查询数据
+		while (true) {
+			Page<PageSingleVersionDetailVO> pageResult = userAnalyseService.pageSingleVersionDetail(pageQuery);
+			List<PageSingleVersionDetailVO> records = pageResult.getRecords();
+			if (CollectionUtils.isEmpty(records)) {
+				break;
+			}
+			allRecords.addAll(records);
+			if (records.size() < batchSize) {
+				break;
+			}
+			pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+		}
+
+		// 设置响应头,动态生成文件名
+		String fileName = URLEncoder.encode(sheetName, StandardCharsets.UTF_8)
+				.replaceAll("\\+", "%20");
+		response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+		response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+		response.setHeader("Content-disposition",
+				"attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+		// 使用EasyExcel手动写入,指定动态表名
+		EasyExcel.write(response.getOutputStream(), PageSingleVersionDetailVO.class)
+				.sheet(sheetName) // 设置工作表名称
+				.doWrite(allRecords);
 	}
 
 	@PostMapping("/version/distribution")
@@ -186,10 +429,85 @@ public class UserAnalyseController {
 	}
 
 
-	@GetMapping("/version/distribution/export")
+	@PostMapping("/version/distribution/export")
 	@Operation(summary = "导出版本用户来源详情")
-	public R exportVersionDistributionDetail() {
-		return R.ok();
+	public void exportVersionDistributionDetail(
+			@Valid @RequestBody GetVersionDistributionDTO reqDto,
+			HttpServletResponse response) throws Exception {
+
+		// 1. 参数校验与获取
+		String version = reqDto.getVersion();
+		String type = reqDto.getType();
+		if (version == null || version.isEmpty()) {
+			throw new BusinessException("版本不能为空");
+		}
+		if (type == null || type.isEmpty()) {
+			throw new BusinessException("类型不能为空");
+		}
+
+		// 2. 动态生成文件名和表头
+		String fileName;
+		List<List<String>> head = new ArrayList<>();
+		String sheetName = version + "版本用户来源详情"; // 固定sheet名
+
+		switch (type) {
+			case "newUserChannel":
+				fileName = version + "版本新增用户渠道详情";
+				head.add(Collections.singletonList("渠道"));
+				head.add(Collections.singletonList("新增用户"));
+				head.add(Collections.singletonList("新增用户占比"));
+				break;
+			case "upgradeChannel":
+				fileName = version + "版本升级用户渠道详情";
+				head.add(Collections.singletonList("渠道"));
+				head.add(Collections.singletonList("升级用户"));
+				head.add(Collections.singletonList("升级用户占比"));
+				break;
+			case "upgradeVersion":
+				fileName = version + "版本升级用户版本详情";
+				head.add(Collections.singletonList("版本"));
+				head.add(Collections.singletonList("升级用户"));
+				head.add(Collections.singletonList("升级用户占比"));
+				break;
+			default:
+				throw new BusinessException("不支持的导出类型: " + type);
+		}
+
+		// 3. 设置Excel响应头(确保文件类型正确)
+		response.reset();
+		response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+		response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+		String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
+		response.setHeader("Content-Disposition",
+				"attachment;filename=\"" + encodedFileName + ".xlsx\";" +
+						"filename*=utf-8''" + encodedFileName + ".xlsx");
+
+		// 4. 分页查询数据
+		List<PageVersionDistributionDetailVO> allRecords = new ArrayList<>();
+		PageVersionDistributionDetailDTO pageQuery = new PageVersionDistributionDetailDTO();
+		BeanUtils.copyProperties(reqDto, pageQuery);
+		long batchSize = 1000;
+		pageQuery.setSize(batchSize);
+		pageQuery.setCurrent(1);
+
+		while (true) {
+			Page<PageVersionDistributionDetailVO> pageResult = userAnalyseService.pageVersionDistributionDetail(pageQuery);
+			List<PageVersionDistributionDetailVO> records = pageResult.getRecords();
+			if (records == null || records.isEmpty()) {
+				break;
+			}
+			allRecords.addAll(records);
+			if (records.size() < batchSize) {
+				break;
+			}
+			pageQuery.setCurrent(pageQuery.getCurrent() + 1);
+		}
+
+		// 使用EasyExcel手动写入,指定动态表名
+		EasyExcel.write(response.getOutputStream())
+				.head(head)
+				.sheet(sheetName)
+				.doWrite(allRecords);
 	}
 
 }

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

@@ -348,6 +348,9 @@ public class UserAnalyseServiceImpl implements UserAnalyseService {
 	 */
 	@Override
 	public Page<PageRetentionDetailVO> pageNewUserRetentionDetail(PageUserAnalyseDTO reqDto) {
+		if (!"day".equals(reqDto.getTimeUnit())) {
+			throw new BusinessException("时间单位只能为day");
+		}
 		// 初始化分页对象并计算总条数
 		Page<PageRetentionDetailVO> page = new Page<>(reqDto.getCurrent(), reqDto.getSize());
 		page.setTotal(calculateTotalCount(reqDto.getFromDate(), reqDto.getToDate(), reqDto.getTimeUnit()));