目录
一、主流方法介绍
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 "文件上传失败";}}
}