SpringBoot項(xiàng)目的多文件兼多線程上傳下載
前言
我們的項(xiàng)目目前需要在一個(gè)相冊(cè)中,上傳多個(gè)的圖片,因此,在一次的用戶提交過程中,會(huì)有多張的圖片需要被處理,那么此時(shí),就需要有一個(gè)方法,來處理這多張文件。
很容易可以想到MultipartFile,我們?cè)谑褂肞OST請(qǐng)求的時(shí)候就知道文件的單張上傳都是POST請(qǐng)求加上一個(gè)@RequestParam的MultipartFile類型的文件。
如下
但是上面只能實(shí)現(xiàn)單張文件的上傳,因此為了確保效率以及以及提交就能完成多文件的上傳,需要把代碼修改為如下狀態(tài),也就是請(qǐng)求參數(shù)為一個(gè)數(shù)組,這樣子就能接受多文件的請(qǐng)求了
文件上傳到本地代碼編寫
先最簡單的介紹一下把文件保存到本地的代碼編寫
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { //判斷文件名長度是否過長 int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); } //判斷文件的擴(kuò)展類型是否合理 assertAllowed(file, allowedExtension); //對(duì)文件名進(jìn)行編碼 String fileName = extractFilename(file); //獲取文件在本機(jī)的絕對(duì)地址 String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); //將文件放到本機(jī)絕對(duì)地址 file.transferTo(Paths.get(absPath)); //返回文件名 return getPathFileName(fileName); } //比較重要的就是這個(gè) 他將會(huì)創(chuàng)建多級(jí)目錄 并且把文件保存到對(duì)應(yīng)的位置 private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { File desc = new File(uploadDir + File.separator + fileName); if (!desc.exists()) { if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } } return desc.isAbsolute() ? desc : desc.getAbsoluteFile(); }
文件上傳之后,目錄結(jié)構(gòu)如下
其中文件目錄結(jié)構(gòu)指定位置為
全局線程池配置
我使用的SpringBoot版本為2.7.7,并且這是一個(gè)SpringCloud項(xiàng)目,因此,我對(duì)各種不同場景下的線程池進(jìn)行了不同的配置,并且在需要使用到線程池的模塊中直接映入這個(gè)線程池配置模塊即可,代碼如下,注意,這個(gè)代碼是我自己編寫的,很多類都是Java中沒有的,你們按照自己的方法編寫線程池配置即可
@AutoConfiguration public class DynamicThreadPool { /** * 初始化線程池 * * @return */ @Bean("fileThreadPool") private static ThreadPoolExecutor buildThreadPoolExecutor() { return new ThreadPoolExecutor(3, 3, 60, TimeUnit.SECONDS, new ResizableCapacityLinkedBlockIngQueue<Runnable>(10), new NamedThreadFactory("file-thread-"), new ThreadPoolExecutor.DiscardPolicy()); } }
然后將這個(gè)類自動(dòng)加載
實(shí)現(xiàn)多線程上傳
其實(shí)實(shí)現(xiàn)多線程上傳比較簡單,很容易的就可以想到Thread類,ThreadPoolExecutor,Semaphore,CountdownLaunch,CompletableFuture,CyclicBarrier等各種解決方法。
這里先簡單的列出CountDownLaunch配合ThreadPoolExecutor來實(shí)現(xiàn)多線程文件上傳的方法
CountDownLaunch
CountDownLatch 有什么用?
CountDownLatch 允許 count 個(gè)線程阻塞在一個(gè)地方,直至所有線程的任務(wù)都執(zhí)行完畢。CountDownLatch 是一次性的,計(jì)數(shù)器的值只能在構(gòu)造方法中初始化一次,之后沒有任何機(jī)制再次對(duì)其設(shè)置值,當(dāng) CountDownLatch 使用完畢后,它不能再次被使用。
在這個(gè)項(xiàng)目中,我們要讀取處理 3個(gè)文件,這 3個(gè)任務(wù)都是沒有執(zhí)行順序依賴的任務(wù),但是我們需要返回給用戶的時(shí)候?qū)⑦@幾個(gè)文件的處理的結(jié)果進(jìn)行統(tǒng)計(jì)整理。為此我們定義了一個(gè)線程池和 count 為3的CountDownLatch對(duì)象 。使用線程池處理讀取任務(wù),每一個(gè)線程處理完之后就將 count-1,調(diào)用CountDownLatch對(duì)象的 await()方法,直到所有文件讀取完之后,才會(huì)接著執(zhí)行后面的邏輯。
代碼比較簡單,也很好理解,也就是每一個(gè)文件拷貝完畢之后,調(diào)用countDownLaunch的countDown方法將計(jì)數(shù)器-1即可。
同時(shí),由于是多線程拷貝,并且我需要保存每一次拷貝的返回結(jié)果,因此,就使用到了CopyOnWriteArrayList來保證并發(fā)情況下的數(shù)據(jù)集合不被丟失修改。
@Autowired @Qualifier("fileThreadPool") private ThreadPoolExecutor fileThreadPool; /** * 實(shí)現(xiàn)多文件多線程上傳 * * @param files 要上傳的文件 * @return 返回戀愛日志信息 */ @ApiOperation(value = "多附件上傳-純附件上傳", notes = "多附件上傳") @ResponseBody @PostMapping("/uploadFiles") public R<LoveLogs> handleFileUpload(@RequestParam("files") MultipartFile[] files) { LoveLogs loveLogs = new LoveLogs(); String[] urls = new String[files.length]; CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); CountDownLatch countDownLatch = new CountDownLatch(files.length); for (int i = 0; i < files.length; i++) { try { 獲取文件名 //String fileName = file.getOriginalFilename(); 拼接文件保存路徑 //String filePath = "D:/uploads/" + fileName; 保存文件到本地 //file.transferTo(new File(filePath)); MultipartFile file = files[i]; //String url = sysFileService.uploadFile(file); //urls[i] = url; //TODO 使用CountDownLaunch或者ComplatableFuture或者Semaphore //來完成多線程的文件上傳 fileThreadPool.submit(() -> { try { String s = sysFileService.uploadFile(file); list.add(s); } catch (Exception e) { throw new RuntimeException(e); } finally { //表示一個(gè)文件已經(jīng)被完成 countDownLatch.countDown(); } }); } catch (Exception e) { throw new RuntimeException(e); } } try { //阻塞直到所有的文件完成復(fù)制 countDownLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } //統(tǒng)計(jì)每個(gè)文件的url String photoUrls = String.join(",", list); loveLogs.setUrls(photoUrls); //返回結(jié)果 return R.ok(loveLogs); }
實(shí)測
然后就是上傳多個(gè)文件,并且發(fā)送請(qǐng)求了,下面是一個(gè)簡單的請(qǐng)求模板,可以發(fā)現(xiàn)我上傳完畢文件之后,他返回給我了本地的這個(gè)文件的位置,當(dāng)然,這個(gè)文件的url地址啥的你們自己與前端對(duì)接完畢即可,我這里是多個(gè)url直接封裝在一起并且使用英文逗號(hào)作為分隔符,具體情況自定義即可。
文件回顯
文件的下載回顯也比較簡單,只要給出文件對(duì)應(yīng)的位置,然后直接去本地加載即可。
這里文件回顯暫時(shí)不做多線程優(yōu)化,等后期項(xiàng)目需要了在做
/** * 文件下載 * * @param name 文件名稱 * @param response 響應(yīng)流 */ @GetMapping("/download") public void download(@RequestParam String name, HttpServletResponse response) { //FileInputStream fis = null; //ServletOutputStream os = null; try (FileInputStream fis = new FileInputStream(new File(name)); ServletOutputStream os = response.getOutputStream()) { //輸入流,通過輸入流讀取文件內(nèi)容 //fis = new FileInputStream(new File( name)); //輸出流,通過輸出流將文件寫回瀏覽器,在瀏覽器展示圖片 //os = response.getOutputStream(); //設(shè)置響應(yīng)的數(shù)據(jù)的格式 response.setContentType("image/jpeg"); int len = 0; byte[] buffre = new byte[1024 * 10]; while ((len = fis.read(buffre)) != -1) { os.write(buffre, 0, len); os.flush(); } } catch (Exception e) { e.printStackTrace(); } }
到此這篇關(guān)于SpringBoot項(xiàng)目的多文件兼多線程上傳下載的文章就介紹到這了,更多相關(guān)SpringBoot 多文件上傳下載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java不解壓直接讀取壓縮包中文件的實(shí)現(xiàn)方法
這篇文章主要介紹了java不解壓直接讀取壓縮包中文件的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04解決mybatis 執(zhí)行mapper的方法時(shí)報(bào)空指針問題
這篇文章主要介紹了解決mybatis 執(zhí)行mapper的方法時(shí)報(bào)空指針問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot security安全認(rèn)證登錄的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot security安全認(rèn)證登錄的實(shí)現(xiàn)方法,也就是使用默認(rèn)用戶和密碼登錄的操作方法,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02Springboot使用Maven占位符@替換不生效問題及解決
這篇文章主要介紹了Springboot使用Maven占位符@替換不生效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04如何使用Java redis實(shí)現(xiàn)發(fā)送手機(jī)驗(yàn)證碼功能
這篇文章主要介紹了如何使用Java redis實(shí)現(xiàn)發(fā)送手機(jī)驗(yàn)證碼功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05解決java項(xiàng)目jar打包后讀取文件失敗的問題
這篇文章主要介紹了解決java項(xiàng)目jar打包后讀取文件失敗的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06解決java.util.zip.ZipException: Not in GZIP&nbs
這篇文章主要介紹了解決java.util.zip.ZipException: Not in GZIP format報(bào)錯(cuò)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java getRealPath("/")與getContextPath()區(qū)別詳細(xì)分析
這篇文章主要介紹了Java getRealPath("/")與getContextPath()區(qū)別詳細(xì)分析,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08