Compare commits
5 Commits
93186db1f8
...
0c9b62209e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c9b62209e | ||
|
|
da89f15296 | ||
|
|
b618f833d1 | ||
|
|
c05fd40a06 | ||
|
|
6f62c54f7a |
@@ -0,0 +1,133 @@
|
|||||||
|
package com.zt.plat.framework.common.util.asyncTask;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步任务同步处理工具类
|
||||||
|
* 多次提交,一次等待
|
||||||
|
*/
|
||||||
|
public class AsyncLatchUtils {
|
||||||
|
private static final ThreadLocal<List<TaskInfo>> THREADLOCAL = ThreadLocal.withInitial(LinkedList::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交一个异步任务
|
||||||
|
* @param executor 指定执行此任务的线程池
|
||||||
|
* @param runnable 需要异步执行的具体业务逻辑
|
||||||
|
*/
|
||||||
|
public static void submitTask(Executor executor, Runnable runnable) {
|
||||||
|
THREADLOCAL.get().add(new TaskInfo(executor, runnable));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前线程已提交的任务列表,并自动清理当前线程的已提交任务列表。
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static List<TaskInfo> popTask() {
|
||||||
|
List<TaskInfo> taskInfos = THREADLOCAL.get();
|
||||||
|
THREADLOCAL.remove();
|
||||||
|
return taskInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发所有已提交任务的执行,并同步等待它们全部完成。
|
||||||
|
* @param timeout 最长等待时间
|
||||||
|
* @param timeUnit 等待时间单位
|
||||||
|
* @return true: 如果所有任务在指定时间内成功完成。false: 如果等待超时。
|
||||||
|
* 该方法在执行后会自动清理当前线程提交的任务列表,因此可以重复使用。
|
||||||
|
*/
|
||||||
|
public static boolean waitFor(long timeout, TimeUnit timeUnit) {
|
||||||
|
List<TaskInfo> taskInfos = popTask();
|
||||||
|
if (taskInfos.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
CountDownLatch latch = new CountDownLatch(taskInfos.size());
|
||||||
|
for (TaskInfo taskInfo : taskInfos) {
|
||||||
|
Executor executor = taskInfo.executor;
|
||||||
|
Runnable runnable = taskInfo.runnable;
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
boolean await = false;
|
||||||
|
try {
|
||||||
|
await = latch.await(timeout, timeUnit);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return await;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TaskInfo {
|
||||||
|
private final Executor executor;
|
||||||
|
private final Runnable runnable;
|
||||||
|
|
||||||
|
public TaskInfo(Executor executor, Runnable runnable) {
|
||||||
|
this.executor = executor;
|
||||||
|
this.runnable = runnable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用样例
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 1. 准备一个线程池
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(3);
|
||||||
|
|
||||||
|
System.out.println("主流程开始,准备分发异步任务...");
|
||||||
|
|
||||||
|
// 2. 提交多个异步任务
|
||||||
|
// 任务一:获取用户信息
|
||||||
|
AsyncLatchUtils.submitTask(executorService, () -> {
|
||||||
|
try {
|
||||||
|
System.out.println("开始获取用户信息...");
|
||||||
|
Thread.sleep(1000); // 模拟耗时
|
||||||
|
System.out.println("获取用户信息成功!");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 任务二:获取订单信息
|
||||||
|
AsyncLatchUtils.submitTask(executorService, () -> {
|
||||||
|
try {
|
||||||
|
System.out.println("开始获取订单信息...");
|
||||||
|
Thread.sleep(1500); // 模拟耗时
|
||||||
|
System.out.println("获取订单信息成功!");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 任务三:获取商品信息
|
||||||
|
AsyncLatchUtils.submitTask(executorService, () -> {
|
||||||
|
try {
|
||||||
|
System.out.println("开始获取商品信息...");
|
||||||
|
Thread.sleep(500); // 模拟耗时
|
||||||
|
System.out.println("获取商品信息成功!");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("所有异步任务已提交,主线程开始等待...");
|
||||||
|
|
||||||
|
// 3. 等待所有任务完成,最长等待5秒
|
||||||
|
boolean allTasksCompleted = AsyncLatchUtils.waitFor(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// 4. 根据等待结果继续主流程
|
||||||
|
if (allTasksCompleted) {
|
||||||
|
System.out.println("所有异步任务执行成功,主流程继续...");
|
||||||
|
} else {
|
||||||
|
System.err.println("有任务执行超时,主流程中断!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 关闭线程池
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ public class GatewayWebClientConfiguration {
|
|||||||
private final int maxInMemorySize;
|
private final int maxInMemorySize;
|
||||||
|
|
||||||
public GatewayWebClientConfiguration(
|
public GatewayWebClientConfiguration(
|
||||||
@Value("${databus.gateway.web-client.max-in-memory-size:2097152}") int maxInMemorySize) {
|
@Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize) {
|
||||||
this.maxInMemorySize = maxInMemorySize;
|
this.maxInMemorySize = maxInMemorySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ public class FileController {
|
|||||||
return success(fileWhitReturn);
|
return success(fileWhitReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload-base64-with-return")
|
||||||
|
@Operation(summary = "上传文件(Base64编码)携带返回实体", description = "支持通过Base64编码上传文件内容,并返回完整文件信息")
|
||||||
|
public CommonResult<FileRespVO> uploadFileBase64WithReturn(@Valid @RequestBody FileUploadBase64ReqVO uploadReqVO) throws Exception {
|
||||||
|
FileRespVO fileRespVO = fileService.createFileFromBase64WithReturn(uploadReqVO);
|
||||||
|
return success(fileRespVO);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/presigned-url")
|
@GetMapping("/presigned-url")
|
||||||
@Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
|
@Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
|
||||||
@Parameters({
|
@Parameters({
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.zt.plat.module.infra.controller.admin.file.vo.file;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenbowen
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - 上传文件(Base64编码) Request VO")
|
||||||
|
@Data
|
||||||
|
public class FileUploadBase64ReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "文件内容(Base64编码)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "文件内容不能为空")
|
||||||
|
private String base64Content;
|
||||||
|
|
||||||
|
@Schema(description = "文件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "example.pdf")
|
||||||
|
@NotBlank(message = "文件名称不能为空")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@Schema(description = "文件目录", example = "XXX/YYY")
|
||||||
|
private String directory;
|
||||||
|
|
||||||
|
@Schema(description = "是否加密", example = "false")
|
||||||
|
private Boolean encrypt = false;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.zt.plat.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
|
|||||||
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePageReqVO;
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePageReqVO;
|
||||||
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
||||||
import com.zt.plat.module.infra.controller.admin.file.vo.file.FileRespVO;
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FileRespVO;
|
||||||
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FileUploadBase64ReqVO;
|
||||||
import com.zt.plat.module.infra.dal.dataobject.file.FileDO;
|
import com.zt.plat.module.infra.dal.dataobject.file.FileDO;
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
@@ -41,6 +42,14 @@ public interface FileService {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
FileRespVO createFileWhitReturn(byte[] content, String name, String directory, String type, Boolean encrypt);
|
FileRespVO createFileWhitReturn(byte[] content, String name, String directory, String type, Boolean encrypt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 Base64 编码内容保存文件,并返回完整的文件信息
|
||||||
|
*
|
||||||
|
* @param uploadReqVO Base64上传请求VO
|
||||||
|
* @return 文件信息
|
||||||
|
*/
|
||||||
|
FileRespVO createFileFromBase64WithReturn(FileUploadBase64ReqVO uploadReqVO) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成文件预签名地址信息
|
* 生成文件预签名地址信息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.zt.plat.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
|
|||||||
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePageReqVO;
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePageReqVO;
|
||||||
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
||||||
import com.zt.plat.module.infra.controller.admin.file.vo.file.FileRespVO;
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FileRespVO;
|
||||||
|
import com.zt.plat.module.infra.controller.admin.file.vo.file.FileUploadBase64ReqVO;
|
||||||
import com.zt.plat.module.infra.dal.dataobject.file.FileDO;
|
import com.zt.plat.module.infra.dal.dataobject.file.FileDO;
|
||||||
import com.zt.plat.module.infra.dal.mysql.file.FileMapper;
|
import com.zt.plat.module.infra.dal.mysql.file.FileMapper;
|
||||||
import com.zt.plat.module.infra.dal.redis.RedisKeyConstants;
|
import com.zt.plat.module.infra.dal.redis.RedisKeyConstants;
|
||||||
@@ -133,6 +134,38 @@ public class FileServiceImpl implements FileService {
|
|||||||
return BeanUtils.toBean(entity, FileRespVO.class);
|
return BeanUtils.toBean(entity, FileRespVO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileRespVO createFileFromBase64WithReturn(FileUploadBase64ReqVO uploadReqVO) throws Exception {
|
||||||
|
// 1. 解码 Base64 内容
|
||||||
|
byte[] content = decodeBase64Content(uploadReqVO.getBase64Content());
|
||||||
|
|
||||||
|
// 2. 推断文件类型
|
||||||
|
String contentType = FileTypeUtils.getMineType(content, uploadReqVO.getFileName());
|
||||||
|
|
||||||
|
// 3. 上传文件并返回完整信息
|
||||||
|
return createFileWhitReturn(content, uploadReqVO.getFileName(), uploadReqVO.getDirectory(),
|
||||||
|
contentType, uploadReqVO.getEncrypt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 Base64 内容
|
||||||
|
*
|
||||||
|
* @param base64Content Base64 编码的内容
|
||||||
|
* @return 解码后的字节数组
|
||||||
|
*/
|
||||||
|
private byte[] decodeBase64Content(String base64Content) {
|
||||||
|
try {
|
||||||
|
// 移除可能存在的 Base64 数据 URL 前缀 (例如: data:image/png;base64,)
|
||||||
|
String actualBase64 = base64Content;
|
||||||
|
if (base64Content.contains(",")) {
|
||||||
|
actualBase64 = base64Content.substring(base64Content.indexOf(",") + 1);
|
||||||
|
}
|
||||||
|
return Base64.getDecoder().decode(actualBase64);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw exception(FILE_IS_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private FileDO uploadFile(byte[] content, String name, String directory, String type, Boolean encrypt) throws Exception {
|
private FileDO uploadFile(byte[] content, String name, String directory, String type, Boolean encrypt) throws Exception {
|
||||||
|
|
||||||
String aesIvBase64 = null;
|
String aesIvBase64 = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user