大文件斷點續傳(vue+springboot+mysql)

斷點續傳

  • vue前端代碼
  • 后端代碼
    • controller 層
    • service層
    • 持久層
      • 主表,初始化單次上傳
      • 文件表,單次上傳所有的文件記錄
      • 文件分塊表

科普信息參考其他博主

流程圖
上傳邏輯

vue前端代碼

這里是只做了demo示例,主線測試沒什么問題,前端同學可參考修改

// 注意,需要導入 axios和spark-MD5
import SparkMD5 from 'spark-md5';
import axios from 'axios';
async upload() {// 1. 驗證數據if (!this.verifyData.pass || this.fileList.length === 0) {this.$message.warning('無可上傳的數據');return;}try {// 2. 準備參數const chunkSize = 10 * 1024 * 1024;const params = JSON.parse(JSON.stringify(this.form.content));params.fileNames = this.fileList.map(file => file.name);const uploadInitParam = {totalFileSize: this.fileList.reduce((sum, file) => sum + file.size, 0),fileCount: this.fileList.length,fileType: params.fileType};// 3. 初始化上傳const initRes = await this.Ajax(API.bigFile.upload_init,uploadInitParam,'post',false,3000);if (initRes.status !== 0) {this.$message.warning('初始化失敗');return;}this.$message.success('初始化成功');const uploadInit = initRes.data;// 4. 順序上傳每個文件for (const file of this.genomeFileList) {await this.uploadSingleFile(file, uploadInit, chunkSize);}// 5. 準備最終參數this.prepareFinalParams(params, uploadInit.id);// 6. 上傳最終表單const uploadFormData = new FormData();if (this.excelList.length > 0) {uploadFormData .append('excelFile', this.excelList[0]);}uploadFormData .append('content', JSON.stringify(params));const finalRes = await this.Ajax(API.sample.save3,uploadFormData ,'post',true,30000);console.log("完成斷點續傳--------->res", finalRes);this.$message.success('文件上傳流程完成');} catch (error) {console.error('上傳過程中出錯:', error);this.$message.error(`上傳失敗: ${error.message}`);}
},
async uploadSingleFile(file, uploadInit, chunkSize) {// 1. 計算文件MD5const chunkCount = Math.ceil(file.size / chunkSize);const spark = new SparkMD5();spark.append(uploadInit.id);spark.append(file.name);spark.append(file.size.toString());spark.append(chunkCount.toString());const fileMd5 = spark.end();// 2. 檢查文件是否已上傳const checkRes = await this.Ajax(API.bigFile.upload_file_check,{uploadId: uploadInit.id,fileMd5: fileMd5},'get',true,30000);if (checkRes.status !== 0) {throw new Error(`驗證文件失敗: ${file.name}`);}if (checkRes.data) {this.$message.success(`文件已存在: ${file.name}`);return;}// 3. 初始化文件上傳const fileInitRes = await this.Ajax(API.bigFile.upload_file_init,{uploadId: uploadInit.id,fileMd5: fileMd5,fileName: file.name,fileSize: file.size,totalChunk: chunkCount,chunkSize: chunkSize},'post',true,30000);if (fileInitRes.status !== 0) {throw new Error(`初始化文件上傳失敗: ${file.name}`);}// 4. 順序上傳每個塊for (let chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++) {await this.uploadFileChunk(file, uploadInit, fileMd5, chunkIndex, chunkSize);}// 5. 合并文件const mergeRes = await this.Ajax(API.bigFile.upload_file_merge,{uploadId: uploadInit.id,fileMd5: fileMd5},'post',true,30000);if (mergeRes.status !== 0) {throw new Error(`文件合并失敗: ${file.name}`);}this.$message.success(`文件上傳完成: ${file.name}`);
},async uploadFileChunk(file, uploadInit, fileMd5, chunkIndex, chunkSize) {// 1. 準備文件塊const start = chunkIndex * chunkSize;const end = Math.min(file.size, start + chunkSize);const fileChunk = file.slice(start, end);// 2. 計算塊MD5const chunkFileMd5 = await this.calculateChunkMd5(fileChunk);// 3. 檢查塊是否已上傳const checkRes = await this.Ajax(API.bigFile.upload_file_chunk_check,{uploadId: uploadInit.id,fileMd5: fileMd5,chunkFileMd5: chunkFileMd5,chunkNumber: chunkIndex + 1},'get',false,30000);if (checkRes.status !== 0) {throw new Error(`檢查文件塊失敗: ${chunkIndex + 1}`);}if (checkRes.data) {this.$message.success(`文件塊已存在: ${file.name} chunk ${chunkIndex + 1}`);return;}// 4. 上傳塊const formData = new FormData();formData.append('file', fileChunk);formData.append('uploadId', uploadInit.id);formData.append('fileMd5', fileMd5);formData.append('chunkFileMd5', chunkFileMd5);formData.append('chunkNumber', chunkIndex + 1);const uploadRes = await this.Ajax(API.bigFile.upload_file_chunk,formData,'post',false,30000);if (uploadRes.status !== 0) {throw new Error(`上傳文件塊失敗: ${chunkIndex + 1}`);}// this.$message.success(`文件塊上傳完成: ${file.name} chunk ${chunkIndex + 1}`);
},
calculateChunkMd5(fileChunk) {return new Promise((resolve) => {const spark = new SparkMD5.ArrayBuffer();const fileReader = new FileReader();fileReader.onload = (e) => {spark.append(e.target.result);resolve(spark.end());};fileReader.onerror = () => {resolve('');};fileReader.readAsArrayBuffer(fileChunk);});
},

后端代碼

controller 層

@Api(tags = "斷點續傳")
@RestController
@RequestMapping("/uploads")
public class ResumableUploadController {@Autowiredprivate ResumableUploadService resumableUploadService;@Autowiredprivate Validator validator;@Autowiredprivate OtherConfig otherConfig;@PostMapping("/upload/init")@ApiOperation(value = "初始化本次上傳信息",notes = "初始化本次上傳信息")@UserRightAnnotationpublic R<Object> uploadInit(@Valid @RequestBody UploadInitDto uploadInitDto) {return R.isOk(resumableUploadService.uploadInit(uploadInitDto));}@GetMapping("/upload/file/check")@ApiOperation(value = "單文件-檢查是否已經合并完成",notes = "單文件-檢查是否已經合并完成")@UserRightAnnotationpublic R<Object> uploadFileCheck(@RequestParam String uploadId, @RequestParam String fileMd5) {return R.isOk(resumableUploadService.uploadFileCheck(uploadId,fileMd5));}@PostMapping("/upload/file/init")@ApiOperation(value = "單文件-初始化上傳文件信息",notes = "單文件-初始化上傳文件信息")@UserRightAnnotationpublic R<Object> uploadFileInit(@Valid @RequestBody UploadFileInitDto uploadFileInitDto) {return R.isOk(resumableUploadService.uploadFileInit(uploadFileInitDto));}@GetMapping("/upload/file/chunk/check")@ApiOperation(value = "單文件-塊-檢查是否已經上傳",notes = "單文件-塊-檢查是否已經上傳")@UserRightAnnotationpublic R<Object> uploadFileChunkCheck(@RequestParam String uploadId,@RequestParam String fileMd5,@RequestParam String chunkFileMd5,@RequestParam Integer chunkNumber) {return R.isOk(resumableUploadService.uploadFileChunkCheck(uploadId, fileMd5, chunkFileMd5,chunkNumber));}@PostMapping("/upload/file/chunk")@ApiOperation(value = "單文件-塊-上傳",notes = "單文件-塊-上傳")@UserRightAnnotationpublic R<Object> uploadFileChunk(UploadFileChunkDto uploadFileChunkDto) {Set<ConstraintViolation<UploadFileChunkDto>> violations = validator.validate(uploadFileChunkDto);if(!violations.isEmpty()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(), "參數錯誤!");}MultipartFile file = uploadFileChunkDto.getFile();if(Objects.isNull(file) || file.getSize() == 0){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"請上傳有內容文件!");}return R.isOk(resumableUploadService.uploadFileChunk(uploadFileChunkDto));}@PostMapping("/upload/file/merge")@ApiOperation(value = "單文件-合并",notes = "單文件-合并")@UserRightAnnotationpublic R<Object> uploadFileMerge(@Valid @RequestBody UploadFileMergeDto uploadFileMergeDto) {return R.isOk(resumableUploadService.uploadFileMerge(uploadFileMergeDto));}
}

service層

@Service
@Slf4j
public class ResumableUploadService {@Autowiredprivate UploadRepository uploadRepository;@Autowiredprivate UploadFileRepository uploadFileRepository;@Autowiredprivate UploadFileChunkRepository uploadFileChunkRepository;@Autowiredprivate OtherConfig otherConfig;@Autowiredprivate TaskConfig taskConfig;@Autowiredprivate FileService fileService;public Upload uploadInit(UploadInitDto uploadInitDto) {log.info("初始化上傳--------uploadInit---------->dto={}",JacksonUtil.toJson(uploadInitDto));User user = UserUtil.getCurrentUser();Upload uploadInit = new Upload();BeanUtils.copyProperties(uploadInitDto, uploadInit);uploadInit.setId(IdUtils.getUuid());uploadInit.setCreatedBy(user.getId());uploadInit.setCreatedDate(DateUtils.getCurrentDate());uploadInit.setUpdateDate(DateUtils.getCurrentDate());StringBuilder sb = new StringBuilder();sb.append(taskConfig.getUploadfilepath()).append(Constants.RESUMABLE_UPLOAD_FOLDERNAME).append(File.separator).append(uploadInit.getId());uploadInit.setPathname(sb.toString());return uploadRepository.save(uploadInit);}@Transactional(rollbackFor = Exception.class)@SneakyThrowspublic void uploadDelete(String uploadId) {log.info("刪除整個上傳批次--------uploadDelete---------->uploadId={}",uploadId);Optional<Upload> uploadOptional = uploadRepository.findById(uploadId);if(!uploadOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"uploadId錯誤");}Upload upload = uploadOptional.get();if(!StringUtils.equals(upload.getCreatedBy(),UserUtil.getCurrentUserId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"警告,非法刪除!");}uploadFileRepository.deleteByUploadId(uploadId);uploadRepository.deleteById(uploadId);File file = new File(upload.getPathname());if(file.exists()){FileUtils.deleteDirectory(new File(upload.getPathname()));}log.info("deleteUpload------------------->刪除信息已完成,uploadId={}",uploadId);}public boolean uploadFileCheck(String uploadId, String fileMd5) {log.info("檢查單個文件上傳是否完成--------uploadFileCheck---------->uploadId={},fileMd5={}",uploadId,fileMd5);Optional<Upload> uploadOptional = uploadRepository.findById(uploadId);if(!uploadOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"uploadId錯誤");}Upload upload = uploadOptional.get();if(!StringUtils.equals(upload.getCreatedBy(),UserUtil.getCurrentUserId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"非法請求!");}Optional<UploadFile> uploadFileOptional = uploadFileRepository.findById(fileMd5);if(!uploadFileOptional.isPresent()){return false;}UploadFile uploadFile = uploadFileOptional.get();if(Constants.NORMAL.equals(uploadFile.getMergeStatus())){// 已經合并完成return true;}return false;}public UploadFile uploadFileInit(UploadFileInitDto uploadFileInitDto) {log.info("初始化單個文件上傳--------uploadFileInit---------->dto={}",JacksonUtil.toJson(uploadFileInitDto));Optional<Upload> uploadOptional = uploadRepository.findById(uploadFileInitDto.getUploadId());if(!uploadOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"uploadId錯誤");}Upload upload = uploadOptional.get();if(!StringUtils.equals(upload.getCreatedBy(),UserUtil.getCurrentUserId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"非法請求!");}otherConfig.checkFileName(uploadFileInitDto.getFileName(),upload.getFileType());// 計算MD5是否和前端傳遞的MD5一致StringBuilder sb = new StringBuilder();sb.append(uploadFileInitDto.getUploadId()).append(uploadFileInitDto.getFileName()).append(uploadFileInitDto.getFileSize()).append(uploadFileInitDto.getTotalChunk());String fileMd5 = Md5Util.calculateMd5(sb.toString());if(!StringUtils.equals(fileMd5,uploadFileInitDto.getFileMd5())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件MD5不一致!");}Optional<UploadFile> uploadFileOptional = uploadFileRepository.findById(uploadFileInitDto.getFileMd5());if(uploadFileOptional.isPresent()){UploadFile uploadFile = uploadFileOptional.get();if(!StringUtils.equals(uploadFile.getUploadId(),uploadFileInitDto.getUploadId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"非法請求!");}// 已經存在return uploadFileOptional.get();}// 驗證文件名稱不能重復UploadFile uploadFile = uploadFileRepository.findByUploadIdAndFileName(upload.getId(),uploadFileInitDto.getFileName());if(!Objects.isNull(uploadFile)){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件名稱重復!");}uploadFile = new UploadFile();BeanUtils.copyProperties(uploadFileInitDto,uploadFile);uploadFile.setId(fileMd5);uploadFile.setMergeStatus(Constants.INVALID);uploadFile.setCreatedDate(DateUtils.getCurrentDate());return uploadFileRepository.save(uploadFile);}public boolean uploadFileChunkCheck(String uploadId, String fileMd5, String chunkFileMd5,Integer chunkNumber) {Optional<Upload> uploadOptional = uploadRepository.findById(uploadId);if(!uploadOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"uploadId錯誤");}Upload upload = uploadOptional.get();if(!StringUtils.equals(upload.getCreatedBy(),UserUtil.getCurrentUserId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"非法請求!");}Optional<UploadFile> uploadFileOptional = uploadFileRepository.findById(fileMd5);if(!uploadFileOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件信息不存在!");}UploadFile uploadFile = uploadFileOptional.get();if(!StringUtils.equals(uploadId,uploadFile.getUploadId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件信息不存在!");}if(chunkNumber > uploadFile.getTotalChunk()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊序號錯誤!");}UploadFileChunk uploadFileChunk = uploadFileChunkRepository.findByUploadIdAndUploadFileIdAndChunkFileMd5AndChunkNumber(uploadId,fileMd5,chunkFileMd5,chunkNumber);return !Objects.isNull(uploadFileChunk);}@SneakyThrowspublic UploadFileChunk uploadFileChunk(UploadFileChunkDto uploadFileChunkDto) {Optional<Upload> uploadOptional = uploadRepository.findById(uploadFileChunkDto.getUploadId());if(!uploadOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"uploadId錯誤");}Upload upload = uploadOptional.get();if(!StringUtils.equals(upload.getCreatedBy(),UserUtil.getCurrentUserId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"非法請求!");}Optional<UploadFile> uploadFileOptional = uploadFileRepository.findById(uploadFileChunkDto.getFileMd5());if(!uploadFileOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件信息不存在!");}UploadFile uploadFile = uploadFileOptional.get();if(!StringUtils.equals(uploadFileChunkDto.getUploadId(),uploadFile.getUploadId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件信息不存在!");}if(uploadFileChunkDto.getChunkNumber() > uploadFile.getTotalChunk()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊序號錯誤!");}UploadFileChunk uploadFileChunk = uploadFileChunkRepository.findByUploadIdAndUploadFileIdAndChunkFileMd5AndChunkNumber(uploadFileChunkDto.getUploadId(),uploadFileChunkDto.getFileMd5(),uploadFileChunkDto.getChunkFileMd5(),uploadFileChunkDto.getChunkNumber());if(!Objects.isNull(uploadFileChunk)){if(!uploadFileChunk.getChunkNumber().equals(uploadFileChunkDto.getChunkNumber())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊序號與原上傳塊號不匹配!");}return uploadFileChunk;}MultipartFile file = uploadFileChunkDto.getFile();String md5 = Md5Util.calculateMd5(file);if(!StringUtils.equals(md5,uploadFileChunkDto.getChunkFileMd5())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊md5不匹配!");}FileInfo fileInfo = fileService.uploadFileChunk(file, upload.getPathname(), uploadFileChunkDto.getFileMd5(), uploadFileChunkDto.getChunkFileMd5(), uploadFileChunkDto.getChunkNumber());if(Objects.isNull(fileInfo)){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"上傳失敗,請聯系管理員!");}// 上傳成功,保存文件塊信息uploadFileChunk = new UploadFileChunk();BeanUtils.copyProperties(uploadFileChunkDto,uploadFileChunk);BeanUtils.copyProperties(fileInfo,uploadFileChunk);uploadFileChunk.setUploadFileId(uploadFileChunkDto.getFileMd5());uploadFileChunk.setCreatedDate(DateUtils.getCurrentDate());return uploadFileChunkRepository.save(uploadFileChunk);}@SneakyThrowspublic UploadFile uploadFileMerge(UploadFileMergeDto uploadFileMergeDto) {log.info("合并文件塊------------------>dto={}",JacksonUtil.toJson(uploadFileMergeDto));Optional<Upload> uploadOptional = uploadRepository.findById(uploadFileMergeDto.getUploadId());if(!uploadOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"uploadId錯誤");}Upload upload = uploadOptional.get();if(!StringUtils.equals(upload.getCreatedBy(),UserUtil.getCurrentUserId())){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"非法請求!");}Optional<UploadFile> uploadFileOptional = uploadFileRepository.findById(uploadFileMergeDto.getFileMd5());if(!uploadFileOptional.isPresent()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件信息不存在!");}UploadFile uploadFile = uploadFileOptional.get();if(Constants.NORMAL.equals(uploadFile.getMergeStatus())){return uploadFile;}List<UploadFileChunk> uploadFileChunkList = uploadFileChunkRepository.findListByUploadIdAndUploadFileId(uploadFileMergeDto.getUploadId(), uploadFileMergeDto.getFileMd5());if(uploadFileChunkList.size() != uploadFile.getTotalChunk()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊數量與文件塊總數不符!");}List<String> chunkFileId = new ArrayList<>();// 驗證文件塊Map<Integer,UploadFileChunk> chunkMap = Maps.newHashMap();for (UploadFileChunk uploadFileChunk : uploadFileChunkList) {chunkMap.put(uploadFileChunk.getChunkNumber(),uploadFileChunk);chunkFileId.add(uploadFileChunk.getId());}List<File> chunkFileList = new ArrayList<>(uploadFile.getTotalChunk());File file;for (int i = 1; i <= uploadFile.getTotalChunk(); i++) {if(!chunkMap.containsKey(i)){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊序號錯誤!");}UploadFileChunk uploadFileChunk = chunkMap.get(i);file = new File(uploadFileChunk.getFilePath());if(!file.exists()){throw new ServiceException(HttpStatus.BAD_REQUEST.value(),"文件塊不存在!");}chunkFileList.add(file);}// 合并文件FileInfo fileInfo = fileService.mergeChunkFile(chunkFileList, uploadFile,upload.getPathname());uploadFile.setFileName(fileInfo.getFileName());uploadFile.setFilePath(fileInfo.getFilePath());uploadFile.setMergeStatus(Constants.NORMAL);uploadFileRepository.save(uploadFile);uploadFileChunkRepository.deleteAllById(chunkFileId);// 刪除文件塊String chunkPathname = upload.getPathname().concat(File.separator).concat(uploadFile.getId());FileUtils.deleteDirectory(new File(chunkPathname));return uploadFile;}}// 補充 上傳與合并
@SneakyThrows
public FileInfo uploadFileChunk(MultipartFile file, String pathname, String uploadFileId,String chunkFileMd5,Integer chunkNumber){try (InputStream inputStream = file.getInputStream()){FileInfo fileInfo = new FileInfo();fileInfo.setId(IdUtils.getUuid());fileInfo.setFileName(chunkNumber.toString().concat(SplitSymbolEnum.SPLIT_UNDERLINE.getSymbol()).concat(chunkFileMd5));fileInfo.setFileSize(file.getSize());// 定義文件路徑StringBuilder sb = new StringBuilder();sb.append(pathname).append(File.separator).append(uploadFileId).append(File.separator)// 編號_md5值.append(fileInfo.getFileName());fileInfo.setFilePath(sb.toString());FileUtils.copyToFile(inputStream,new File(fileInfo.getFilePath()));return fileInfo;}catch (Exception e){log.error("FileService.uploadFileChunk--------出現未知異常--------->pathname={},uploadFileId={},chunkFileMd5={},chunkNumber={}",pathname,uploadFileId,chunkFileMd5,chunkNumber,e);}return null;
}/**** @param chunkFileList 排好序的文件* @param uploadFile* @param pathname*/
public FileInfo mergeChunkFile(List<File> chunkFileList,UploadFile uploadFile,String pathname) {StringBuilder sb = new StringBuilder();sb.append(pathname).append(File.separator).append(uploadFile.getFileName()).append(File.separator);String finalFilePath = pathname.concat(File.separator).concat(uploadFile.getFileName());try (RandomAccessFile destFile = new RandomAccessFile(finalFilePath, "rw")) {// 按分片索引排序// 逐個寫入for (File chunk : chunkFileList) {try (FileInputStream fis = new FileInputStream(chunk)) {byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {destFile.write(buffer, 0, len);}}catch (Exception e){log.error("FileService.mergeChunkFile--------出現未知異常--------->chunkFile={}",chunk.getAbsolutePath());}}} catch (Exception e) {log.error("FileService.mergeChunkFile--------出現未知異常--------->chunkFileList={},fileName={},pathname={}",chunkFileList,uploadFile.getFileName(),pathname,e);throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR.value(),"合并文件失敗!");}File file = new File(finalFilePath);if(!file.exists()){log.error("FileService.mergeChunkFile--------出現未知異常--------->chunkFileList={},fileName={},pathname={}",chunkFileList,uploadFile.getFileName(),pathname);throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR.value(),"合并文件失敗!");}FileInfo fileInfo = new FileInfo();fileInfo.setId(IdUtils.getUuid());fileInfo.setFileName(uploadFile.getFileName());fileInfo.setFileSize(file.length());fileInfo.setFilePath(finalFilePath);return fileInfo;
}

持久層

主表,初始化單次上傳

@Data
@Entity
@Table(name = "upload")
public class Upload implements Serializable {@Id@ApiModelProperty(example = "主鍵")@Column(length = 32)private String id;@ApiModelProperty(value = "總文件大小")private Long totalFileSize;@ApiModelProperty(value = "文件數量")private Integer fileCount;@ApiModelProperty(value = "文件類型")@Column(length = 32)private String fileType;@ApiModelProperty(value = "文件類型")@Column(length = 300)private String pathname;@ApiModelProperty(value = "創建人",example = "創建人")@Column(length = 32)private String createdBy;@ApiModelProperty(value = "創建時間",example = "創建時間")@JsonFormat(shape =JsonFormat.Shape.STRING, pattern= "yyyy-MM-dd HH:mm:ss" ,timezone ="GMT+8")@Temporal(TemporalType.TIMESTAMP)private Date createdDate;@ApiModelProperty(value = "更新時間",example = "更新時間")@JsonFormat(shape =JsonFormat.Shape.STRING, pattern= "yyyy-MM-dd HH:mm:ss" ,timezone ="GMT+8")@Temporal(TemporalType.TIMESTAMP)private Date updateDate;}

文件表,單次上傳所有的文件記錄

@Data
@Entity
@Table(name = "upload_file")
public class UploadFile implements Serializable {@Id@ApiModelProperty(example = "主鍵")@Column(length = 32)private String id;@ApiModelProperty(example = "upload主鍵")@Column(length = 32)private String uploadId;@ApiModelProperty(value = "文件名稱")@Column(length = 300)private String fileName;@ApiModelProperty(value = "文件大小")private Long fileSize;@ApiModelProperty(value = "總文件塊")private Integer totalChunk;@ApiModelProperty(value = "分塊大小")private Long chunkSize;@ApiModelProperty(value = "文件路徑")@Column(length = 300)private String filePath;@ApiModelProperty(value = "合并狀態")private Integer mergeStatus;@ApiModelProperty(value = "創建時間",example = "創建時間")@JsonFormat(shape =JsonFormat.Shape.STRING, pattern= "yyyy-MM-dd HH:mm:ss" ,timezone ="GMT+8")@Temporal(TemporalType.TIMESTAMP)private Date createdDate;
}

文件分塊表

@Data
@Entity
@Table(name = "upload_file_chunk")
public class UploadFileChunk implements Serializable {@Id@ApiModelProperty(example = "主鍵")@Column(length = 32)private String id;@ApiModelProperty(example = "upload主鍵")@Column(length = 32)private String uploadId;@ApiModelProperty(example = "文件主鍵")@Column(length = 32)private String uploadFileId;@ApiModelProperty(example = "md5值")@Column(length = 32)private String chunkFileMd5;@ApiModelProperty(example = "塊編號")private Integer chunkNumber;@ApiModelProperty(value = "文件大小")private Long fileSize;@ApiModelProperty(value = "文件名稱")@Column(length = 300)private String fileName;@ApiModelProperty(value = "文件路徑")@Column(length = 300)private String filePath;@ApiModelProperty(value = "創建時間",example = "創建時間")@JsonFormat(shape =JsonFormat.Shape.STRING, pattern= "yyyy-MM-dd HH:mm:ss" ,timezone ="GMT+8")@Temporal(TemporalType.TIMESTAMP)private Date createdDate;}

結語:保持誰使用誰刪除
祝~~ 愉快

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

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

相關文章

Nodejs》》MySql

Node.js 操作MySQL數據庫 文檔 # 項目要先安裝mysql包npm i mysqlxx // 安裝指定版本npm i mysql // 默認安裝最新版本 # 連接 mysq// 使用連接池連接const mysql require(mysql)# 建立連接const db mysql.createPool({host:, // 數據庫的IP地址user:ro…

金倉數據庫常見問題(持續更新)

目錄 1.查看大小是否敏感寫參數&#xff0c;提示&#xff1a;未認可的配置參數 "case_sensitive" 2.sys_backup.sh init時提示can not connect the primary node 3.設置邏輯備份運行腳本時提示錯誤are not allowed to use this program (crontab) 4.修改表字段類…

Docker Buildx最佳實踐:多架構鏡像構建指南

文章目錄為什么需要 Docker Buildx安裝與啟用 Docker Buildx創建多架構構建器實例構建多架構鏡像優化構建性能調試多架構構建實戰案例&#xff1a;構建 Go 應用多架構鏡像總結Docker Buildx 是 Docker 官方推出的擴展工具&#xff0c;用于支持多平臺鏡像構建&#xff0c;簡化跨…

你用的是什么鍵盤?

在電競行業飛速發展的當下&#xff0c;游戲鍵盤作為玩家操作的核心載體&#xff0c;其性能表現直接影響著游戲體驗與競技結果。而賽卓電子推出的磁軸鍵盤專用芯片 SC4823&#xff0c;憑借一系列突破性的技術特性&#xff0c;正成為游戲鍵盤領域的性能革新者。?對于游戲玩家而言…

Activiti 中各種 startProcessInstance 接口之間的區別

前言在用 RuntimeService 接口啟動流程實例時&#xff0c;總是分不清楚不同 startProcessInstanceXXX 接口之間的區別&#xff0c;這篇文章基于 Activiti 7.0.0.GA 版本&#xff0c;對這一類接口進行一個梳理和歸類。詳解接口列表RuntimeService 接口中以 startProcessInstance…

新手BUG:函數中 static 變量的賦值語句只會執行一次

在 C 函數中使用 static 變量時&#xff0c;很多新手會陷入一個認知誤區&#xff1a;認為變量的初始化語句會在每次函數調用時執行。比如在bool funcA() { // Q&#xff1a;多次調用funcA&#xff0c;funcB會被執行幾次&#xff1f;// A&#xff1a;1次static bool value func…

Python 基礎詳解:數據類型(Data Types)—— 程序的“數據基石”

一、引言&#xff1a;為什么數據類型如此重要&#xff1f;在 Python 編程中&#xff0c;數據類型決定了&#xff1a;數據的存儲方式可以對數據執行的操作數據的取值范圍不同類型之間的運算規則理解數據類型是編寫正確、高效程序的基礎。Python 是動態類型語言&#xff0c;雖然你…

WindowsLinux系統 安裝 CUDA 和 cuDNN

Windows安裝前的準備工作 檢查硬件兼容性&#xff1a;確認電腦顯卡為 NVIDIA GPU。通過快捷鍵 Win R 喚出“運行”&#xff0c;輸入“control /name Microsoft.DeviceManager”喚出“設備管理器”&#xff0c;點擊“顯示適配器”查看是否有 NVIDIA 字樣。 驗證 CUDA 支持性&a…

工業數采引擎-通信鏈路SOCKET

通信庫&#xff1a;DotNetty 封裝實現&#xff1a;TcpServer、TcpClient、Udp TCP協議特性&#xff1a;面向連接協議&#xff1b;每個新連接都會創建獨立的ChannelHandler實例&#xff1b;TcpHandler構造函數在每次客戶端連接時觸發 UDP協議特性&#xff1a;無連接協議&#…

PHP小白零基礎入門(附視頻教程)

概述 PHP是一種通用開源腳本語言&#xff0c;常用于服務器端Web開發&#xff0c;具有語法簡單、上手快等特點。視頻教程&#xff1a;https://pan.quark.cn/s/8f214c23301b 搭建開發環境&#xff1a; 選擇集成工具&#xff1a;可選擇XAMPP&#xff08;支持Windows/Mac/Linux…

驗證碼等待時間技術在酒店自助入住、美容自助與社區場景中的應用必要性研究—仙盟創夢IDE

代碼 代碼 完整<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>驗證碼倒計時</title><s…

Flask從入門到實戰:基礎、進階、項目架構與接口測試

本文將帶你從零開始掌握Flask框架&#xff0c;涵蓋基礎使用、進階技巧、項目架構設計&#xff0c;并提供完整的接口測試客戶端代碼。 目錄一、Flask基礎入門1.1 Flask簡介與安裝1.2 第一個Flask應用1.3 路由與請求處理1.4 請求與響應處理二、Flask進階使用2.1 模板引擎Jinja22.…

華為云產品圖解

框架圖核心說明: 1. 分層邏輯清晰 基礎設施層(IaaS):提供最基礎的計算(ECS/BMS)、存儲(OBS/EVS)、網絡(VPC/CDN)資源,是所有上層服務的 “物理底座”。 平臺服務層(PaaS):基于 IaaS 構建,提供容器編排(CCE)、數據庫(GaussDB)、大數據與 AI(ModelArts)、中…

Git 中如何回退到以前的提交記錄?

回答重點要在 Git 中回退到以前的提交記錄&#xff0c;你可以使用 git reset 命令。這個命令有三個常用選項來控制你想要回退的程度&#xff1a;1&#xff09; git reset --soft <commit> &#xff1a;僅修改 HEAD 指針&#xff0c;不修改索引和工作區內容。2&#xff09…

JavaWeb03——基礎標簽及樣式(表單)(黑馬視頻筆記)

1.表單標簽 及 表單屬性表單標簽是 &#xff1a;<form> 表單屬性有&#xff1a;action 和 method&#xff1b;action屬性&#xff1a;規定向何處發送表單數據。method屬性&#xff1a;規定用什么方法發送數據。&#xff08;get和post&#xff09;get:在發送的url后面拼接…

STM32的SPI通信(軟件讀寫W25Q64)

在了解完I2C通信后&#xff0c;不免會接觸到到SPI通信。而一開始&#xff0c;可能會覺得兩者好似沒什么區別。為什么要學SPI呢&#xff0c;I2C和SPI有什么區別呢。為此我詳細展開說說。1.什么是 SPI&#xff1f;SPI&#xff0c;全稱 Serial Peripheral Interface&#xff0c;中…

子詞分詞器(Byte Pair Encoding + WordPiece)

參考文章&#xff1a;子詞分詞器BPE和WordPiece理解_wordpeice-CSDN博客 子詞分詞器BPE和WordPiece理解_wordpeice-CSDN博客 WordPiece 和 BPE 的區別-CSDN博客 點互信息&#xff08;PMI&#xff09;和正點互信息&#xff08;PPMI&#xff09;-CSDN博客 https://zhuanlan.z…

阿里招AI產品運營

AI產品運營&#xff08;崗位信息已經過jobleap.cn授權&#xff0c;可在csdn發布&#xff09;靈犀互娛 廣州收錄時間&#xff1a; 2025年08月05日職位描述負責AI技術在游戲行業的應用與落地&#xff0c;專注于海外市場的運營中臺建設&#xff1b; 將結合AI技術與游戲行業特點&a…

Git 分支遷移完整指南(結合分支圖分析)

基于分支圖的當前狀態分析 分支圖關鍵信息解讀?分支結構?&#xff1a; #mermaid-svg-gc9SPnwlbrM2FzHf {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-gc9SPnwlbrM2FzHf .error-icon{fill:#552222;}#mermaid-svg-…

小程序省市級聯組件使用

背景。uni-data-picker組件用起來不方便。調整后級聯效果欠佳&#xff0c;會關閉彈窗需要重新選擇。解決方案。讓cursor使用uniapp 原生組件生成懶加載省市級聯 <template><view class"picker-cascader"><view class"cascader-label">&l…