Minio实现文件上传、下载、预览、删除
环境:
JDK11
Minio8
服务器搭建Minio:https://www.cnblogs.com/warmNest-llb/p/18233203
完成项目
AjaxResult 结果返回使用的 若依。
1. pom.xml
<!-- MinIO Client --> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.2.2</version> </dependency>
2. application.yml
# Spring配置 spring: # minio配置 servlet: multipart: enabled: true # 单个文件大小 max-file-size: 100MB # 设置总上传的文件大小 max-request-size: 200MB location: /usr/local/minio/data_file/temp_file/
注意:配置的存放文件的路径。
# minio配置 minio: endpoint: http://120.服务器:9001 accessKey: minioadmin secretKey: minioadmin bucket: jx-file
注意:endpoint- ip + 端口
账号 密码
bucket-创建的桶
3. Minio工具类
/** * MinIO 服务 */ @Component public class MinIOUtil { // 指定MinIO服务的访问地址(包括协议、域名或IP以及端口) private static String endpoint; // MinIO的访问密钥(Access Key),用于身份验证 private static String accessKey; // MinIO的秘密密钥(Secret Key),与访问密钥配对使用,也是认证的一部分。 private static String secretKey; // 指定默认的存储桶(Bucket)名称,MinIO中用于组织和存储对象(文件)的基本容器。 private static String bucket; @Value("${minio.endpoint}") public void setEndpoint(String endpoint) { MinIOUtil.endpoint = endpoint; } @Value("${minio.accessKey}") public void setAccessKey(String accessKey) { MinIOUtil.accessKey = accessKey; } @Value("${minio.secretKey}") public void setSecretKey(String secretKey) { MinIOUtil.secretKey = secretKey; } @Value("${minio.bucket}") public void setBucket(String bucket) { MinIOUtil.bucket = bucket; } /** * 创建并返回一个配置好的MinioClient实例 * 用于与MinIO服务器交互,上传文件、下载文件、删除文件 * * @return 配置 */ private static MinioClient getMinioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } /** * 上传:将一个输入流中的文件上传到MinIO服务器上指定的存储桶(bucket)里 * * @param objectName .object(objectName)指定了上传后对象的名称。 * @param inputStream 转换为 流 * @param size 文件大小 * @param contentType 内容类型 * @throws Exception 异常 */ public static void uploadFile(String objectName, InputStream inputStream, long size, String contentType) throws Exception { MinioClient minioClient = getMinioClient(); minioClient.putObject( // .bucket(bucket) 指定了目标存储桶的名称。.object(objectName)指定了上传后对象的名称。 PutObjectArgs.builder().bucket(bucket).object(objectName).stream(inputStream, size, -1) // .contentType(contentType)指定了上传文件的内容类型。 .contentType(contentType) .build()); } /** * 下载:从MinIO服务器下载指定存储桶(bucket)中的文件 * * @param objectName 指定要从MinIO下载的文件对象名称(即文件路径和文件名)。 * @return 流 对象 * @throws Exception 异常 */ public static InputStream downloadFile(String objectName) throws Exception { MinioClient minioClient = getMinioClient(); return minioClient.getObject( GetObjectArgs.builder() // .bucket(bucket)指定了文件所在的存储桶名称。 .bucket(bucket) // .object(objectName)指定了要下载的对象名称。 .object(objectName) .build()); } /** * 预览:生成一个预签名的URL,允许用户通过浏览器或其他HTTP客户端以GET方法访问MinIO存储桶中指定对象(文件)的临时链接 * * @param objectName 指定需要获取预览链接的文件对象名称(包括路径)。 * @return 对象 * @throws Exception .method(Method.GET)指定了请求的方法为GET,这是预览文件时的标准HTTP方法。 * .bucket(bucket)指定了存储桶的名称。 * .object(objectName)指定了对象(文件)的名称。 */ public static String getPreviewUrl(String objectName) throws Exception { MinioClient minioClient = getMinioClient(); return minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucket) .object(objectName) .build()); } /** * 删除:从MinIO服务器上的指定存储桶中删除一个文件 * * @param objectName 指定要删除的文件对象名称(包括路径和文件名) * @throws Exception */ public static void deleteFile(String objectName) throws Exception { MinioClient minioClient = getMinioClient(); minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucket).object(objectName).build()); } }
4. 实体类
/** * 文件的实体类 */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class JxAttachment { /** 文件ID */ @TableId(value = "fileId") private Integer fileId; /** 文件名称 */ private String fileName; /** 文件大小 */ private String fileSize; /** 上传人员 */ private String uploadingStaff; /** 上传时间 */ private String uploadTime; /** 文件新名词 */ private String fileNewName; /** 文件地址 */ private String fileAddress; /** 部门编号 */ private String deptNumber; }
/** * 文件预览DTO */ @Data @AllArgsConstructor @NoArgsConstructor public class JxAttachmentPreviewDTO { /** 文件ID */ private Integer fileId; /** 文件名称 */ private String fileName; /** 文件大小 */ private String fileSize; /** 上传人员 */ private String uploadingStaff; /** 文件地址 */ private String fileAddress; /** 文件新名词 */ private String fileNewName; /** 上传时间 */ private String uploadTime; }
5. Mapper层
@Mapper public interface JxFileMapper { /** 添加上传信息 */ int insertFiles(JxAttachment attachment); /** 部门编号 查询文件信息 */ List<JxAttachmentPreviewDTO> previewFileByDeptNum(String deptNumber); /** 文件删除 */ void deleteFile(@Param("fileName") String fileName, @Param("deptNumber") String deptNumber); } <!-- 添加上传信息 --> <insert id="insertFiles"> insert into jx_attachment (file_id, file_name, file_size, uploading_staff, upload_time, file_new_name, file_address, dept_number) values (#{fileId}, #{fileName}, #{fileSize}, #{uploadingStaff}, #{uploadTime}, #{fileNewName}, #{fileAddress}, #{deptNumber}) </insert> <!-- 部门编号 查询文件信息 --> <select id="previewFileByDeptNum" resultType="com.ruoyi.xinzhi.model.dto.JxAttachmentPreviewDTO"> select file_id as fileId, file_name as fileName, file_size as fileSize, uploading_staff as uploadingStaff, file_address as fileAddress, upload_time as uploadTime, file_new_name as fileNewName from jx_attachment where dept_number = #{deptNumber}; </select> <!-- 文件删除 --> <delete id="deleteFile"> delete from jx_attachment where dept_number = #{deptNumber} and file_new_name = #{fileName}; </delete>
PS:预览功能使用了部门编号进行作为条件。可做参考。
删除功能使用了部门编号与文件名配合。
6. Service业务层
只需要实现类与Controller交互
/** * 文件上传、下载、预览、删除 */ @Service public class JxFileService { @Autowired private JxUserMapper jxUserMapper; @Autowired private JxFileMapper jxFileMapper; @Value("${spring.servlet.multipart.location}") private String tempDir; /** * 文件上传 * * @param file 文件属性 * @param deptNumber 部门编号 * @return 成功 ? 失败 * @throws Exception */ @Transactional public AjaxResult uploadFile(MultipartFile file, String deptNumber) throws Exception { String filename = file.getOriginalFilename(); // 获取文件名 long size = file.getSize(); // 文件大小 File tempDirectory = new File(tempDir); if (!tempDirectory.exists()) { tempDirectory.mkdir(); } try (InputStream inputStream = file.getInputStream()) { // 获取文件扩展名 String fileExtension = FilenameUtils.getExtension(filename); // 获取不带后缀文件名 String baseName = FilenameUtils.getBaseName(filename); // 生成5位时间戳 String timestamp = new SimpleDateFormat("ssSSS").format(new Date()); // 生成唯一文件名 String fileName = baseName + "_" + timestamp + "." + fileExtension; // 文件的路径 String fileAddress = "/usr/local/minio/data_file/" + fileName; // 文件上传到 minio 客户端 MinIOUtil.uploadFile(fileName, inputStream, size, file.getContentType()); // 使用 部门编号 获取对应 用户 String userFullName = jxUserMapper.selectUserFullName(deptNumber); // 将当前格式转为年月日 String date = formatDate(new Date()); // 文件大小转换 String fileSize = formatFileSize(size); // 信息存入数据库 JxAttachment attachment = JxAttachment.builder() .fileName(filename) .fileSize(fileSize) .uploadingStaff(userFullName) .uploadTime(date) .fileNewName(fileName) .fileAddress(fileAddress) .deptNumber(deptNumber) .build(); // 添加数据库 jxFileMapper.insertFiles(attachment); return AjaxResult.success("上传成功"); } catch (Exception e) { return AjaxResult.error("上传有误" + e.getMessage()); } } /** * 预览文件 * @param deptNumber 部门编号 * @return 文件预览路径 * @throws Exception 异常 */ public AjaxResult previewFile(String deptNumber) throws Exception { // 部门编号 查询文件信息 List<JxAttachmentPreviewDTO> attachments = jxFileMapper.previewFileByDeptNum(deptNumber); for (JxAttachmentPreviewDTO attachment : attachments) { String fileName = attachment.getFileNewName(); // 文件名 String fileUrl = MinIOUtil.getPreviewUrl(fileName); // 生成文件路径 attachment.setFileAddress(fileUrl); // 文件地址设置为预览地址 } return AjaxResult.success(attachments); } /** * 文件大小转换 * @param size 文件大小 * @return 文件大小 */ private String formatFileSize(long size) { DecimalFormat decimalFormat = new DecimalFormat("0.00"); if (size < 1024) { return size + "B"; } else if (size < 1024 * 1024) { return decimalFormat.format((double) size / 1024) + "KB"; } else { return decimalFormat.format((double) size / (1024 * 1024)) + " MB"; } } /** * 将 Date 转换为 yyyy-MM-dd 格式的字符串 * * @param date 当前时间 * @return 方法 */ private String formatDate(Date date) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); return formatter.format(date); } }
7. Controller控制层
/** * 文件 */ @RestController @RequestMapping("/file") public class JxFileController { @Autowired private JxFileService jxFileService; @Autowired private JxFileMapper jxFileMapper; /** * 文件上传 * * @param file 文件属性 * @param deptNumber 部门编号 * @return 成功 ? 失败 */ @PostMapping("/upload") public AjaxResult uploadFile(@RequestPart @RequestParam("file") MultipartFile file, @RequestParam("deptNumber") String deptNumber) { try { return jxFileService.uploadFile(file, deptNumber); } catch (MultipartException e) { throw new BusinessException("请求错误"); } catch (Exception e) { throw new BusinessException("上传有误"); } } /** * 文件下载 * @param fileName 文件名 * @param response 响应 * @return 成功 ? 失败 */ @GetMapping("/download") @ResponseBody public void downloadFile(@RequestParam String fileName, HttpServletResponse response) { try (InputStream inputStream = MinIOUtil.downloadFile(fileName)) { // 文件名进行编码 String encodedFileName = URLEncoder.encode(fileName, "UTF-8") .replaceAll("\\+", "%20"); // 设置响应内容类型 response.setContentType("application/octet-stream"); // 设置响应头,告知浏览器进行下载处理 response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName); // 获取响应输出流 ServletOutputStream outputStream = response.getOutputStream(); // 文件内容写入响应输出流 byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } catch (IOException e) { throw new BusinessException("IO异常"); } catch (Exception e) { throw new BusinessException("下载有误"); } } /** * 预览文件 * * @param deptNumber 部门编号 * @return 文件预览路径 */ @GetMapping("/preview") public AjaxResult previewFile(@RequestParam String deptNumber) { try { AjaxResult result = jxFileService.previewFile(deptNumber); return AjaxResult.success("预览路径", result); } catch (Exception e) { throw new BusinessException("生成路径有误"); } } /** * 删除文件 * @param fileName 文件名 * @return 成功 ? 失败 * @throws Exception 异常 */ @DeleteMapping("/delete") private AjaxResult deleteFile(@RequestParam String fileName, String deptNumber){ try{ MinIOUtil.deleteFile(fileName); jxFileMapper.deleteFile(fileName, deptNumber); return AjaxResult.success("删除成功"); } catch (Exception e) { throw new BusinessException("删除有误"); } } }
8. 测试
PS:文件可以存储多种格式的。但越大响应速度越慢。
1) 上传
参数:file 文件;deptNumber 部门编号
在上传时,使用当前时间时间戳生成5为数,防止文件名重复。
成功 为返回。
上传文件时,传入服务器,同时生成 别的信息存储数据库。
可以看到 服务器 与 桶 里也存在。
2) 预览
传参:deptNumber 部门编号去查询到对应的文件。
返回的是:fileAddress 文件路径。使用文件路径可以直接访问预览。
查到的两个文件,使用链接浏览器是可以访问的。
3) 下载
下载时 使用服务器存储的文件名。(在上传时生成的文件名+时间戳+后缀)
与浏览器做了响应,postman无法测试。
4) 删除
此时服务器、桶、数据库存储了两个数据。
参数:文件名,使用服务器存储的文件名。(在上传时生成的文件名+时间戳+后缀)
部门编号
删除pdf文件。
9. 联调
前端:vue2 + ElementUI
一个上传按钮(未截图) + 四个图标按钮。
<!-- 上传文件按钮 --> <el-upload ref="upload" action="" :http-request="uploadFile" :before-upload="beforeUpload" :show-file-list="false" > <el-button type="primary" class="vertical-center" style=" background-color: rgb(2, 167, 240); height: 40px; margin-right: 10px; " @click="handleUpload" > 上传文件 </el-button> </el-upload>
<div> <!-- 表格里的字段 --> <el-table :data="tableData" border style="width: 100%"> <!-- 添加复选框 --> <el-table-column type="selection" width="55"></el-table-column> <!-- 表格列 --> <el-table-column fixed prop="fileName" label="文件名称" border ></el-table-column> <el-table-column prop="fileSize" label="文件大小" show-overflow-tooltip border width="200px" ></el-table-column> <el-table-column prop="uploadingStaff" label="上传人员" border width="200px" ></el-table-column> <el-table-column prop="uploadTime" label="上传时间" show-overflow-tooltip border width="200px" ></el-table-column> <!-- slot-scope="scope" --> <el-table-column prop="operation" label="操作" border width="200px"> <template v-slot:default="scope"> <div class="operation-icons"> <!-- 查看按钮 @click="handleView(scope.row)"--> <el-tooltip content="查看" placement="bottom"> <el-button type="text" icon="el-icon-search" @click="handleView(scope.row)" ></el-button> </el-tooltip> <!-- 删除按钮 @click="handleDelete(scope.row)"--> <el-tooltip content="删除" placement="bottom"> <el-button type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" ></el-button> </el-tooltip> <!-- 下载按钮 @click="handleDownload(scope.row)"--> <el-tooltip content="下载" placement="bottom"> <el-button type="text" icon="el-icon-download" @click="handleDownload(scope.row)" ></el-button> </el-tooltip> <!-- 上传按钮 @click="handleUpload"--> <el-tooltip content="上传" placement="bottom"> <el-upload ref="upload" action="" :http-request="uploadFile" :before-upload="beforeUpload" :show-file-list="false" @change="handleUpload" > <el-button type="text" icon="el-icon-upload2" @click="handleUpload" ></el-button> </el-upload> </el-tooltip> </div> </template> </el-table-column> </el-table> </div>
.operation-icons { display: flex; justify-content: space-around; align-items: center; width: 150px; /* 宽度调整 */ }
四个方法:
@click="handleUpload" -- 上传
@click="handleDownload(scope.row)" -- 下载
@click="handleView(scope.row)" -- 预览
@click="handleDelete(scope.row)" -- 删除
// 文件上传 async uploadFile(option) { // 可以以同步的方式编写异步代码,暂停函数的执行,等待一个Promise解析(resolve或reject),然后继续执行函数。 const formData = new FormData(); formData.append("file", option.file); try { const response = await request.post( "http://localhost:8080/file/upload", formData ); if (response.code === 200) { this.$message.success("成功上传"); this.fetchTableDate(); // 重新加载表格数据 } else { this.$message.error( `上传失败: ${response ? response.message : "未知错误"}` ); } } catch (error) { this.$message.error(`上传失败: ${error.message}`); } }, // 文件上传 handleUpload() { this.$refs.upload.submit(); // 引用为upload的表单元素并提交 }, // 预上传处理函数 beforeUpload(file) { return true; // 可以在此处添加例如检查文件类型、大小等逻辑 },
// 文件预览 触发查询 fetchTableDate() { request .get("http://localhost:8080/file/preview") .then((res) => { this.tableData = res.data.data; }) .catch((error) => { this.$message.error("获取文件有误", error); }); }, // 点击预览 handleView(row) { const fileUrl = row.fileAddress; window.open(fileUrl, "_blank"); // 在新窗口中打开预览URL },
// 文件下载 handleDownload(row) { const fileName = row.fileNewName; // 发起请求到后端下载 request({ url: "http://localhost:8080/file/download", method: "get", responseType: "blob", // 指定类型为 blob params: { fileName }, }) .then((res) => { // this.$message.success(res.msg); // 创建一个 URL 链接到文件内容 const url = window.URL.createObjectURL(res); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName); // 设置下载属性 document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); // 释放URL对象 }) .catch((error) => { this.$message.error("下载有误", error.message); }); },
// 删除文件 handleDelete(row) { const fileName = row.fileNewName; // 文件名称 console.log("删除文件--", fileName); this.$confirm("确定文件永久删除吗,是否继续", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(() => { request({ url: "http://localhost:8080/file/delete", method: "delete", params: { fileName }, }) .then((res) => { this.$message.success(res.msg); this.fetchTableDate(); // 重新加载表格数据 }) .catch((error) => { this.$message.error(res.msg); }); }) .catch((error) => { this.$message.error("文件删除有误,请稍后重试"); }); },
点击上传成功。
完成代码:
# 自定义的请求。请求头内存储了token与部门编号
import request from "@/utils/request";
// 导入axios库,用于发起HTTP请求 import axios from 'axios'; // 使用axios.create方法创建一个axios实例,以便进行定制化配置 const service = axios.create({ // 设置基础URL,所有使用此axios实例发起的请求都会以此作为前缀 // 部署服务器时替换服务器域名 baseURL: 'http://localhost:8089', // 设置请求超时时间,单位为毫秒。此处设置为150000毫秒,即15秒 timeout: 150000 }); // 添加请求拦截器。此拦截器会在每个请求发送出去之前被调用 service.interceptors.request.use( // 这个函数会在请求发出前执行 request => { // 从localStorage中尝试获取token,通常在用户登录后存储 const token = localStorage.getItem('token'); // 从localStorage 中获取 部门编号 const deptNumber = localStorage.getItem("deptNumber"); // 如果token存在,则将其添加到请求头中,以便服务器进行权限验证 // 注意:这里使用'token'作为请求头的key可能需要根据后端要求调整,常见的还有'Authorization' if (token) { request.headers.token = token; } // 如果部门ID存在,将其添加到请求参数中 if (deptNumber) { if (request.method === 'get' || request.method === 'delete') { // GET 请求的参数 request.params = { ...request.params, deptNumber }; } else if (request.method === 'post' || request.method === 'put') { if (request.data instanceof FormData) { request.data.append('deptNumber', deptNumber); } else { // POST 请求的参数 request.data = { ...request.data, deptNumber }; } } } // 必须返回修改后的请求配置,以便axios继续处理 return request; }, // 如果在请求拦截器中抛出了错误,此函数会被调用 error => { // 将错误传递给下一个处理错误的逻辑(例如响应拦截器或全局错误处理) return Promise.reject(error); } ); // 添加响应拦截器。此拦截器会在每个请求的响应到达后被调用 service.interceptors.response.use( // 当请求成功时(HTTP状态码为2xx),此函数会被调用 // response => { response => response.data, error => { if (error.response) { const { code, message } = error.response.data; console.error(`Error ${code}: ${message}`); if (code === 401) { alert(message); // 跳转到登录页面或执行其他操作 router.push('/login'); } } return Promise.reject(error); } ); // 导出定制化的axios实例,供其他模块使用 export default service;
<template> <div> <!-- 上传文件按钮 --> <el-upload ref="upload" action="" :http-request="uploadFile" :before-upload="beforeUpload" :show-file-list="false" > <el-button type="primary" class="vertical-center" style=" background-color: rgb(2, 167, 240); height: 40px; margin-right: 10px; " @click="handleUpload" > 上传文件 </el-button> </el-upload> <!-- 表格里的字段 --> <el-table :data="tableData" border style="width: 100%; margin-top: 20px"> <!-- 添加复选框 --> <el-table-column type="selection" width="55"></el-table-column> <!-- 表格列 --> <el-table-column fixed prop="fileName" label="文件名称" border ></el-table-column> <el-table-column prop="fileSize" label="文件大小" show-overflow-tooltip border width="200px" ></el-table-column> <el-table-column prop="uploadingStaff" label="上传人员" border width="200px" ></el-table-column> <el-table-column prop="uploadTime" label="上传时间" show-overflow-tooltip border width="200px" ></el-table-column> <!-- slot-scope="scope" --> <el-table-column prop="operation" label="操作" border width="200px"> <template v-slot:default="scope"> <div class="operation-icons"> <!-- 查看按钮 @click="handleView(scope.row)"--> <el-tooltip content="查看" placement="bottom"> <el-button type="text" icon="el-icon-search" @click="handleView(scope.row)" ></el-button> </el-tooltip> <!-- 删除按钮 @click="handleDelete(scope.row)"--> <el-tooltip content="删除" placement="bottom"> <el-button type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" ></el-button> </el-tooltip> <!-- 下载按钮 @click="handleDownload(scope.row)"--> <el-tooltip content="下载" placement="bottom"> <el-button type="text" icon="el-icon-download" @click="handleDownload(scope.row)" ></el-button> </el-tooltip> <!-- 上传按钮 @click="handleUpload"--> <el-tooltip content="上传" placement="bottom"> <el-upload ref="upload" action="" :http-request="uploadFile" :before-upload="beforeUpload" :show-file-list="false" @change="handleUpload" > <el-button type="text" icon="el-icon-upload2" @click="handleUpload" ></el-button> </el-upload> </el-tooltip> </div> </template> </el-table-column> </el-table> </div> </template> <script> import request from "@/utils/request"; export default { data() { return { tableData: [], }; }, mounted() { // 预览挂载 this.fetchTableDate(); }, methods: { // 文件上传 async uploadFile(option) { // 可以以同步的方式编写异步代码,暂停函数的执行,等待一个Promise解析(resolve或reject),然后继续执行函数。 const formData = new FormData(); formData.append("file", option.file); try { const response = await request.post( "http://localhost:8080/file/upload", formData ); if (response.code === 200) { this.$message.success("成功上传"); this.fetchTableDate(); // 重新加载表格数据 } else { this.$message.error( `上传失败: ${response ? response.message : "未知错误"}` ); } } catch (error) { this.$message.error(`上传失败: ${error.message}`); } }, // 文件上传 handleUpload() { this.$refs.upload.submit(); // 引用为upload的表单元素并提交 }, // 预上传处理函数 beforeUpload(file) { return true; // 可以在此处添加例如检查文件类型、大小等逻辑 }, // 文件预览 触发查询 fetchTableDate() { request .get("http://localhost:8080/file/preview") .then((res) => { this.tableData = res.data.data; }) .catch((error) => { this.$message.error("获取文件有误", error); }); }, // 点击预览 handleView(row) { const fileUrl = row.fileAddress; window.open(fileUrl, "_blank"); // 在新窗口中打开预览URL }, // 文件下载 handleDownload(row) { const fileName = row.fileNewName; // 发起请求到后端下载 request({ url: "http://localhost:8080/file/download", method: "get", responseType: "blob", // 指定类型为 blob params: { fileName }, }) .then((res) => { // this.$message.success(res.msg); // 创建一个 URL 链接到文件内容 const url = window.URL.createObjectURL(res); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName); // 设置下载属性 document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); // 释放URL对象 }) .catch((error) => { this.$message.error("下载有误", error.message); }); }, // 删除文件 handleDelete(row) { const fileName = row.fileNewName; // 文件名称 console.log("删除文件--", fileName); this.$confirm("确定文件永久删除吗,是否继续", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(() => { request({ url: "http://localhost:8080/file/delete", method: "delete", params: { fileName }, }) .then((res) => { this.$message.success(res.msg); this.fetchTableDate(); // 重新加载表格数据 }) .catch((error) => { this.$message.error(res.msg); }); }) .catch((error) => { this.$message.error("文件删除有误,请稍后重试"); }); }, }, }; </script> <style scoped> .operation-icons { display: flex; justify-content: space-around; align-items: center; width: 150px; /* 宽度调整 */ } .operation-icons .el-button { padding: 0; } </style>
点击下载,通过浏览器下载本地路径。