前后端聯合實現文件下載,實現 SQL Server image 類型文件下載

1、前端 Vue3

QualityFile.vue

<script setup lang="ts" name="QualityFile">
......
// 下載,實現 SQL Server image 類型文件下載
const onDownloadClick = async (fileNo: string) => {// const result = await qualityFileDownloadFileWithPutService(fileNo);// const result = await qualityFileDownloadFileService(fileNo);const result = await qualityFileDownloadFileWithPostService(fileNo);downloadFile(result);
};
......
</script><template>
......<el-table-column label="操作" width="150" header-align="center" align="center" fixed="right"><template #default="scope"><BasePreventReClickButtonclass="table-btn"type="primary"size="default"text@click="onDownloadClick(scope.row.fileNo)">下載</BasePreventReClickButton></template></el-table-column>
......
</template>

qualityFile.ts

import request from "@/utils/request";
import type { IQualityFile, IQualityFileQueryObj } from "@/views/resources/QualityFile/types";/*** 下載質量體系文件,實現 SQL Server image 類型文件下載,使用 get 請求* @param fileNo 文件編號(可能包含特殊字符如 /)* @returns 文件流 {@link Blob}*/
export const qualityFileDownloadFileService = (fileNo: string) => {// 對特殊字符進行編碼處理const encodedFileNo = encodeURIComponent(fileNo);return request.get("/resources/qualityFile/downloadFile", {params: {fileNo: encodedFileNo},// 響應類型為 blob,用于接收二進制數據流responseType: "blob"});
};/*** 下載質量體系文件,實現 SQL Server image 類型文件下載,使用 post 請求* @param fileNo 文件編號(可能包含特殊字符如 /)* @returns 文件流 {@link Blob}*/
export const qualityFileDownloadFileWithPostService = (fileNo: string) => {// 使用 post 請求避免 URL 解析問題return request.post("/resources/qualityFile/downloadFile",{// 使用 post,直接傳遞參數,無需編碼fileNo: fileNo},{// 響應類型為 blob,用于接收二進制數據流responseType: "blob"});
};/*** 下載質量體系文件,實現 SQL Server image 類型文件下載,使用 put 請求* @param fileNo 文件編號(可能包含特殊字符如 /)* @returns 文件流 {@link Blob}*/
export const qualityFileDownloadFileWithPutService = (fileNo: string) => {return request.put("/resources/qualityFile/downloadFile", null, {params: {fileNo: fileNo},// 響應類型為 blob,用于接收二進制數據流responseType: "blob"});
};

download.ts

import { type AxiosResponse } from "axios";/*** 下載文件* @param response Axios 響應對象(需包含 Blob 數據)* @param fileName 可選文件名(未提供時從 Content-Disposition 中提取)*/
export const downloadFile = (response: AxiosResponse<Blob>, fileName?: string) => {try {// 從響應標頭中獲取文件名(后端需設置 Content-Disposition)// 從響應標頭中獲取 content-disposition 屬性的信息const contentDisposition = response.headers["content-disposition"];// let fileName = "download-file";// if (contentDisposition) {//   // 通過正則表達式解析出文件名稱,數據示例:['filename=%E6%96%87%E4%BB%B6.txt', '%E6%96%87%E4%BB%B6.txt', '', index: 11, input: 'attachment;filename=%E6%96%87%E4%BB%B6.txt', groups: undefined]//   const fileNameMatch = contentDisposition.match(/filename=(.*?)(;|$)/);//   if (fileNameMatch && fileNameMatch.length > 2) {//     // 獲取原始文件名稱(索引為 1 的元素內容)//     // decodeURIComponent 是 JavaScript 的內置函數,用于解碼通過 URL 傳輸的編碼字符//     // matchArray[1] 通常是通過正則表達式匹配得到的文件名字符串,可能包含 URL 編碼(如 %E6%96%87%E4%BB%B6.txt)//     // 經過解碼后,fileName 得到的是可讀的原始文件名(如 文件.txt)//     fileName = decodeURIComponent(fileNameMatch[1]);//   }// }const fileNameMatch = contentDisposition.match(/filename="?(.+)"?/);// 后端使用 URLEncoder 編碼,前端使用 decodeURIComponent 解碼// let downloadFileName = fileName || fileNameMatch ? decodeURIComponent(fileNameMatch[1]) : "download-file";// 處理編碼問題// 原值:CZCDC∕QM-2018-B2 4.2 人員.doc// 后端編碼傳過來的值:CZCDC%E2%88%95QM-2018-B2+4.2+%E4%BA%BA%E5%91%98.doc// 前端使用 decodeURIComponent 解碼后的值:CZCDC∕QM-2018-B2+4.2+人員.doc// 將 + 替換為 空格// downloadFileName = downloadFileName.replace("+", " "); // 只替換前面第一個 +// downloadFileName = downloadFileName.replace(/\+/g, " "); // 使用正則表達式替換所有 +// 統一編碼解碼規則:后端使用 UriUtils 編碼,前端使用 decodeURIComponent 解碼,此方案支持空格和+等特殊字符let downloadFileName = "download-file";try {downloadFileName = fileName || fileNameMatch ? decodeURIComponent(fileNameMatch[1]) : "download-file";} catch (error) {console.error("解碼失敗:", error);}// 創建 Blob 對象// 將接收到的響應消息體的內容(二進制數據流)response.data,創建為 Blob 對象,用于對文件的操作const blob = new Blob([response.data]);// 下載文件// 創建鏈接標簽 aconst link = document.createElement("a");link.style.display = "none";// 設置鏈接路徑,將響應消息體的內容(二進制數據流)轉換為 url 地址對象link.href = URL.createObjectURL(blob);// 設置下載的文件名稱link.download = downloadFileName;// 增加鏈接標簽document.body.appendChild(link);// 觸發下載,模擬點擊鏈接標簽,下載文件link.click();// 清理資源// 移除 url 地址對象,釋放資源URL.revokeObjectURL(link.href);// 移除鏈接標簽document.body.removeChild(link);} catch (error) {console.error("下載文件失敗!", error);throw new Error(`下載文件失敗: ${error instanceof Error ? error.message : String(error)}`);}
};/*** 下載靜態文件* @param fileUrl 靜態文件地址*/
export const downloadStaticFile = (fileUrl: string, fileName?: string) => {// 文件路徑,開頭的 / 表示 public 目錄// const fileUrl = "/template/試劑導入模板.xlsx";// todo 檢查路徑if (!fileUrl) return;// 下載文件// 創建鏈接標簽 aconst link = document.createElement("a");// 設置鏈接路徑link.href = fileUrl;// 設置下載的文件名(可選)if (fileName) link.download = fileName;// 增加鏈接標簽document.body.appendChild(link);// 觸發下載,模擬點擊鏈接標簽,下載文件link.click();// 移除鏈接標簽document.body.removeChild(link);
};

2、后端 Spring boot + Mybatis

控制層:FileDownloadController.java
package com.weiyu.controller;import com.weiyu.anno.Debounce;
import com.weiyu.pojo.FileData;
import com.weiyu.service.FileDownloadService;
import com.weiyu.utils.FileDownloadUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;/*** 文件下載 Controller*/
@RestController
@Slf4j
public class FileDownloadController {@Autowiredprivate FileDownloadService fileDownloadService;/*** 質量體系文件下載,實現 SQL Server image 類型文件下載,使用 @GetMapping 接收請求* @param fileNo 文件編號(可能包含特殊字符如 /)* @return 文件數據流 {@link ResponseEntity}&lt;{@link Resource}&gt;* @apiNote 本接口使用防抖機制,5s 內重復請求會被忽略*/@GetMapping("/resources/qualityFile/downloadFile")@Debounce(key = "/resources/qualityFile/downloadFile", value = 5000)public ResponseEntity<Resource> downloadFileForQualityFile(@RequestParam String fileNo) {log.info("【質量體系文件下載】,實現 SQL Server image 類型文件下載,使用 @GetMapping 接收請求," +"/resources/qualityFile/downloadFile,fileNo = {}", fileNo);// 解碼參數(Spring 默認會自動解碼,但顯式處理更安全)String decodedFileNo = URLDecoder.decode(fileNo, StandardCharsets.UTF_8);// 獲取文件數據FileData fileData = fileDownloadService.queryFileDataForQualityFile(decodedFileNo);return FileDownloadUtil.downloadFile(fileData);}/*** 質量體系文件下載,實現 SQL Server image 類型文件下載,使用 @PostMapping 接收請求* @param argsMap 參數Map,包含 fileNo* @return 文件數據流 {@link ResponseEntity}&lt;{@link Resource}&gt;* @apiNote 本接口使用防抖機制,5s 內重復請求會被忽略*/@PostMapping("/resources/qualityFile/downloadFile")@Debounce(key = "/resources/qualityFile/downloadFile", value = 5000)public ResponseEntity<Resource> downloadFileForQualityFileWithPost(@RequestBody Map<String, String> argsMap) {log.info("【質量體系文件下載】,實現 SQL Server image 類型文件下載,使用 @PostMapping 接收請求," +"/resources/qualityFile/downloadFile,argsMap = {}", argsMap);// 從參數Map中獲取文件編號String fileNo = argsMap.get("fileNo");// 獲取文件數據FileData fileData = fileDownloadService.queryFileDataForQualityFile(fileNo);return fileDownloadService.downloadFile(fileData);}
}
服務層接口實現:FileDownloadServiceImpl.java
package com.weiyu.service.impl;import com.weiyu.mapper.FileDownloadMapper;
import com.weiyu.pojo.FileData;
import com.weiyu.service.FileDownloadService;
import com.weiyu.utils.FileDownloadUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;/*** 文件下載 Service 接口實現*/
@Service
public class FileDownloadServiceImpl implements FileDownloadService {@Autowiredprivate FileDownloadMapper fileDownloadMapper;/*** 查詢質量體系文件數據* @param fileNo 文件編號*/@Overridepublic FileData queryFileDataForQualityFile(String fileNo) {return fileDownloadMapper.selectFileDataForQualityFile(fileNo);}/*** 下載文件* @param fileData 文件數據對象*/@Overridepublic ResponseEntity<Resource> downloadFile(FileData fileData) {return FileDownloadUtil.downloadFile(fileData);}
}
數據表結構
數據傳輸對象 DTO:FileData.java
package com.weiyu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 文件數據*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileData {private String fileName;private byte[] fileContent;
}

持久層:FileDownloadMapper.java

package com.weiyu.mapper;import com.weiyu.pojo.FileData;
import org.apache.ibatis.annotations.Mapper;/*** 文件下載 Mapper*/
@Mapper
public interface FileDownloadMapper {/*** 查詢質量體系文件數據* @param fileNo 文件編號*/FileData selectFileDataForQualityFile(String fileNo);
}

持久層數據庫sql查詢:FileDownloadMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.weiyu.mapper.FileDownloadMapper"><!--mssql--><!-- 查詢質量體系文件數據 --><select id="selectFileDataForQualityFile" resultType="com.weiyu.pojo.FileData">selectcfm_ContentFileName as fileName, cfm_Content as fileContentfrom ControledFileMainwhere Cfm_BigType = '3' and Cfm_ID = #{fileNo}</select>
</mapper>
文件下載工具類:FileDownloadUtil.java
package com.weiyu.utils;import com.weiyu.pojo.FileData;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriUtils;import java.nio.charset.StandardCharsets;/*** 文件下載工具*/
public class FileDownloadUtil {/*** 下載文件* @param fileData 文件數據對象 {@link FileData}* @return 文件數據流 {@link ResponseEntity}&lt;{@link Resource}&gt;*/public static ResponseEntity<Resource> downloadFile(FileData fileData) {// 創建資源對象ByteArrayResource resource = new ByteArrayResource(fileData.getFileContent());// 資源為nullif (resource.contentLength() == 0) {return ResponseEntity.noContent().build();}// 編碼示例:空格 編碼為 +,前端解碼后還是 +// URLEncoder.encode("CZCDC∕QM-2018-B2 4.2 人員.doc", StandardCharsets.UTF_8));// 編碼為:CZCDC%E2%88%95QM-2018-B2+4.2+%E4%BA%BA%E5%91%98.doc// 前端解碼為:CZCDC∕QM-2018-B2+4.2+人員.doc// 這個問題的根本原因在于 URLEncoder.encode 使用 application/x-www-form-urlencoded 編碼標準,它使用 + 表示空格。但在某些上下文中,這個 + 沒有被正確解碼回空格。// String encodedFileName = URLEncoder.encode(fileData.getFileName(), StandardCharsets.UTF_8);// 后端處理空格編碼,將 + 替換為 空格// encodedFileName = encodedFileName.replace("+", " ");// 統一編碼解碼規則:后端使用 UriUtils 編碼,前端使用 decodeURIComponent 解碼,此方案支持空格和+等特殊字符String encodedFileName = UriUtils.encode(fileData.getFileName(), StandardCharsets.UTF_8);// 返回響應實體return ResponseEntity// 設置狀態.ok()// 設置內容類型為 MediaType.APPLICATION_OCTET_STREAM,八位字節的二進制數據流.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength())// 設置響應標頭,添加屬性 Content-Disposition,Content-Disposition就是當用戶想把請求所得的內容存為一個文件的時候提供一個默認的文件名。// 其屬性值必須要加上attachment,如: attachment;filename="name.xlsx",就是文件名稱的信息,并且文件名稱需要用雙引號包裹(不支持中文編碼,需要編碼轉換)// 設置內容處置為附件,并指定文件名,到時前端就可以解析這個響應頭拿到這個文件名稱進行下載// .header("Content-Disposition", "attachment;filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) +"\"")// 實際測試發現文件名稱不用雙引號包裹,也是可以達到需求目標,并且前端通過正則表達式解析出文件名稱時還簡單一些// 文件名通常放在雙引號內,如果文件名包含空格或特殊字符,使用雙引號是必要的.header("Content-Disposition", "attachment;filename=" + encodedFileName)// 設置響應消息體為 resource.body(resource);}
}

3、應用效果

文件名稱支持空格和加號

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96247.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96247.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96247.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【OneAI】使用Rust構建的輕量AI網關

LLM網關 統一大模型API入口&#xff0c;使用一個令牌調用多家模型&#xff0c;無需切換API Key兼容OpenAI輸入輸出規范內置10提供商和50模型&#xff0c;開箱即用支持自動負載、限流、IP限制、Token用量限制等功能支持釘釘、飛書、企微消息預警支持對不同提供商設置代理支持主…

Jenkins服務器配置SSH

1. 創建Jenkins用戶的SSH配置ssh-keygen -t rsa -b 4096 -f /tmp/jenkins_ssh_key -N ""2. 在Jenkins服務器上執行以下命令# 切換到root用戶 sudo su -# 創建Jenkins用戶的SSH目錄 mkdir -p /var/lib/jenkins/.ssh chown jenkins:jenkins /var/lib/jenkins/.ssh chmo…

nginx-下載功能-狀態統計-訪問控制

nginx-下載功能-狀態統計-訪問控制一、利用nginx做網站提供下載功能1. 進入nginx存放配置文件目錄2. 編輯nginx.conf文件&#xff0c;開啟下載功能3. 檢查nginx.conf主配置文件是否正確4. 重啟nginx服務5. 修改首頁文件index.html6. 訪問首頁7. 去網頁根目錄下新建download目錄…

GitLab CI/CD、Jenkins與GitHub Actions在Kubernetes環境中的方案對比分析

GitLab CI/CD、Jenkins與GitHub Actions在Kubernetes環境中的方案對比分析 隨著容器化和微服務的普及&#xff0c;基于Kubernetes的部署已經成為主流。在實際的生產環境中&#xff0c;如何選擇合適的CI/CD流水線方案以實現自動化構建、測試、部署和發布&#xff0c;直接關系到團…

tcp會無限次重傳嗎

tcp作為面向連接的&#xff0c;可靠的&#xff0c;字節流。最重要的特點就是可靠&#xff0c;其中重傳又是保證可靠的重要前提。那么當tcp發送數據之后&#xff0c;收不到ack的情況下&#xff0c;會無限次重傳嗎。不會。# cat /proc/sys/net/ipv4/tcp_retries1 3 # cat /proc/s…

EasyAIoT平臺部署

EasyAIoT官方文檔專注于 AIoT 智能硬件與工業軟件解決方案&#xff0c;提供從設備接入到云端管理的全棧服務http://pro.basiclab.top:9988/

功能測試相關問題

1.功能測試流程&#xff08;工作流程&#xff09;需求分析 -- 測試點分析&#xff08;xmind&#xff09;-- 編寫測試計劃/用例及評審 -- 執行測試用例&#xff08;開發提交測試&#xff09;-- 發現缺陷通過缺陷管理工具提交 -- 回歸測試及bug驗證&#xff08;開發提測新版本&am…

微服務網關中數據權限傳遞的那些坑:從 Feign 兼容性問題到解決方案

在微服務架構中&#xff0c;網關作為流量入口&#xff0c;常常需要承擔身份認證、權限校驗等職責。其中&#xff0c;用戶數據權限的傳遞看似簡單&#xff0c;卻隱藏著不少兼容性陷阱。本文將結合實際項目經驗&#xff0c;聊聊如何解決 Feign 調用時請求頭中 JSON 數據的傳遞問題…

基于SpringBoot的旅游攻略系統網站【2026最新】

作者&#xff1a;計算機學姐 開發技術&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源碼”。 專欄推薦&#xff1a;前后端分離項目源碼、SpringBoot項目源碼、Vue項目源碼、SSM項目源碼、微信小程序源碼 精品專欄&#xff1a;…

thingsboard 服務器在2核CPU、2G內存資源配置下如何調優提速,適合開發/演示

物聯網設備管理平臺致力于為客戶提供高效、可靠的物聯網解決方案。基于開源物聯網平臺進行深度二次開發&#xff0c;我們打造了功能強大、靈活易用的物聯網平臺&#xff0c;廣泛應用于智能家居、智能工廠、智能城市等多個領域 一、標準資源要求 CPU&#xff1a;建議至少 8 vCP…

C#多線程學習—主子線程,Invoke與begininvoke

一、為什么需要多線程操作&#xff1f;在 WinForms 應用程序中&#xff0c;主線程&#xff08;UI 線程&#xff09;負責處理用戶交互和界面更新。當執行耗時操作&#xff08;如網絡請求、文件讀寫、復雜計算&#xff09;時&#xff0c;如果直接在 UI 線程執行&#xff0c;會導致…

Vue 核心知識點總結

Vue 作為國內最普及的前端框架,是面試中考察概率最高的技術之一。本文將系統梳理 Vue 的核心知識點,包括 Vue3 與 Vue2 的區別、組件通信、生命周期、性能優化等關鍵內容。 ?? Vue3 和 Vue2 的主要區別 Vue 3 提供了更現代化、更高性能的架構,通過 Composition API 和 P…

Python腳本每天爬取微博熱搜-升級版

主要優化內容&#xff1a; 定時任務調整&#xff1a; 將定時任務從每小時改為每10分鐘執行一次 調整了請求延遲時間&#xff0c;從1-3秒減少到0.5-1.5秒 縮短了請求超時時間&#xff0c;從10秒減少到8秒 性能優化&#xff1a; 移除了廣告數據的處理&#xff0c;減少不必要的處理…

win11兼容運行遠古游戲

游戲<遠古戰爭>屬于win7時代的游戲&#xff0c;在win11系統中運行&#xff0c;當鼠標移動立馬卡住 解決方案&#xff1a; 最優&#xff1a;采用wmware虛擬機安裝win7系統 最簡單&#xff1a;使用 DxWnd 模擬老游戲運行環境 DxWnd官網下載 附錄&#xff1a;游戲下載網址…

Docker小游戲 | 使用Docker部署人生重開模擬器

Docker小游戲 | 使用Docker部署人生重開模擬器 前言 項目介紹 項目簡介 項目預覽 二、系統要求 環境要求 環境檢查 Docker版本檢查 檢查操作系統版本 三、部署人生重開模擬器小游戲 下載鏡像 創建容器 檢查容器狀態 檢查服務端口 安全設置 四、訪問人生重開模擬器 五、總結 前言…

從依賴到自研:一個客服系統NLP能力的躍遷之路

前言&#xff1a;七年磨一劍的技術突圍2015年在某平臺上線初期&#xff0c;智能客服系統即采用行業通用的第三方NLP解決方案。在隨后的八年發展歷程中&#xff0c;系統雖歷經三次重大版本迭代&#xff0c;但始終未能突破核心語義識別能力的外部依賴。這種依賴帶來了三重困境&am…

50.Seata-AT模式

AT模式同樣是分階段提交的事務模型。優勢是彌補了XA模型中資源鎖定周期過長的缺陷。 沒有代碼入侵,框架自動完成快照生成、回滾和提交。實現非常簡單。 兩階段之間屬于軟狀態,屬于最終一致。 AT模式 階段一RM的工作: 1.注冊分支事務 2.記錄undo-log (數據快照),記錄更…

Android13車機系統自定義系統欄顯示策略之狀態欄下拉異常

1、引言 文章《Android13車機系統實現系統欄自定義顯示策略》介紹了車機系統上自定義系統欄(狀態欄、底部欄)顯示策略,文中末尾提到了一個遺留問題: 由于狀態欄區域支持點擊或下拉顯示出快捷設置&消息通知欄,三方應用顯示時,從狀態欄中間區域而不從頂部邊緣下拉,底…

【Langchain系列五】DbGPT——Langchain+PG構建結構化數據庫智能問答系統

Langchain二次開發專欄 【Langchain系列一】常用大模型的key獲取與連接方式 【Langchain系列二】LangChain+Prompt +LLM智能問答入門 【Langchain系列三】GraphGPT——LangChain+NebulaGraph+llm構建智能圖數據庫問答系統 【Langchain系列四】RAG——基于非結構化數據庫的智能問…

生信分析自學攻略 | R語言數據類型和數據結構

在前面兩篇文章中&#xff0c;我們已經成功搭建了R和RStudio這一強大的生信分析平臺。然而&#xff0c;工具再好&#xff0c;若不懂得如何“放置”和“理解”你的數據&#xff0c;一切都將寸步難行。今天&#xff0c;我們將學習R語言最重要的部分——數據類型&#xff08;Data …