Browse Source

draft: consistent async exports

dengjia 4 days ago
parent
commit
e2711bbb2e

+ 5 - 0
hnqz-common/hnqz-common-core/src/main/java/com/qunzhixinxi/hnqz/common/core/constant/CommonConstants.java

@@ -82,4 +82,9 @@ public interface CommonConstants {
 	 */
 	String IMAGE_CODE_TYPE = "blockPuzzle";
 
+	/**
+	 * 报告文件有效期
+	 */
+	long DEF_REPORT_TTL = 7L * 24 * 60 * 60 * 1000;
+
 }

+ 2 - 1
hnqz-upms/hnqz-upms-api/src/main/java/com/qunzhixinxi/hnqz/admin/api/constant/enums/ExportType.java

@@ -15,7 +15,8 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum ExportType {
 
-	USER("USER", "人员信息");
+	USER("USER", "人员信息"),
+	CHECK_SUP("CHECK_SUP", "审核监督信息");
 
 	/**
 	 * 类型

+ 8 - 4
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/controller/init/InitController.java

@@ -1466,9 +1466,9 @@ public class InitController {
     return R.ok(r);
   }
 
-  @ResponseExcel(
+  /* @ResponseExcel(
       name = "taskCheckHistoryExcel",
-      sheets = {@Sheet(sheetName = "审核信息")})
+      sheets = {@Sheet(sheetName = "审核信息")}) */
   @GetMapping(value = "/cnbg/init/task/check/his/export")
   public List<TaskCheckHistoryExcelModel> exportTaskCheckHistoryExcel(
     @RequestParam(value = "taskPeriod", required = true) LocalDate[] taskPeriod) {
@@ -1481,8 +1481,12 @@ public class InitController {
       throw new RuntimeException("没有导出审核监督明细的权限");
     }
 
-    return wmTaskSupervisionService.selectTaskSupProcess(SecurityUtils.getUser(), taskPeriod);
-    
+    // sync
+    // return wmTaskSupervisionService.selectTaskSupProcess(SecurityUtils.getUser(), taskPeriod);
+
+    // async
+    wmTaskSupervisionService.asyncExportTaskCheckSup(SecurityUtils.getUser(), taskPeriod);
+    return List.of();
   }
 
   private Map<String, String> getInfo(

+ 26 - 0
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/event/TaskCheckSupExportEvent.java

@@ -0,0 +1,26 @@
+package com.qunzhixinxi.hnqz.admin.event;
+
+import com.qunzhixinxi.hnqz.common.security.service.HnqzUser;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.time.LocalDate;
+
+/**
+ * 任务审核监督导出事件
+ *
+ */
+@Getter
+@AllArgsConstructor
+public class TaskCheckSupExportEvent {
+
+	/**
+	 * 用户
+	 */
+	private HnqzUser user;
+
+	/**
+	 * 查询参数
+	 */
+	private LocalDate[] taskPeriod;
+}

+ 37 - 0
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/listener/TaskCheckSupExportEventListener.java

@@ -0,0 +1,37 @@
+package com.qunzhixinxi.hnqz.admin.listener;
+
+import com.qunzhixinxi.hnqz.admin.event.TaskCheckSupExportEvent;
+import com.qunzhixinxi.hnqz.admin.event.UserExportEvent;
+import com.qunzhixinxi.hnqz.admin.service.SysUserExportService;
+import com.qunzhixinxi.hnqz.admin.service.WmTaskSupervisionService;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
+
+/**
+ * 任务审核监督导出事件监听器
+ *
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Configuration
+public class TaskCheckSupExportEventListener {
+
+	private final WmTaskSupervisionService wmTaskSupervisionService;
+
+	/**
+	 * 推送事件
+	 *
+	 * @param event 事件
+	 */
+	@Async
+	@Order
+	@EventListener(TaskCheckSupExportEvent.class)
+	public void pushEvent(TaskCheckSupExportEvent event) {
+		wmTaskSupervisionService.selectTaskSupProcess(event.getUser(), event.getTaskPeriod());
+	}
+}

+ 7 - 0
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/service/WmTaskSupervisionService.java

@@ -59,5 +59,12 @@ public interface WmTaskSupervisionService extends IService<WmTaskSupervision> {
      * @return
      */
     List<TaskCheckHistoryExcelModel> selectTaskSupProcess(HnqzUser user,  LocalDate[] taskPeriod);
+
+    /**
+     * 异步导出
+     * @param user
+     * @param taskPeriod
+     */
+    void asyncExportTaskCheckSup(HnqzUser user, LocalDate[] taskPeriod);
 }
 

+ 69 - 146
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/service/impl/SysUserExportServiceImpl.java

@@ -32,7 +32,10 @@ import com.qunzhixinxi.hnqz.admin.mapper.SysUserRoleMapper;
 import com.qunzhixinxi.hnqz.admin.service.SysFileService;
 import com.qunzhixinxi.hnqz.admin.service.SysUserAreaService;
 import com.qunzhixinxi.hnqz.admin.service.SysUserExportService;
+import com.qunzhixinxi.hnqz.admin.util.ExportUtils;
 import com.qunzhixinxi.hnqz.admin.util.OsEnvUtils;
+import com.qunzhixinxi.hnqz.admin.util.RedisUtils;
+import com.qunzhixinxi.hnqz.common.core.constant.CommonConstants;
 import com.qunzhixinxi.hnqz.common.core.exception.BizException;
 import com.qunzhixinxi.hnqz.common.core.util.SpringContextHolder;
 import com.qunzhixinxi.hnqz.common.security.service.HnqzUser;
@@ -77,15 +80,10 @@ public class SysUserExportServiceImpl implements SysUserExportService {
 	private final SysUserAreaService userAreaService;
 	private final SysDeptMapper deptMapper;
 	private final SysUserCertificateMapper userCertificateMapper;
-	private final SysFileService fileService;
 	private final UpmsConfig upmsConfig;
 	private final RetryTemplate retryTemplate;
-
-	private static final long DEF_REPORT_TTL = 7L * 24 * 60 * 60 * 1000;
-	private static final String ERROR_MSG_UNKNOWN = "ERROR_未知错误,请联系管理员";
-	private static final String ERROR_MSG_NO_DATA = "ERROR_没有数据";
-	private static final String ERROR_MSG_UPLOAD_FAIL = "ERROR_上传OSS失败";
-
+	private final RedisUtils redisUtils;
+	private final ExportUtils exportUtils;
 
 	/**
 	 * 异步导出
@@ -99,34 +97,10 @@ public class SysUserExportServiceImpl implements SysUserExportService {
 	@Override
 	@ExportGuard(type = "USER")
 	public Boolean asyncExport(HnqzUser user, List<Integer> roles, SysUserDTO.OnList query) {
-		return retryTemplate.execute(retryContext -> {
-			log.info("人员异步导出第{}次重试", retryContext.getRetryCount());
-
-			// 使用Redis原子操作实现限流
-			String key = CacheConstants.ASYNC_EXPORT_LIMIT_KEY;
-			Long increment = redisTemplate.opsForValue().increment(key);
-
-			// 设置key的过期时间,避免计数器无限增长
-			if (increment != null && increment.equals(1L)) {
-				redisTemplate.expire(key, 1, TimeUnit.MINUTES);
-			}
-
-			// 检查是否超过限流阈值
-			if (increment != null && increment > upmsConfig.getAsyncExportLimit()) {
-				// 超过限流阈值时,减少计数器并抛出异常
-				redisTemplate.opsForValue().decrement(key);
-				throw new BizException("系统繁忙,请稍后再试");
-			}
-
-			// 缓存key
-			String cacheKey = String.format(CacheConstants.ASYNC_EXPORT_CACHE, ExportType.USER.getType(), user.getId());
-			// 更新状态为生成中
-			redisTemplate.opsForValue().set(cacheKey, WmReportOpt.WmReportOptStatus.GENERATING.name(), DEF_REPORT_TTL, TimeUnit.MILLISECONDS);
-
-			SpringContextHolder.getApplicationContext().publishEvent(new UserExportEvent(user, roles, query));
-			return Boolean.TRUE;
-		});
 
+		redisUtils.setExportStarting(ExportType.USER, user.getId());
+		SpringContextHolder.getApplicationContext().publishEvent(new UserExportEvent(user, roles, query));
+		return true;	
 	}
 
 	/**
@@ -139,114 +113,76 @@ public class SysUserExportServiceImpl implements SysUserExportService {
 	 */
 	@Override
 	public Boolean export(HnqzUser user, List<Integer> roles, SysUserDTO.OnList query) {
-		// 缓存key
-		String key = String.format(CacheConstants.ASYNC_EXPORT_CACHE, ExportType.USER.getType(), user.getId());
-
-		// 临时文件路径
-		String tempPath = OsEnvUtils.getEachEnvPaths().get(OsEnvUtils.TargetFile.TEMP.getName());
-		// 缓存文件名
-		String fileName = "人员_" + DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN)
-				.format(LocalDateTime.now()) + RandomStringUtils.randomNumeric(6) + ".xlsx";
-		String fullPath = tempPath + fileName;
-
 		try {
-			// 查询用户列表
-			List<SysUser> users = this.listUsers(user, roles, query);
+			return retryTemplate.execute(retryContext -> {
+				log.info("人员异步导出第{}次重试", retryContext.getRetryCount());
 
-			if (CollUtil.isEmpty(users)) {
-				log.info("用户列表为空");
-				redisTemplate.opsForValue().set(key, ERROR_MSG_NO_DATA, DEF_REPORT_TTL, TimeUnit.MILLISECONDS);
-				return Boolean.FALSE;
-			}
+				redisUtils.checkExportGlobalAllows(upmsConfig.getAsyncExportLimit());
 
-			// 构建Excel数据并导出
-			String resultValue = buildAndExportExcel(users, fullPath, fileName, user);
-			redisTemplate.opsForValue().set(key, resultValue, DEF_REPORT_TTL, TimeUnit.MILLISECONDS);
+				// 查询用户列表 构建Excel数据
+				List<SysUser> users = this.listUsers(user, roles, query);
+				List<SysUserExcelModel> data = buildExcelModel(users);
 
-			return !StrUtil.startWith(resultValue, "ERROR");
-		} finally {
-			// 清理临时文件
-			cleanupTempFile(fullPath);
+				// 导出
+				String resultValue = exportUtils.WriteExportExcel(ExportType.USER, data, SysUserExcelModel.class, user);
+				return !StrUtil.startWith(resultValue, "ERROR");
+			});
 
-			// 删除限流key
-			String limitKey = CacheConstants.ASYNC_EXPORT_LIMIT_KEY;
-			redisTemplate.delete(limitKey);
+		} finally {
+			// 释放全局限流
+			redisTemplate.opsForValue().decrement(CacheConstants.ASYNC_EXPORT_LIMIT_KEY);
 		}
 	}
+	
 
-	/**
-	 * 构建Excel数据并导出到文件
-	 *
-	 * @param users 用户列表
-	 * @param fullPath 完整文件路径
-	 * @param fileName 文件名
-	 * @param user 当前用户
-	 * @return 结果值(URL或错误信息)
-	 */
-	private String buildAndExportExcel(List<SysUser> users, String fullPath, String fileName, HnqzUser user) {
-		try {
-			// 查询企业信息
-			Map<Integer, SysDept> deptMap = queryDeptInfo(users);
-
-			// 查询角色信息
-			Map<Integer, String> roleMap = queryRoleInfo(users);
-			Map<Integer, List<SysUserRole>> userRolesMap = queryUserRoles(users);
-
-			// 查询备案信息
-			Map<Integer, Long> userCertMap = queryCertificateInfo(users);
-
-			// 转为excel列表
-			List<SysUserExcelModel> excelModels = users.stream().map(u -> {
-				SysUserExcelModel excelModel = BeanUtil.copyProperties(u, SysUserExcelModel.class, "lockFlag");
-				excelModel.setRealName(u.getRealname());
-				excelModel.setPhoneNumber(u.getPhone());
-				// 锁定状态
-				excelModel.setLockFlag("0".equals(u.getLockFlag()) ? "活跃" : "休眠");
-				// 创建时间
-				excelModel.setCreateTime(u.getCreateTime().format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
-
-				// 派工方
-				SysDept dept = deptMap.get(u.getDeptId());
-				if (dept != null) {
-					excelModel.setDeptName(dept.getName());
-				}
-
-				// 角色
-				List<SysUserRole> userRoleList = userRolesMap.get(u.getUserId());
-				if (CollUtil.isNotEmpty(userRoleList)) {
-					String roleNames = userRoleList.stream()
-							.map(userRole -> roleMap.getOrDefault(userRole.getRoleId(), ""))
-							.collect(Collectors.joining(","));
-					excelModel.setRoleList(roleNames);
-				}
-
-				// 备案状态
-				long certCount = userCertMap.getOrDefault(u.getUserId(), 0L);
-				excelModel.setCertificateFlag(certCount > 0 ? "是" : "否");
-
-				return excelModel;
-			}).collect(Collectors.toList());
-
-			// 写入excel文件
-			EasyExcel.write(fullPath, SysUserExcelModel.class).sheet("人员")
-					.doWrite(excelModels);
-			log.info("人员导出生成缓存文件:{}", fullPath);
-
-			// 上传oss
-			try (FileInputStream inputStream = new FileInputStream(fullPath)) {
-				Map<String, String> uploadResult = fileService.upload(inputStream, fileName, fileName, user.getUsername());
-				log.info("人员导出生成oss文件:{}", uploadResult);
-
-				if (CollUtil.isNotEmpty(uploadResult)) {
-					return uploadResult.get("url");
-				} else {
-					return ERROR_MSG_UPLOAD_FAIL;
-				}
-			}
-		} catch (Exception e) {
-			log.error("人员导出失败", e);
-			return ERROR_MSG_UNKNOWN;
+	private List<SysUserExcelModel> buildExcelModel(List<SysUser> users) {
+		if (CollUtil.isEmpty(users)) {
+			log.info("用户列表为空");
+			return List.of();
 		}
+		// 查询企业信息
+		Map<Integer, SysDept> deptMap = queryDeptInfo(users);
+
+		// 查询角色信息
+		Map<Integer, String> roleMap = queryRoleInfo(users);
+		Map<Integer, List<SysUserRole>> userRolesMap = queryUserRoles(users);
+
+		// 查询备案信息
+		Map<Integer, Long> userCertMap = queryCertificateInfo(users);
+
+		// 转为excel列表
+		List<SysUserExcelModel> excelModels = users.stream().map(u -> {
+			SysUserExcelModel excelModel = BeanUtil.copyProperties(u, SysUserExcelModel.class, "lockFlag");
+			excelModel.setRealName(u.getRealname());
+			excelModel.setPhoneNumber(u.getPhone());
+			// 锁定状态
+			excelModel.setLockFlag("0".equals(u.getLockFlag()) ? "活跃" : "休眠");
+			// 创建时间
+			excelModel.setCreateTime(u.getCreateTime().format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
+
+			// 派工方
+			SysDept dept = deptMap.get(u.getDeptId());
+			if (dept != null) {
+				excelModel.setDeptName(dept.getName());
+			}
+
+			// 角色
+			List<SysUserRole> userRoleList = userRolesMap.get(u.getUserId());
+			if (CollUtil.isNotEmpty(userRoleList)) {
+				String roleNames = userRoleList.stream()
+						.map(userRole -> roleMap.getOrDefault(userRole.getRoleId(), ""))
+						.collect(Collectors.joining(","));
+				excelModel.setRoleList(roleNames);
+			}
+
+			// 备案状态
+			long certCount = userCertMap.getOrDefault(u.getUserId(), 0L);
+			excelModel.setCertificateFlag(certCount > 0 ? "是" : "否");
+
+			return excelModel;
+		}).collect(Collectors.toList());
+
+		return excelModels;
 	}
 
 	/**
@@ -304,19 +240,6 @@ public class SysUserExportServiceImpl implements SysUserExportService {
 				.collect(Collectors.groupingBy(SysUserCertificate::getUserId, Collectors.counting()));
 	}
 
-	/**
-	 * 清理临时文件
-	 */
-	private void cleanupTempFile(String fullPath) {
-		try {
-			File file = new File(fullPath);
-			if (file.exists()) {
-				file.delete();
-			}
-		} catch (Exception e) {
-			log.warn("删除临时文件失败: {}", fullPath, e);
-		}
-	}
 
 
 	/**

+ 40 - 2
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/service/impl/WmTaskSupervisionServiceImpl.java

@@ -4,21 +4,31 @@ import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qunzhixinxi.hnqz.admin.api.constant.UpmsState;
+import com.qunzhixinxi.hnqz.admin.api.constant.enums.ExportType;
 import com.qunzhixinxi.hnqz.admin.api.dto.WmSupervisionDto;
 import com.qunzhixinxi.hnqz.admin.api.entity.WmTaskSupervision;
 import com.qunzhixinxi.hnqz.admin.api.model.excel.TaskCheckHistoryExcelModel;
+import com.qunzhixinxi.hnqz.admin.aspect.ExportGuard;
+import com.qunzhixinxi.hnqz.admin.config.UpmsConfig;
+import com.qunzhixinxi.hnqz.admin.event.TaskCheckSupExportEvent;
 import com.qunzhixinxi.hnqz.admin.mapper.WmTaskSupervisionMapper;
 import com.qunzhixinxi.hnqz.admin.service.WmTaskSupervisionService;
+import com.qunzhixinxi.hnqz.admin.util.ExportUtils;
+import com.qunzhixinxi.hnqz.admin.util.RedisUtils;
+import com.qunzhixinxi.hnqz.common.core.util.SpringContextHolder;
 import com.qunzhixinxi.hnqz.common.security.service.HnqzUser;
 import com.qunzhixinxi.hnqz.common.security.util.SecurityUtils;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.retry.support.RetryTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -33,6 +43,11 @@ import java.util.List;
 public class WmTaskSupervisionServiceImpl extends ServiceImpl<WmTaskSupervisionMapper, WmTaskSupervision> implements WmTaskSupervisionService {
 
     private final WmTaskSupervisionMapper wmTaskSupervisionMapper;
+	private final RetryTemplate retryTemplate;
+	private final RedisUtils redisUtils;
+	private final UpmsConfig upmsConfig;
+    private final ExportUtils exportUtils;
+
     /**
      * 创建审核
      *
@@ -126,9 +141,32 @@ public class WmTaskSupervisionServiceImpl extends ServiceImpl<WmTaskSupervisionM
     }
 
     @Override
-    // @ExportGuard
+    @ExportGuard(type = "CHECK_SUP")
+    public void asyncExportTaskCheckSup(HnqzUser user, LocalDate[] taskPeriod) {
+
+        redisUtils.setExportStarting(ExportType.CHECK_SUP, user.getId());
+        SpringContextHolder.getApplicationContext().publishEvent(new TaskCheckSupExportEvent(user, taskPeriod));
+    }
+
+    @Override
     public List<TaskCheckHistoryExcelModel> selectTaskSupProcess(HnqzUser user,  LocalDate[] taskPeriod) {
-        List<TaskCheckHistoryExcelModel> t = wmTaskSupervisionMapper.selectTaskSupProcess(LocalDateTime.of(taskPeriod[0], LocalTime.MIN), LocalDateTime.of(taskPeriod[1], LocalTime.MAX));
+        List<TaskCheckHistoryExcelModel> t = List.of();
+        try {
+            t = retryTemplate.execute(retryContext -> {
+                log.info("人员异步导出第{}次重试", retryContext.getRetryCount());
+
+                redisUtils.checkExportGlobalAllows(upmsConfig.getAsyncExportLimit());
+                List<TaskCheckHistoryExcelModel> data = 
+                    wmTaskSupervisionMapper.selectTaskSupProcess(LocalDateTime.of(taskPeriod[0], LocalTime.MIN), LocalDateTime.of(taskPeriod[1], LocalTime.MAX));
+                // 导出
+                exportUtils.WriteExportExcel(ExportType.CHECK_SUP, data, TaskCheckHistoryExcelModel.class, user);
+
+                return data;
+            });
+		} finally {
+            redisUtils.releaseExportGlobalAllows();
+		}
+
         return t;
     }
 }

+ 93 - 0
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/util/ExportUtils.java

@@ -0,0 +1,93 @@
+package com.qunzhixinxi.hnqz.admin.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DatePattern;
+import com.alibaba.excel.EasyExcel;
+import com.qunzhixinxi.hnqz.admin.api.constant.enums.ExportType;
+import com.qunzhixinxi.hnqz.admin.service.SysFileService;
+import com.qunzhixinxi.hnqz.common.core.constant.CommonConstants;
+import com.qunzhixinxi.hnqz.common.security.service.HnqzUser;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+/**
+ * async export 工具类
+ *
+ */
+@Slf4j
+@Component
+public final class ExportUtils {
+
+  @Resource private RedisTemplate<String, Object> redisTemplate;
+  @Resource private SysFileService fileService;
+
+  public static final String ERROR_MSG_UNKNOWN = "ERROR_未知错误,请联系管理员";
+	public static final String ERROR_MSG_NO_DATA = "ERROR_没有数据";
+	public static final String ERROR_MSG_UPLOAD_FAIL = "ERROR_上传OSS失败";
+
+	public String WriteExportExcel(ExportType exportType, List<?> data, Class head, HnqzUser user) {
+		// 临时文件路径
+		String tempPath = OsEnvUtils.getEachEnvPaths().get(OsEnvUtils.TargetFile.TEMP.getName());
+		// 缓存文件名
+		String fileName = exportType.getDescription() + DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN)
+				.format(LocalDateTime.now()) + RandomStringUtils.randomNumeric(6) + ".xlsx";
+		String fullPath = tempPath + fileName;
+
+		String ret = ERROR_MSG_UNKNOWN;
+		try {
+			// 写入excel文件
+			EasyExcel.write(fullPath, head).sheet(exportType.getDescription())
+					.doWrite(data);
+			log.info("{}导出生成缓存文件:{}", exportType.getDescription(), fullPath);
+
+			// 上传oss
+			try (FileInputStream inputStream = new FileInputStream(fullPath)) {
+				Map<String, String> uploadResult = fileService.upload(inputStream, fileName, fileName, user.getUsername());
+				log.info("{}导出生成oss文件:{}", exportType.getDescription(), uploadResult);
+
+				if (CollUtil.isNotEmpty(uploadResult)) {
+					ret = uploadResult.get("url");
+				} else {
+					ret = ERROR_MSG_UPLOAD_FAIL;
+				}
+			}
+		} catch (Exception e) {
+			log.error(exportType.getDescription() + "导出失败", e);
+			// 清理临时文件
+			cleanupTempFile(fullPath);
+			ret = ERROR_MSG_UNKNOWN;
+		}
+
+		// 缓存key
+		String key = ExportType.getAsyncExportCache(exportType, user.getId());
+		redisTemplate.opsForValue().set(key, ret, CommonConstants.DEF_REPORT_TTL, TimeUnit.MILLISECONDS);
+		return ret;
+	}
+
+  	/**
+	 * 清理临时文件
+	 */
+	private void cleanupTempFile(String fullPath) {
+		try {
+			File file = new File(fullPath);
+			if (file.exists()) {
+				file.delete();
+			}
+		} catch (Exception e) {
+			log.warn("删除临时文件失败: {}", fullPath, e);
+		}
+	}
+
+}

+ 35 - 0
hnqz-upms/hnqz-upms-biz/src/main/java/com/qunzhixinxi/hnqz/admin/util/RedisUtils.java

@@ -2,6 +2,11 @@ package com.qunzhixinxi.hnqz.admin.util;
 
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.IdUtil;
+
+import com.qunzhixinxi.hnqz.admin.api.constant.CacheConstants;
+import com.qunzhixinxi.hnqz.admin.api.constant.enums.ExportType;
+import com.qunzhixinxi.hnqz.admin.api.entity.WmReportOpt;
+import com.qunzhixinxi.hnqz.common.core.constant.CommonConstants;
 import com.qunzhixinxi.hnqz.common.core.exception.BizException;
 import java.time.LocalDateTime;
 import java.util.Collections;
@@ -61,6 +66,36 @@ public final class RedisUtils {
     return new Token(token, value, LocalDateTime.now().plusSeconds(DEF_DURATION));
   }
 
+  public void checkExportGlobalAllows(int max) {
+    // 使用Redis原子操作实现限流
+			String key = CacheConstants.ASYNC_EXPORT_LIMIT_KEY;
+			Long increment = redisTemplate.opsForValue().increment(key);
+
+			// 设置key的过期时间,避免计数器无限增长
+			if (increment != null && increment.equals(1L)) {
+				redisTemplate.expire(key, 1, TimeUnit.MINUTES);
+			}
+
+			// 检查是否超过限流阈值
+			if (increment != null && increment > max) {
+				// 超过限流阈值时,减少计数器并抛出异常
+				redisTemplate.opsForValue().decrement(key);
+				throw new BizException("系统繁忙,请稍后再试");
+			}
+  }
+
+  public void setExportStarting(ExportType exportType, Integer userId) {
+      // 缓存key
+			String cacheKey = ExportType.getAsyncExportCache(exportType, userId);
+			// 更新状态为生成中
+			redisTemplate.opsForValue().set(cacheKey, WmReportOpt.WmReportOptStatus.GENERATING.name(), CommonConstants.DEF_REPORT_TTL, TimeUnit.MILLISECONDS);
+  }
+
+  public void releaseExportGlobalAllows() {
+      // 释放全局限流
+		  redisTemplate.opsForValue().decrement(CacheConstants.ASYNC_EXPORT_LIMIT_KEY);
+  }
+
   @Data
   @AllArgsConstructor
   public static class Token{