SpringBoot+Vue 2 多方法实现(图片/视频/报表)文件上传下载,示例超详细 !

目录

一、主流方法介绍

1. Base 64

2. 二进制流传输

3. multipart/form-data

4. FTP/SFTP

5. 云存储服务API

二、multipart/form-data 方式上传单个文件

1、前端部分

2、后端部分

三、multipart/form-data 方式上传多个文件

1、前端部分

 2、后端部分

四、Base 64 方式上传单个或多个文件

1、Base64 和 二进制流 是两种不同的概念:

2、前端部分

3、后端部分

五、二进制流传输文件

1、前端部分

2、后端部分


一、主流方法介绍

1. Base 64

优点:简单易用,可以直接嵌入到文本中。适用于小文件或需要将文件嵌入到JSON等文本格式中的场景。
缺点:编码后的数据大小会增加约33%,不适合大文件传输。需要额外的编码和解码步骤,增加了处理时间。

2. 二进制流传输

优点:传输效率高,适合大文件传输。不需要额外的编码和解码步骤。
缺点:实现相对复杂,需要处理二进制数据。不适合直接嵌入到文本协议中。

3. multipart/form-data

优点:通过HTTP POST请求中的multipart/form-data格式上传文件,易于实现。支持同时上传多个文件和文本字段。
缺点:性能略低于二进制流传输。处理大文件时可能需要更多的内存和处理时间。

4. FTP/SFTP

优点:适合需要长期稳定传输大量文件的场景。提供了丰富的文件管理功能,如目录操作、权限管理等。
缺点:需要额外的服务器配置和维护。安全性相对较低(FTP),SFTP更安全但配置复杂。

5. 云存储服务API

优点:高性能和高可用性,通常具有CDN加速功能。提供丰富的API和SDK,易于集成。安全性高,支持多种认证机制。(如阿里云OSS、AWS S3等)
缺点:需要支付云服务费用。可能存在网络延迟问题。

下面将主要对前三种方式进行演示说明,并附上前后端的示例代码。

二、multipart/form-data 方式上传单个文件

 

1、前端部分

<template><div class="uploadFile"><section><div class="selectFileBox"><el-uploadref="uploadExcel"action="/sensorjxpro/eutest/excel":limit="limitNum"name="file":auto-upload="true"accept=".xlsx":before-upload="beforeUploadFile":before-remove='removeFile':on-exceed="exceedFile":on-error="handleError":file-list="fileList":http-request="httpRequest"><div class="img"><img src="@/assets/upload.png" alt=""></div><el-button size="small" type="primary">选择文件</el-button><div slot="tip" class="el-upload__tip">只能上传xlsx文件,且不超过10M</div></el-upload><br/><el-button  size="small" type="primary" @click="goReview" :disabled="isCanClick==true ? false : true">预览文件</el-button></div></section></div>
</template><script>import {UploadFile} from '@/api/request'import domMessage from '@/utils/messageOnce'const messageOnce = new domMessage()
export default {data(){return {limitNum:1,fileList:[],isCanClick:false}},methods:{goReview(){this.$router.push('/sensorjxpro/web/operationfile/review')this.$emit('setActive', 1);},removeFile(){this.fileList=[]this.isCanClick = false},// 上传文件之前的钩子, 参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传beforeUploadFile(file) {const extension = file.name.substring(file.name.lastIndexOf('.') + 1)const size = file.size / 1024 / 1024if (extension !== 'xlsx' || file.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {messageOnce.error({message:'请上传后缀为.xlsx的excel文件',type:'warning'})this.isCanClick = false}if (size > 10) {messageOnce.error({message:'文件大小不得超过10M',type:'warning'})this.isCanClick = false}},// 文件超出个数限制时的钩子exceedFile(files, fileList) {messageOnce.error({type:'warning',message: `只能选择 ${this.limitNum} 个文件,当前共选择了 ${files.length + fileList.length} 个`})this.isCanClick = false},// 文件上传失败时的钩子handleError(err) {this.$message.error(err.msg)this.isCanClick = false},httpRequest(item){console.log('item',item);this.fileList = item.filethis.importData()},importData(){let formData = new FormData() //创建一个 FormData 对象formData.append('file', this.fileList) // 将文件添加到 FormData 对象中UploadFile(formData).then((res)=>{ // 调用 UploadFile API 上传文件,并处理响应。const resData = JSON.parse(res.data)if(resData.result == 1){this.$toast('上传成功')// 将表头和表格数据存起来sessionStorage.setItem('SET_EXCELHEADER',JSON.stringify(resData.data.excelHeaders))sessionStorage.setItem('SET_EXCELDATA',JSON.stringify(resData.data.inJxContents))// this.$store.commit('SET_EXCELHEADER',resData.data.excelHeaders)// this.$store.commit('SET_EXCELDATA',resData.data.inJxContents)this.isCanClick = true // 启用预览按钮}console.log('上传文件res',res);})}}}
</script><style lang="scss" scoped>@import '@/style/uploadFile/uploadFile.scss'
</style>

对于上传的组件,使用的是element ui  组件 | Element

          <el-uploadref="uploadExcel"action="/sensorjxpro/eutest/excel":limit="limitNum"name="file":auto-upload="true"accept=".xlsx":before-upload="beforeUploadFile":before-remove='removeFile':on-exceed="exceedFile":on-error="handleError":file-list="fileList":http-request="httpRequest">
****************************************************************
el-upload 是 Element UI 提供的文件上传组件。
ref="uploadExcel":给组件设置一个引用名称,方便在 JavaScript 中操作。
action="/sensorjxpro/eutest/excel":文件上传的 URL。
:limit="limitNum":限制上传文件的数量,limitNum 是一个绑定的数据属性。
name="file":上传文件的表单字段名。
:auto-upload="true":是否自动上传文件。
accept=".xlsx":只允许上传 .xlsx 文件。
:before-upload="beforeUploadFile":文件上传前的钩子函数。
:before-remove="removeFile":文件移除前的钩子函数。
:on-exceed="exceedFile":超过文件数量限制时的钩子函数。
:on-error="handleError":文件上传失败时的钩子函数。
:file-list="fileList":已上传文件列表,fileList 是一个绑定的数据属性。
:http-request="httpRequest":自定义上传请求的函数。

2、后端部分

使用 MultipartFile 对象进行接收,以及后续的逻辑操作

    @PostMapping("/upload")public RespondDto uploadFile(@RequestParam("file") MultipartFile multipartFile) {ExcelOperatVo excelOperatVo = excelOperatService.uploadExcel(multipartFile);return new RespondDto(excelOperatVo);}
    public ExcelOperatVo uploadExcel(MultipartFile multipartFile) {//       保存文件到本地File dir = new File("uploadFile/excel");if (!dir.exists()) {dir.mkdirs();}LocalDateTime current = LocalDateTime.now();DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");String formatted = current.format(formatter);//加上三位随机数Random random = new Random();int end3 = random.nextInt(999);File file = new File(dir.getAbsolutePath() + File.separator + formatted + "-" + end3 + "-" + multipartFile.getOriginalFilename());try {multipartFile.transferTo(file);} catch (IOException e) {e.printStackTrace();}log.info("【上传文件》文件已保存到本地:{}】",file.getAbsolutePath());
//        获取excel文件内容ArrayList<InJxContent> inJxContents = readExcel(file.getAbsolutePath());//        读取excel表头 新建动态数组,返回两个数组SSExcel07Sheet sheet = new SSExcel07Workbook().open(file.getAbsolutePath()).getSheet(0);ArrayList<ExcelHeaderTvo> excelHeaderTvos = new ArrayList<>();for (int i = 0; i < 7; i++) {String cellValue = sheet.getCell(0, i).getCellValue().toString();ExcelHeaderTvo excelHeaderTvo = new ExcelHeaderTvo(cellValue, cellValue, cellValue, "center", "13%", true);if(i <= 1){excelHeaderTvo.setEdit(false);}switch (i) {case 0:excelHeaderTvo.setField("tel");break;case 1:excelHeaderTvo.setField("name");break;case 2:excelHeaderTvo.setField("degree");break;case 3:excelHeaderTvo.setField("attitude");break;case 4:excelHeaderTvo.setField("duty");break;case 5:excelHeaderTvo.setField("pMPoints");break;case 6:excelHeaderTvo.setField("jxPoints");break;case 7:excelHeaderTvo.setField("coefficient");break;}excelHeaderTvos.add(excelHeaderTvo);}ExcelOperatVo excelOperatVo = new ExcelOperatVo(excelHeaderTvos, inJxContents);return excelOperatVo;}
 private ArrayList<InJxContent> readExcel(String filePath) {SSExcel07Sheet sheet = (new SSExcel07Workbook()).open(filePath).getSheet(0);ArrayList<InJxContent> inJxContents = new ArrayList<>();log.info("【readExcel方法开始:】");//外层控制行,每行为一个jxContent对象for (int i = sheet.getFirstRowNum() + 1; i <= sheet.getLastRowNum(); i++) {InJxContent injxContent = new InJxContent();//内层各项为各列属性值for (int j = 0; j <= 7; j++) {if (sheet.getCell(i, j).getCellValue() != null || j == 2) {switch (j) {case 0:injxContent.setTel(BigInteger.valueOf(Long.valueOf((String) sheet.getCell(i, j).getCellValue())));break;case 1:injxContent.setName((String) sheet.getCell(i, j).getCellValue());break;case 2:Double d1 = sheet.getCell(i, j).getXSSFCell().getNumericCellValue() * 100;injxContent.setDegree(new DecimalFormat("#").format(d1) + "%");break;case 3:injxContent.setAttitude(Integer.valueOf((String) sheet.getCell(i, j).getCellValue()));break;case 4:injxContent.setDuty(Integer.valueOf((String) sheet.getCell(i, j).getCellValue()));break;case 5:injxContent.setPmPoints((String) sheet.getCell(i, j).getCellValue());break;case 6:Float v = Float.parseFloat((String) sheet.getCell(i, j).getCellValue());String format = new DecimalFormat("#.#").format(v);injxContent.setJxPoints(Float.parseFloat(format));break;case 7:injxContent.setCoefficient(Float.parseFloat((String) sheet.getCell(i, j).getCellValue()));break;default:log.info("执行了default");break;}}}injxContent.setRowkey(i);inJxContents.add(injxContent);}log.info("【上传文件:】excel:内容:{}",inJxContents);return inJxContents;}

三、multipart/form-data 方式上传多个文件

        单文件和多文件上传没用太大的区别,但是还是分开介绍一下,此处支持选择一个或多个文件文件后,点击上传一次性全部上传完

 这段代码跟单个文件上传相比,主要是加入了循环遍历的逻辑:

      // 获取文件输入框的引用const fileInputs = this.$refs.uploadForm.querySelectorAll('input[type="file"]');let hasFileSelected = false;fileInputs.forEach((fileInput) => {const fileList = fileInput.files;for (let i = 0; i < fileList.length; i++) {formData.append('multipartFiles', fileList[i]);formData.append('types', fileInput.id);hasFileSelected = true;}});

1、前端部分

<template><div><h1>文件上传和导出</h1><form ref="uploadForm" @submit="uploadFormSubmit"  enctype="multipart/form-data"><div><label for="初测主特性">初测主特性文件:</label><input type="file" id="初测主特性" name="files[]" accept=".xlsx"><el-button class="export-file" >从数据库中导出</el-button></div>。。。<div><label for="指标要求">指标要求文件:</label><input type="file" id="指标要求" name="files[]" accept=".xlsx"><el-button class="export-file" >从数据库中导出</el-button></div><el-button type="primary" @click="uploadFormSubmit">上传</el-button><el-button :disabled="!fileUrls" @click="exportButtonClick">导出测试报告</el-button></form></div>
</template>
<script>
export default {data() {return {fileUrls: null,};},methods: {uploadFormSubmit(e) {e.preventDefault();const formData = new FormData();// 获取文件输入框的引用const fileInputs = this.$refs.uploadForm.querySelectorAll('input[type="file"]');let hasFileSelected = false;fileInputs.forEach((fileInput) => {const fileList = fileInput.files;for (let i = 0; i < fileList.length; i++) {formData.append('multipartFiles', fileList[i]);formData.append('types', fileInput.id);hasFileSelected = true;}});if (!hasFileSelected) {alert('请至少选择一个文件!');return;}this.showLoadingMessage('文件上传中...');this.$request.post('/manage/server/uploadExcels', formData).then((response) => {// console.log(response.data);const data = response.data;if (data.result === 1 && data.data.length > 0) {this.fileUrls = data.data;this.showMessage('文件上传完成!', 'success');}}).catch((error) => {console.error('上传失败:', error);this.showMessage('文件上传失败,请重试!', 'error');});},exportButtonClick() {const fileUrls = this.fileUrls;console.log(fileUrls);if (!fileUrls) {alert('请先上传文件!');return;}this.showLoadingMessage('文件导出中...');this.$request.get('/manage/server/writeExcel', {params: {// fileUrls: fileUrls,// 通过将参数值转换为逗号分隔的字符串,可以避免方括号被自动编码,从而解决这个异常问题。记得在后端接收参数时进行相应的处理,将逗号分隔的字符串转换为数组再进行后续操作。fileUrls: fileUrls.join(',')},responseType: 'blob'}).then((response) => {const blob = new Blob([response.data]);const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = '测试报告.xlsx';a.click();this.showMessage('文件导出完成!', 'success');setTimeout(() => {const fileInputs = this.$refs.uploadForm.querySelectorAll('input[type="file"]');fileInputs.forEach((fileInput) => {fileInput.value = '';});this.fileUrls = null;const messageElements = document.querySelectorAll('.loading, .success, .error');messageElements.forEach((messageElement) => {messageElement.remove();});}, 5000)}).catch((error) => {console.error('导出失败:', error);this.showMessage('文件导出失败,请重试!', 'error');});},showLoadingMessage(message) {const loadingElement = document.createElement('div');loadingElement.classList.add('loading');loadingElement.innerText = message;this.$refs.uploadForm.appendChild(loadingElement);},showMessage(message, type) {const messageElement = document.createElement('div');messageElement.classList.add(type);messageElement.innerText = message;this.$refs.uploadForm.appendChild(messageElement);}}
}
</script>

 2、后端部分

后端接收参数则从 MultipartFile 变成了 MultipartFile[] 

    /*** 1、接受前端传入的文件数组和文件类型对应的数组【文件和类型数组要一一对应】,下载到本地,并将下载到本地的文件路径返回给前端* 2、url: http://localhost:xxxxx/manage/server/uploadExcels* @param multipartFiles* @param types*/@PostMapping(value = "/uploadEndTestExcels")public RespondDto upLoadFiles(@NonNull @RequestParam("multipartFiles") MultipartFile[] multipartFiles,@NonNull @RequestParam("types") String[] types) throws IOException {return fileService.upLoadFiles(multipartFiles,types);}/*** 1、接受前端传入的文件路径参数,对文件进行解析,最终生成/导出一个最终的Excel文件* 2、url: http://localhost:xxxx/manage/server/writeExcel* @param fileUrls* @throws IOException* @throws URISyntaxException*/@GetMapping(value = "/writeEndTestExcel")public void writeExcel(HttpServletResponse response ,@RequestParam("fileUrls") String[] fileUrls) throws IOException, URISyntaxException {fileService.writeExcel(response,fileUrls);}
    /*** 实现多文件一次性上传功能【上传多个Excel文件,并获取每个上传文件的类型】** 0、实现上传的文件保存到本地* 1、将每个文件的名字以文件类型的名字进行重命名,并且保存到指定文件路径下,并且将文件文件路径以数组的形式返回给前端*/public RespondDto upLoadFiles(MultipartFile[] files, String[] types) throws IOException {String formattedDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));//加上三位随机数int randomNum = new Random().nextInt(999);//将每次上传的Excel文件放到指定的文件夹下File dir = new File(absolutePath + File.separator + "uploadFile" + File.separator + formattedDate + randomNum);if (!dir.exists()) {dir.mkdirs();}// 将重命名文件名变量fileRename移至循环外部,初始化为nullString fileRename = null;// 将fileToSave移至循环外部,初始化为nullFile fileToSave;List<String> fileUrls = new ArrayList<String>();// 存储已上传的type类型Set<String> uploadedTypes = new HashSet<String>();for (int i = 0; i < files.length; i++) {MultipartFile file = files[i];String type = types[i];// 判断文件或类型不能为空,不能上传空文件if (file.isEmpty() || StringUtils.isEmpty(type)) {return new RespondDto<>("文件或类型不能为空,请重新上传!");}String originalFileName = file.getOriginalFilename();// 判断文件后缀名是否符合要求if (!originalFileName.endsWith("xlsx")) {log.error(originalFileName + "不是.xlsx后缀的Excel文件!");throw new RuntimeException("仅支持.xlsx后缀的Excel文件!");}uploadedTypes.add(type);String fileExt = originalFileName.substring(originalFileName.lastIndexOf("."));Workbook workbook = new XSSFWorkbook(file.getInputStream());Sheet sheet = workbook.getSheet("XXXX表");if (Objects.isNull(sheet)) {Sheet sheet2 = workbook.getSheet("增益压缩");Row row = sheet2.getRow(0);Cell cell = row.getCell(0);if (cell != null && Objects.equals(cell.getCellTypeEnum(), CellType.STRING)) {String value = cell.getStringCellValue();if (value.equals("测试项目")) {fileRename = type + "指标";}}} else {Row row = sheet.getRow(0);Cell cell = row.getCell(7);if (cell != null && Objects.equals(cell.getCellTypeEnum(), CellType.STRING)) {String value = cell.getStringCellValue();fileRename = type + "XX性" + value.substring(value.length() - 3);}}// 在循环内部为fileToSave赋值fileToSave = new File(dir.getAbsolutePath() + File.separator + fileRename + fileExt);if (fileToSave.exists()) {fileToSave.delete();}FileOutputStream os = null;try {os = new FileOutputStream(fileToSave);IOUtils.copy(file.getInputStream(), os);String fileUrl = fileToSave.getAbsolutePath();fileUrls.add(fileUrl);} catch (Exception e) {e.printStackTrace();return new RespondDto<>("文件上传失败!");} finally {if (os != null) {IOUtils.closeQuietly(os);}}log.info("【源文件】" + originalFileName + "已保存到本地,【文件名】是 " + fileRename + " 【存放路径是】 " + fileToSave.getAbsolutePath());}return new RespondDto<>(fileUrls);}
    /*** 实现多文件解析并导出生成测试报告功能** 0、将源文件路径遍历出来,每次都放到输入流中,同时去遍历配置文件,将类型名称与文件名称符合的配置文件读取出来* 1、进行copy操作,最后将源文件路径遍历完后将目标Excel进行保存*/@Overridepublic RespondDto writeExcel(HttpServletResponse response, String[] fileUrls) throws IOException, URISyntaxException {String excelFilePath = absolutePath + File.separator + "template.xlsx";FileInputStream destinationFileInputStream = new FileInputStream(excelFilePath);Workbook destinationWorkbook = new XSSFWorkbook(destinationFileInputStream);List<String> errorMessages = new ArrayList<>();Integer currentRow = 0;for (String url : fileUrls) {// 追踪当前需要写入的行数processFile(url, destinationWorkbook, configCheck.getConfig(), errorMessages , currentRow);if(url != null && url.contains("符合性")){currentRow++;}}destinationWorkbook.setForceFormulaRecalculation(true);destinationWorkbook.write(response.getOutputStream());destinationFileInputStream.close();destinationWorkbook.close();if (errorMessages.isEmpty()) {return new RespondDto<>("Excel导出成功!");} else {String errorInfo = String.join("\n", errorMessages);return new RespondDto<>("Excel导出过程中发生错误:\n" + errorInfo);}}

四、Base 64 方式上传单个或多个文件

1、Base64 和 二进制流 是两种不同的概念:

二进制流:
直接传输文件的原始字节数据。适用于大文件传输,效率高。不需要额外的编码和解码步骤。
Base64:
将二进制数据转换为文本形式,以便在文本协议(如HTTP)中传输。编码后的数据大小会增加约33%。需要在发送前进行编码,在接收后进行解码。

2、前端部分

        <el-col :span="24"><el-form-item label="上传文件:"><el-button type="primary" @click="handleFileSelect">拍照/视频</el-button><input id="fileInput" type="file" ref="fileInput" multiple accept="image/*,video/*"@change="handleFileChange" style="display: none;"><div v-for="(file, index) in form.fileList" :key="index"><el-image :src="file.content" v-if="file.type === 'image'"                style="max-width: 100px; max-height: 100px;"></el-image><video v-else-if="file.type === 'video'" :src="file.content" controls                style="max-width: 100px; max-height: 100px;"></video><i class="el-icon-delete" @click="deleteFile(index)"></i></div><div style="display: block; font-size: 12px; color: #888;">支持一次上传一个/多个文件,但不是必选项,若无法描述清楚,或需操作多步骤时应拍照/视频</div></el-form-item></el-col>
    handleFileSelect() {console.log('文件选择操作触发');this.$refs.fileInput.click();},handleFileChange(event) {console.log('文件改变事件触发');let files = event.target.files;for (let i = 0; i < files.length; i++) {const file = files[i];// 检查文件类型是否符合要求if (!this.checkFileType(file)) {alert('文件格式不符合要求,图片只能识别png、jpg、jpeg,视频只能识别mp4、avi、mov !');continue;}const reader = new FileReader();reader.onload = e => {let fileType = file.type.includes('video') ? 'video' : 'image';this.form.fileList.push({ content: e.target.result, type: fileType, file: file });};reader.readAsDataURL(file);}},checkFileType(file) {const imageTypes = ['image/png', 'image/jpeg', 'image/jpg'];const videoTypes = ['video/mp4', 'video/avi', 'video/quicktime'];if (file.type.includes('image')) {return imageTypes.includes(file.type);} else if (file.type.includes('video')) {return videoTypes.includes(file.type);}return false;},toBase64(file, callback) {console.log('开始转换文件为Base64编码');const reader = new FileReader();reader.readAsDataURL(file);reader.onload = () => {const base64 = reader.result.split(',')[1];callback(base64);};reader.onerror = error => {console.error('Error converting file to base64:', error);};},saveDataToBackend() {console.log('开始保存数据到后端');if (!this.form.operation) {alert('请完成备注后再保存!');return;}this.loading = true; // 开始保存时显示loading图标const reqData = {productId: this.form.productId,currentLocation: this.form.currentLocation,operation: this.form.operation, // 添加操作种类fileList: this.form.fileList.length === 0 ? null : [], // 根据文件列表是否为空初始化 fileListuserIndex: this.getPersonIndex(),questReason: this.form.questReason, // 问题描述isUsed: sessionStorage.getItem(key_DingResponseUsed) || cachedResponseUsed ,isStored: sessionStorage.getItem(key_DingResponseStored) || cachedResponseStored,belongContent:this.form.belongContent //归属项目};// 如果 fileList 不为空,进行 Base64 转换if (this.form.fileList.length > 0) {const promises = this.form.fileList.map(file => {return new Promise((resolve) => {this.toBase64(file.file, base64 => {reqData.fileList.push({content: base64,type: file.type,});resolve();});});});// 等待所有文件转换完成后再发送请求Promise.all(promises).then(() => {// 发送请求到后端return SensorBorderRequest.SaveAllCardRecords(reqData);}).then(response => {console.log('保存成功:', response);this.$message.success('保存成功!');setTimeout(() => {this.resetTestingFields(); // 重置表单字段this.$router.push("/ddinguia/web/history");}, 500); // 0.5 秒后跳转}).catch(error => {console.error('保存失败:', error);this.$message.error('保存失败!');}).finally(() => {this.loading = false; // 保存完成后取消loading状态});} else {// 如果 fileList 为空,直接发送请求SensorBorderRequest.SaveAllCardRecords(reqData).then(response => {console.log('保存成功:', response);this.$message.success('保存成功!');setTimeout(() => {this.resetTestingFields(); // 重置表单字段this.$router.push("/ddinguia/web/history");}, 500); // 0.5 秒后跳转}).catch(error => {console.error('保存失败:', error);this.$message.error('保存失败!');}).finally(() => {this.loading = false; // 保存完成后取消loading状态});}},

3、后端部分

对于后端部分,接收到的文件就是以base64编码的字符串,按照对象格式进行接收转化,再解码就可以保存到服务器上了

    public class ContentFile {private String name;//文件名private String content;// base64private String type; // 图片或者视频}private List<String> saveFilesByType(List<ContentFile> files) {if (files == null || files.isEmpty()) {return new ArrayList<>();}List<String> urlList = new ArrayList<>();for (ContentFile file : files) {if (file == null || file.getContent() == null || file.getContent().isEmpty()) {continue;}String fileType = file.getType();String extension = getFileExtensionByType(fileType);if (extension.isEmpty()) {// 不支持的文件类型,进行相应的处理continue;}String fileName;if (file.getName() != null && !file.getName().isEmpty()) {// 获取UUID,并截取前三位
//                String shortUUID = UUID.randomUUID().toString().substring(0, 3);//fileName = file.getName() + shortUUID + extension;fileName = file.getName() +  extension;} else {
//                System.out.println("44444444444");// 获取当前时间Date currentDate = new Date();// 格式化日期时间SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");String formattedDate = dateFormat.format(currentDate);// 获取UUID,并截取前三位String shortUUID = UUID.randomUUID().toString().substring(0, 3);// 构造新的文件名fileName = fileType + "_" + formattedDate + "_" + shortUUID + extension;}File directory = new File(fileProperties.getSavePath() + File.separator + fileType + "s");System.out.println("fileProperties.getSavePath()对应的路径是:" + directory);if (!directory.exists()) {directory.mkdirs();}String fileSavePath = fileProperties.getSavePath() + fileType + "s" + File.separator + fileName;System.out.println("fileSavePath文件保存的路径是:" + fileSavePath);try (FileOutputStream fos = new FileOutputStream(fileSavePath)) {byte[] decodedBytes = Base64.getDecoder().decode(file.getContent());fos.write(decodedBytes);urlList.add("static/ddinguia" + File.separator + "server" + File.separator + "files" + File.separator + fileType + "s" + File.separator + fileName);System.out.println(urlList);} catch (IOException e) {throw new RuntimeException(e);}}return urlList;}

关于base 64的工具类 可以自己写,也可以使用 java.util 自带的

五、二进制流传输文件

1、前端部分

        使用FormData对象可以方便地处理文件上传,但这里为了演示二进制流传输,直接将文件读取为二进制数据

<template><div><input type="file" @change="handleFileChange" /><button @click="uploadFile">上传</button></div>
</template><script>
import axios from 'axios';export default {data() {return {file: null,};},methods: {handleFileChange(event) {this.file = event.target.files[0];},async uploadFile() {if (!this.file) {alert('请选择一个文件');return;}const formData = new FormData();formData.append('file', this.file);try {const response = await axios.post('http://localhost:8080/upload', formData, {headers: {'Content-Type': 'application/octet-stream',},});console.log('文件上传成功', response.data);} catch (error) {console.error('文件上传失败', error);}},},
};
</script>

2、后端部分

        Spring Boot 使用@RequestBody注解接收二进制数据,并将其保存到指定路径。

import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;@RestController
public class FileUploadController {@PostMapping("/upload")public String uploadFile(@RequestBody byte[] fileData) {try {// 保存文件到指定路径Path path = Path.of("path/to/save/file");Files.write(path, fileData, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);return "文件上传成功";} catch (IOException e) {e.printStackTrace();return "文件上传失败";}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/475117.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小米顾此失彼:汽车毛利大增,手机却跌至低谷

科技新知 原创作者丨依蔓 编辑丨蕨影 三年磨一剑的小米汽车毛利率大增&#xff0c;手机业务毛利率却出现下滑景象。 11月18日&#xff0c;小米集团发布 2024年第三季度财报&#xff0c;公司实现营收925.1亿元&#xff0c;同比增长30.5%&#xff0c;预估902.8亿元&#xff1b;…

unity中:超低入门级显卡、集显(功耗30W以下)运行unity URP管线输出的webgl程序有那些地方可以大幅优化帧率

删除Global Volume&#xff1a; 删除Global Volume是一项简单且高效的优化措施。实测表明&#xff0c;这一改动可以显著提升帧率&#xff0c;甚至能够将原本无法流畅运行的场景变得可用。 更改前的效果&#xff1a; 更改后的效果&#xff1a; 优化阴影和材质&#xff1a; …

webgl threejs 云渲染(服务器渲染、后端渲染)解决方案

云渲染和流式传输共享三维模型场景 1、本地无需高端GPU设备即可提供三维项目渲染 云渲染和云流化媒体都可以让3D模型共享变得简单便捷。配备强大GPU的远程服务器早就可以处理密集的处理工作&#xff0c;而专有应用程序&#xff0c;用户也可以从任何个人设备查看全保真模型并与…

React Native 基础

React 的核心概念 定义函数式组件 import组件 要定义一个Cat组件,第一步要使用 import 语句来引入React以及React Native的 Text 组件: import React from react; import { Text } from react-native; 定义函数作为组件 const CatApp = () => {}; 渲染Text组件

【专题】中国企业出海洞察报告暨解码全球制胜之道报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p38314 在当今全球化的浪潮中&#xff0c;中国企业的出海行动正以前所未有的规模和速度展开&#xff0c;成为全球经济舞台上的重要力量。本报告旨在对 2024 年中国企业出海情况进行深度洞察&#xff0c;涵盖多个领域和视角。 从对外投…

『VUE』32. 动态组件,组件的动态切换(详细图文注释)

目录 动态组件示例代码总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 动态组件 有些时候我们的组件需要切换,这个时候用到<conponent></conponent>标签, <component :is"choseComponent"><…

传输层协议TCP

一.TCP协议格式 对于传输层协议我们之前是学过了UDP&#xff0c;对于传输层协议是存在了一定的了解的&#xff0c;所以现在我们再来看TCP协议格式&#xff1a; 我们之前学过UDP的报文格式&#xff0c;所以源端口和目的端口是不需要进行再次讲解的&#xff0c;对于32序号和确认序…

不能打开网页,但能打开QQ、微信(三种方式)

1.VPN错误 下面三个开关全关闭 2.DNS问题 WINR 输入CMD打开命令行 命令行输入 ipconfig/flushdns 重启电脑 3.直接火绒&#xff08;一键修复&#xff09;

Unity类银河战士恶魔城学习总结(P132 Merge skill tree with skill Manager 把技能树和冲刺技能相组合)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了解锁技能后才可以使用技能&#xff0c;先完成了冲刺技能的锁定解锁 Dash_Skill.cs using System.Collections; using System…

Linux-第2集-打包压缩 zip、tar WindowsLinux互传

欢迎来到Linux第2集&#xff0c;这一集我会非常详细的说明如何在Linux上进行打包压缩操作&#xff0c;以及解压解包 还有最最重要的压缩包的网络传输 毕竟打包压缩不是目的&#xff0c;把文件最终传到指定位置才是目的 由于打包压缩分开讲没有意义&#xff0c;并且它们俩本来…

mysql delete后通过日志恢复数据

1.打开navicat查看删除时间 2.查看日志功能是否打开 show variables like %log_bin%;3. 查看日志文件所在目录 show variables like %datadir%;4.用这个路径去找日志文件&#xff0c;名字里带bin&#xff0c;最后修改时间和你第一步找到删除时间一样(如果之后有过其它增删改…

鸿蒙NEXT开发-用户通知服务的封装和文件下载通知

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

C++——智能指针剖析

参考&#xff1a; 恋恋风辰官方博客 动态内存管理 - cppreference.com SRombauts/shared_ptr&#xff1a; 一个最小的 shared/unique_ptr 实现&#xff0c;用于处理 boost/std&#xff1a;&#xff1a;shared/unique_ptr 不可用的情况。 C智能指针_c 智能指针-CSDN博客 当…

【java】java入门

盘符名称冒号---------盘符切换 dir---------------查看当前路径下的内容 cd目录--------进入单级目录 cd..----------回退到上一级目录 cd \----------回退到盘符目录 cls----------清屏 exit 为什么要配环境变量&#xff1f; 在任意的目录下都可以打开指定的软件。把软件的路…

11.19.2024刷华为OD

文章目录 HJ51HJ53 杨辉三角HJ56HJ57 高精度整数加法HJ58HJ60 简单题HJ63 DNA序列&#xff08;简单题&#xff09;语法知识记录 HJ51 https://www.nowcoder.com/practice/54404a78aec1435a81150f15f899417d?tpId37&tags&title&difficulty0&judgeStatus0&…

数据挖掘英语及概念

分类 classify 上涨或跌 回归 regression 描述具体数值 分类模型评估 1.混淆&#xff08;误差&#xff09;矩阵 confusion matrix 2.ROC曲线 receiver operating characteristic curve 接收者操作特征曲线 3.AUC面积 area under curve ROC曲线下与坐标轴围成的面积&#x…

GOLANG+VUE后台管理系统

1.截图 2.后端工程截图 3.前端工程截图

go-zero(三) 数据库操作

go-zero 数据库操作 在本篇文章中&#xff0c;我们将实现一个用户注册和登录的服务。我们将为此构建一个简单而高效的 API&#xff0c;包括请求参数和响应参数的定义。 一、Mysql连接 1. 创建数据库和表 在 MySQL 中创建名为 test_zero的数据库&#xff0c;并创建user 表 …

MFC图形函数学习09——画多边形函数

这里所说的多边形是指在同一平面中由多条边构成的封闭图形&#xff0c;强调封闭二字&#xff0c;否则无法进行颜色填充&#xff0c;多边形包括凸多边形和凹多边形。 一、绘制多边形函数 原型&#xff1a;BOOL Polygon(LPPOINT lpPoints,int nCount); 参数&#x…

【算法】回文数索引、回文子串输出、整数反转

目录 回文数索引 思路&#xff1a; 回文子串输出 思路 回文数索引 思路&#xff1a; 目标字母索引可能是一个或者是两个&#xff0c;返回任意的一个索引即可&#xff0c;如果已经是回文串则直接返回-1。 下面列出几种目标删除字母可能出现的位置&#xff1a; 我们可以先定…