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 …