最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot異步導(dǎo)出文件的實現(xiàn)步驟

 更新時間:2025年09月04日 09:14:08   作者:月生_  
本文主要介紹了SpringBoot異步導(dǎo)出文件的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

序言

在工作當(dāng)中經(jīng)常會碰到文件下載的功能,當(dāng)文件較大時,如果使用原來的下載方式會導(dǎo)致下載進(jìn)度慢,甚至有可能存在請求超時的情況,所以封裝異步下載文件的功能是非常有必要的。我私下嘗試了,可以實現(xiàn),代碼已經(jīng)提交到我倉庫。

思路

既然是要實現(xiàn)異步下載,那么在發(fā)送下載文件的請求,頁面就不應(yīng)該處于等待狀態(tài),而是給一個提示,比如 開始導(dǎo)出文件 之類的,然后后臺開始執(zhí)行下載文件的代碼,下載完成后上傳到 Minio 中,然后將這訪問路徑存儲到數(shù)據(jù)庫表中,前段再去請求這個下載路徑就可以實現(xiàn)了,

代碼實現(xiàn)

前端發(fā)送文件下載的請求時,后端返回一個 processId 給前端,然后前端根據(jù)這個id一直輪詢查詢這個流程的狀態(tài),直到這個流程的狀態(tài)為完成時結(jié)束輪詢,然后就可以根據(jù)后端返回的訪問 url 地址來下載文件。

上面是一個大概的思路,下面來看實現(xiàn):

自定義注解

首先需要定義兩個注解 : ProcessRunnerProcessHandle

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProcessRunner {

    String processName() default "";

    ProcessType processType() default ProcessType.EXCEL_TYPE;

    String description() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ProcessHandle {

    String value() default "";

    ProcessType processType() default ProcessType.EXCEL_TYPE;
}

ProcessType 定義如下:

public enum ProcessType {

    EXCEL_TYPE("Excel類型", ProcessTypeEnum.EXCEL_TYPE);

    private final String value;

    private final ProcessTypeEnum type;

    ProcessType(String value, ProcessTypeEnum type) {
        this.value = value;
        this.type = type;
    }

    public String getValue() {
        return value;
    }
    public ProcessTypeEnum getType() {
        return type;
    }

    public enum ProcessTypeEnum {
        /**
         * 進(jìn)程類型
         */
        EXCEL_TYPE("Excel類型", "EXCEL_TYPE");

        private final String desc;
        private final String type;

        ProcessTypeEnum(String desc, String type) {
            this.desc = desc;
            this.type = type;
        }
        public String getDesc() {
            return desc;
        }
        public String getType() {
            return type;
        }
    }
}

這里的兩個注解主要用來做Aop切面和掃描使用的,具體如下:

AOP 切面

@Aspect
@Component
@Order(1)
@Slf4j
public class ProcessRunnerAop {

    @Resource
    private ProcessService processService;

    @Around("@annotation(processRunner)")
    public Object processRunner(ProceedingJoinPoint proceedingJoinPoint, ProcessRunner processRunner) throws Throwable {
        // 插入流程到數(shù)據(jù)庫表
        Object proceed = proceedingJoinPoint.proceed();
        if (proceed instanceof ProcessDTO processDTO) {
            Process process = new Process();
            process.setProcessId(processDTO.getProcessId());
            process.setProcessName(processRunner.processName());
            process.setDescription(processRunner.description());
            process.setProcessType(processRunner.processType().name());
            process.setStartTime(new Date());
            process.setStatus((byte) 0);
            process.setParams(JSONUtil.toJsonStr(processDTO));
            processService.insert(process);
        }
        return proceed;
    }
}

注解掃描

@Component
@Slf4j
public class ProcessManager implements BeanPostProcessor, PriorityOrdered {

    private static final Map<String, Invoker> INVOKER_MAP = new ConcurrentHashMap<>();

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public static final Pattern SERVICE_IMPL_BEAN_NAME_PATTERN = Pattern.compile("(?i)[.a-z]+ServiceImpl");


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> beanClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean) : bean.getClass();
        if (!SERVICE_IMPL_BEAN_NAME_PATTERN.matcher(beanClass.getName()).matches()) {
            return bean;
        }
        log.info("beanName:{}, beanType:{}", beanName, beanClass.getName());


        Method[] declaredMethods = ClassUtil.getDeclaredMethods(beanClass);
        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.isAnnotationPresent(ProcessHandle.class)) {
                ProcessHandle processHandle = declaredMethod.getAnnotation(ProcessHandle.class);
                if (INVOKER_MAP.containsKey(processHandle.processType().toString())) {
                    throw new RuntimeException("processType 重復(fù)" + processHandle.processType().toString());
                }
                Parameter[] parameters = declaredMethod.getParameters();
                // 校驗processId參數(shù)是否存在
                boolean processIdExist = false;
                for (Parameter parameter : parameters) {
                    String name = parameter.getName();
                    if (StrUtil.equalsIgnoreCase("processId", name)) {
                        processIdExist = true;
                        break;
                    }
                }
                if (!processIdExist) {
                    throw new RuntimeException("processId 參數(shù)不存在," + processHandle.processType().toString());
                }
                if (declaredMethod.getReturnType() != String.class) {
                    throw new RuntimeException("返回值必須為String," + processHandle.processType().toString());
                }
                INVOKER_MAP.put(processHandle.processType().name(), new Invoker(bean, declaredMethod, parameters));
            }
        }
        return bean;
    }

    public static void handleProcess(Long processId, boolean isEnd) {
        log.info("processId:{} isEnd:{}", processId, isEnd);
        ProcessService processService = SpringUtil.getBean(ProcessService.class);
        Process process = processService.queryById(processId);
        if (process == null) {
            throw new RuntimeException("processId 不存在" + processId);
        }
        if (isEnd) {
            process.setStatus((byte) 2);
        } else {
            process.setStatus((byte) 1);
        }
        processService.update(process);
        log.info("任務(wù)更新狀態(tài)成功");
    }

    public void startProcess(Long processId) {
        ProcessService processService = SpringUtil.getBean(ProcessService.class);
        Process process = processService.queryById(processId);
        if (process == null) {
            return;
        }
        String params = process.getParams();
        try {
            JsonNode jsonNode = OBJECT_MAPPER.readTree(params);
            Invoker invoker = INVOKER_MAP.get(process.getProcessType());
            Method method = invoker.getMethod();
            Parameter[] parameters = method.getParameters();
            Object[] args = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                Class<?> type = parameter.getType();
                String name = parameter.getName();
                if (Long.class.isAssignableFrom(type) && StrUtil.equalsIgnoreCase("processId", name)) {
                    args[i] = jsonNode.get(name).asLong();
                } else if (Map.class.isAssignableFrom(type)) {
                    JsonNode valueNode = jsonNode.get(name);
                    args[i] = OBJECT_MAPPER.convertValue(valueNode, type);
                }
            }
            method.setAccessible(true);
            Object url = method.invoke(INVOKER_MAP.get(process.getProcessType()).getBean(), args);
            if (url != null) {
                process.setStatus((byte) 2);
                process.setUrl(url.toString());
                processService.update(process);
            }
        } catch (JsonProcessingException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class Invoker {
        private Object bean;
        private Method method;
        private Object[] args;
    }
}

這段代碼的大體思路就是掃描所有以 ServiceImpl 來結(jié)尾的 Bean 對象,然后找到帶有 ProcessHandle 注解的方法,構(gòu)建成 Invoker對象存到 INVOKER_MAP 集合中。在這個里面不可以使用 Resource這類依賴注入的注解,即便使用了 Spring 也無法注入對象,因為 ProcessManager 實現(xiàn)了 PriorityOrdered 接口(實現(xiàn)這個接口是為了保證最先被初始化),這個 Bean 被實例化的時候,其他的 Bean 還沒被創(chuàng)建,注入一直都是空對象。

服務(wù)層代碼實現(xiàn)

@Override
@ProcessRunner(processName = "異步導(dǎo)出excel", processType = ProcessType.EXCEL_TYPE)
public ProcessDTO startProcess() {
    HashMap<String, Object> hashMap = new HashMap<>();
    hashMap.put("userId", 1L);
    Process process = new Process();
    process.setProcessName("異步導(dǎo)出excel");
    hashMap.put("bean", process);
    return ProcessDTO.createProcessDTO(IdUtil.getSnowflakeNextId(), hashMap);
}

@Override
@ProcessHandle(value = "異步導(dǎo)出excel", processType = ProcessType.EXCEL_TYPE)
public String downloadExcel(Long processId, Map<String, Object> data) {
    // 這里模擬下載excel文件,我就直接找一個本地excel文件,不使用poi來生成excel文件了
    log.info("userId:{}", data.get("userId"));
    log.info("bean:{}", data.get("bean"));
    File file = FileUtil.file("C:\Users\Administrator\Downloads\1.xlsx");
    ProcessManager.handleProcess(processId, true);
    try {
        minioUtils.putObject("test", file.getName(), FileUtil.getInputStream(file), FileUtil.size(file), FileUtil.getType(file));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return minioUtils.getObjectUrl("test", file.getName());
}

定時任務(wù)

服務(wù)層的代碼實現(xiàn)完畢,我們需要定義一個定時任務(wù)去后臺執(zhí)行文件的下載操作,后臺下載完成后,會把 Minio 的可訪問 url 同步到數(shù)據(jù)庫中,前端根據(jù)這個 url 下載就可以了,具體代碼如下:

/**
 * 定時任務(wù)
 * 表示上一次執(zhí)行完畢后,間隔2秒執(zhí)行下一次
 */
@Scheduled(fixedDelay = 2000)
public void run() {
    log.info("開始執(zhí)行定時任務(wù)");
    processService.run();
}

定時使用 HttpUtil 工具類去請求

@Override
public void run() {
    // 查詢出之前未開始的任務(wù)
    Process process = processMapper.listByStatus(0);
    if (process == null) {
        return;
    }
    // 開始執(zhí)行任務(wù)
    HashMap<String, Object> map = new HashMap<>();
    map.put("processId", process.getProcessId());
    HttpUtil.get("http://127.0.0.1:8080/processManager/startProcess?processId=" + process.getProcessId());
}

請求的具體方法如下

@RestController
@RequestMapping("/processManager")
public class ProcessManagerController {


    @Resource
    private ProcessManager processManager;

    @GetMapping("/startProcess")
    public void  startProcess(@RequestParam(value = "processId") Long processId) {
        processManager.startProcess(processId);
    }
}
public void startProcess(Long processId) {
    ProcessService processService = SpringUtil.getBean(ProcessService.class);
    Process process = processService.queryById(processId);
    if (process == null) {
        return;
    }
    String params = process.getParams();
    try {
        JsonNode jsonNode = OBJECT_MAPPER.readTree(params);
        Invoker invoker = INVOKER_MAP.get(process.getProcessType());
        Method method = invoker.getMethod();
        Parameter[] parameters = method.getParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Class<?> type = parameter.getType();
            String name = parameter.getName();
            if (Long.class.isAssignableFrom(type) && StrUtil.equalsIgnoreCase("processId", name)) {
                args[i] = jsonNode.get(name).asLong();
            } else if (Map.class.isAssignableFrom(type)) {
                JsonNode valueNode = jsonNode.get(name);
                args[i] = OBJECT_MAPPER.convertValue(valueNode, type);
            }
        }
        method.setAccessible(true);
        Object url = method.invoke(INVOKER_MAP.get(process.getProcessType()).getBean(), args);
        if (url != null) {
            process.setStatus((byte) 2);
            process.setUrl(url.toString());
            processService.update(process);
        }
    } catch (JsonProcessingException | IllegalAccessException | InvocationTargetException e) {
        throw new RuntimeException(e);
    }
}

這里下載完成后,數(shù)據(jù)庫表中任務(wù)的狀態(tài)和 url 地址就會更新

總結(jié)

我這里并沒有寫前端代碼,最后和大家口述一下大概的流程:

  1. 前端發(fā)送一個下載文件的請求,后端結(jié)構(gòu)返回一個 processId 值,并提示 “開始下載文件”
  2. 使用 AOP 切面,將這個 processId 存儲到數(shù)據(jù)庫,并記錄開始時間和任務(wù)狀態(tài)
  3. 后臺維護(hù)定時任務(wù)去定時查詢數(shù)據(jù)庫中未開始的下載任務(wù),根據(jù)時間升序排序
  4. 如果存在未完成的定時任務(wù),則請求 processManager 去觸發(fā)任務(wù)的下載,下載完成后會將任務(wù)的狀態(tài)標(biāo)記為已完成,同時更新文件的可訪問 url
  5. 前端觸發(fā)下載的請求后,根據(jù)返回的 processId 一直走接口去查詢?nèi)蝿?wù)的狀態(tài)嗎,如果返回的數(shù)據(jù)狀態(tài)為已完成,則直接訪問 url 就可以觸發(fā)瀏覽器的下載操作。

到此這篇關(guān)于SpringBoot異步導(dǎo)出文件的實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)SpringBoot異步導(dǎo)出 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringCloudGateway?自定義局部過濾器場景分析

    SpringCloudGateway?自定義局部過濾器場景分析

    這篇文章主要介紹了SpringCloudGateway?自定義局部過濾器場景分析,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-06-06
  • JVM垃圾收集器詳解

    JVM垃圾收集器詳解

    本文主要介紹了JVM垃圾收集器的相關(guān)知識。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • java開發(fā)時各類工具的使用規(guī)范

    java開發(fā)時各類工具的使用規(guī)范

    這篇文章主要介紹了java編碼時各類工具的使用規(guī)范,多人協(xié)作、共同開發(fā)一個項目,如果沒有統(tǒng)一的代碼規(guī)范的話,項目中的每個人都按照自己的習(xí)慣率性而為,就會導(dǎo)致整個項目的代碼看上去雜亂無章,可讀性非常差,并且持續(xù)增加后續(xù)的維護(hù)成本。對此感興趣可以來了解一下
    2020-07-07
  • 簡述JAVA中堆內(nèi)存與棧內(nèi)存的區(qū)別

    簡述JAVA中堆內(nèi)存與棧內(nèi)存的區(qū)別

    這篇文章主要介紹了JAVA中堆內(nèi)存與棧內(nèi)存的區(qū)別,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • Mybatis plus結(jié)合springboot使用

    Mybatis plus結(jié)合springboot使用

    本文主要介紹了MyBatisPlus使用SpringBoot數(shù)據(jù)庫操作,從添加依賴到測試,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-11-11
  • springmvc+mybatis 做分頁sql 語句實例代碼

    springmvc+mybatis 做分頁sql 語句實例代碼

    本文通過一段實例代碼給大家介紹了springmvc+mybatis 做分頁sql 語句的方法,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2017-07-07
  • 關(guān)于國際化、OGNL表達(dá)式語言

    關(guān)于國際化、OGNL表達(dá)式語言

    本篇文章,小編為大家介紹關(guān)于國際化、OGNL表達(dá)式語言,有需要的朋友可以參考一下
    2013-04-04
  • Java全版本特性與用法示例(含總覽?+?分版本要點)

    Java全版本特性與用法示例(含總覽?+?分版本要點)

    Java自JDK8開啟現(xiàn)代化演進(jìn)以來,始終遵循半年特性版本+兩年LTS(長期支持)版本的發(fā)布節(jié)奏,持續(xù)在語言表達(dá)力、并發(fā)性能、開發(fā)體驗、安全能力四大維度突破,這篇文章主要介紹了Java全版本特性與用法(含總覽+分版本要點)的相關(guān)資料,需要的朋友可以參考下
    2026-05-05
  • 一不小心就讓Java開發(fā)踩坑的fail-fast是個什么鬼?(推薦)

    一不小心就讓Java開發(fā)踩坑的fail-fast是個什么鬼?(推薦)

    這篇文章主要介紹了Java fail-fast,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • SpringBoot中給指定接口加上權(quán)限校驗的實現(xiàn)

    SpringBoot中給指定接口加上權(quán)限校驗的實現(xiàn)

    本文介紹了使用SpringSecurity為接口添加權(quán)限校驗,以防止外部訪問并確保安全性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12

最新評論

綦江县| 容城县| 依兰县| 浠水县| 石嘴山市| 德昌县| 阿瓦提县| 贺兰县| 楚雄市| 德惠市| 莫力| 三门县| 孝感市| 梅州市| 宜兰县| 玉龙| 新乐市| 张家口市| 萨迦县| 凯里市| 桂阳县| 揭东县| 巴南区| 炉霍县| 临武县| 子长县| 陆丰市| 南郑县| 中超| 驻马店市| 安宁市| 门头沟区| 磐石市| 武城县| 中阳县| 宜都市| 南皮县| 泉州市| 泰和县| 岱山县| 庐江县|