springboot項(xiàng)目實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能
java代碼
package com.ruoyi.web.upload.controller; import com.ruoyi.web.upload.dto.FileChunkDTO; import com.ruoyi.web.upload.dto.FileChunkResultDTO; import com.ruoyi.web.upload.result.Result; import com.ruoyi.web.upload.service.IUploadService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** * @ProjectName UploaderController * @author Administrator * @version 1.0.0 * @Description 附件分片上傳 * @createTime 2022/4/13 0013 15:58 */ @RestController @RequestMapping("upload") public class UploaderController { @Resource private IUploadService uploadService; /** * 檢查分片是否存在 * * @return */ @GetMapping("chunk") public Result checkChunkExist(FileChunkDTO chunkDTO) { FileChunkResultDTO fileChunkCheckDTO; try { fileChunkCheckDTO = uploadService.checkChunkExist(chunkDTO); return Result.ok(fileChunkCheckDTO); } catch (Exception e) { return Result.fail(e.getMessage()); } } /** * 上傳文件分片 * * @param chunkDTO * @return */ @PostMapping("chunk") public Result uploadChunk(FileChunkDTO chunkDTO) { try { uploadService.uploadChunk(chunkDTO); return Result.ok(chunkDTO.getIdentifier()); } catch (Exception e) { return Result.fail(e.getMessage()); } } /** * 請(qǐng)求合并文件分片 * * @param chunkDTO * @return */ @PostMapping("merge") public Result mergeChunks(@RequestBody FileChunkDTO chunkDTO) { try { boolean success = uploadService.mergeChunk(chunkDTO.getIdentifier(), chunkDTO.getFilename(), chunkDTO.getTotalChunks()); return Result.ok(success); } catch (Exception e) { return Result.fail(e.getMessage()); } } }
package com.ruoyi.web.upload.dto; import org.springframework.web.multipart.MultipartFile; /** * @ProjectName FileChunkDTO * @author Administrator * @version 1.0.0 * @Description 附件分片上傳 * @createTime 2022/4/13 0013 15:59 */ public class FileChunkDTO { /** * 文件 md5 */ private String identifier; /** * 分塊文件 */ MultipartFile file; /** * 當(dāng)前分塊序號(hào) */ private Integer chunkNumber; /** * 分塊大小 */ private Long chunkSize; /** * 當(dāng)前分塊大小 */ private Long currentChunkSize; /** * 文件總大小 */ private Long totalSize; /** * 分塊總數(shù) */ private Integer totalChunks; /** * 文件名 */ private String filename; public String getIdentifier() { return identifier; } public void setIdentifier(String identifier) { this.identifier = identifier; } public MultipartFile getFile() { return file; } public void setFile(MultipartFile file) { this.file = file; } public Integer getChunkNumber() { return chunkNumber; } public void setChunkNumber(Integer chunkNumber) { this.chunkNumber = chunkNumber; } public Long getChunkSize() { return chunkSize; } public void setChunkSize(Long chunkSize) { this.chunkSize = chunkSize; } public Long getCurrentChunkSize() { return currentChunkSize; } public void setCurrentChunkSize(Long currentChunkSize) { this.currentChunkSize = currentChunkSize; } public Long getTotalSize() { return totalSize; } public void setTotalSize(Long totalSize) { this.totalSize = totalSize; } public Integer getTotalChunks() { return totalChunks; } public void setTotalChunks(Integer totalChunks) { this.totalChunks = totalChunks; } public String getFilename() { return filename; } public void setFilename(String filename) { this.filename = filename; } @Override public String toString() { return "FileChunkDTO{" + "identifier='" + identifier + '\'' + ", file=" + file + ", chunkNumber=" + chunkNumber + ", chunkSize=" + chunkSize + ", currentChunkSize=" + currentChunkSize + ", totalSize=" + totalSize + ", totalChunks=" + totalChunks + ", filename='" + filename + '\'' + '}'; } }
package com.ruoyi.web.upload.dto; import java.util.Set; /** * @ProjectName FileChunkResultDTO * @author Administrator * @version 1.0.0 * @Description 附件分片上傳 * @createTime 2022/4/13 0013 15:59 */ public class FileChunkResultDTO { /** * 是否跳過上傳 */ private Boolean skipUpload; /** * 已上傳分片的集合 */ private Set<Integer> uploaded; public Boolean getSkipUpload() { return skipUpload; } public void setSkipUpload(Boolean skipUpload) { this.skipUpload = skipUpload; } public Set<Integer> getUploaded() { return uploaded; } public void setUploaded(Set<Integer> uploaded) { this.uploaded = uploaded; } public FileChunkResultDTO(Boolean skipUpload, Set<Integer> uploaded) { this.skipUpload = skipUpload; this.uploaded = uploaded; } public FileChunkResultDTO(Boolean skipUpload) { this.skipUpload = skipUpload; } }
package com.ruoyi.web.upload.dto; import lombok.Getter; /** * @Author * @Date Created in 2023/2/23 17:25 * @DESCRIPTION: 統(tǒng)一返回結(jié)果狀態(tài)信息類 * @Version V1.0 */ @Getter @SuppressWarnings("all") public enum ResultCodeEnum { SUCCESS(200,"成功"), FAIL(201, "失敗"), PARAM_ERROR( 202, "參數(shù)不正確"), SERVICE_ERROR(203, "服務(wù)異常"), DATA_ERROR(204, "數(shù)據(jù)異常"), DATA_UPDATE_ERROR(205, "數(shù)據(jù)版本異常"), LOGIN_AUTH(208, "未登陸"), PERMISSION(209, "沒有權(quán)限"), CODE_ERROR(210, "驗(yàn)證碼錯(cuò)誤"), LOGIN_MOBLE_ERROR(211, "賬號(hào)不正確"), LOGIN_DISABLED_ERROR(212, "改用戶已被禁用"), REGISTER_MOBLE_ERROR(213, "手機(jī)號(hào)碼格式不正確"), REGISTER_MOBLE_ERROR_NULL(214, "手機(jī)號(hào)碼為空"), LOGIN_AURH(214, "需要登錄"), LOGIN_ACL(215, "沒有權(quán)限"), URL_ENCODE_ERROR( 216, "URL編碼失敗"), ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回調(diào)請(qǐng)求"), FETCH_ACCESSTOKEN_FAILD( 218, "獲取accessToken失敗"), FETCH_USERINFO_ERROR( 219, "獲取用戶信息失敗"); private Integer code; private String message; private ResultCodeEnum(Integer code, String message) { this.code = code; this.message = message; } }
package com.ruoyi.web.upload.service; import com.ruoyi.web.upload.dto.FileChunkDTO; import com.ruoyi.web.upload.dto.FileChunkResultDTO; import java.io.IOException; /** * @ProjectName IUploadService * @author Administrator * @version 1.0.0 * @Description 附件分片上傳 * @createTime 2022/4/13 0013 15:59 */ public interface IUploadService { /** * 檢查文件是否存在,如果存在則跳過該文件的上傳,如果不存在,返回需要上傳的分片集合 * @param chunkDTO * @return */ FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO); /** * 上傳文件分片 * @param chunkDTO */ void uploadChunk(FileChunkDTO chunkDTO) throws IOException; /** * 合并文件分片 * @param identifier * @param fileName * @param totalChunks * @return * @throws IOException */ boolean mergeChunk(String identifier,String fileName,Integer totalChunks)throws IOException; }
package com.ruoyi.web.upload.service.impl; import com.ruoyi.web.upload.dto.FileChunkDTO; import com.ruoyi.web.upload.dto.FileChunkResultDTO; import com.ruoyi.web.upload.service.IUploadService; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.io.*; import java.util.*; /** * @ProjectName UploadServiceImpl * @author Administrator * @version 1.0.0 * @Description 附件分片上傳 * @createTime 2022/4/13 0013 15:59 */ @Service @SuppressWarnings("all") public class UploadServiceImpl implements IUploadService { private Logger logger = LoggerFactory.getLogger(UploadServiceImpl.class); @Autowired private RedisTemplate redisTemplate; @Value("${ruoyi.profile}") private String uploadFolder; /** * 檢查文件是否存在,如果存在則跳過該文件的上傳,如果不存在,返回需要上傳的分片集合 * 檢查分片是否存在 ○ 檢查目錄下的文件是否存在。 ○ 檢查redis存儲(chǔ)的分片是否存在。 ○ 判斷分片數(shù)量和總分片數(shù)量是否一致。 如果文件存在并且分片上傳完畢,標(biāo)識(shí)已經(jīng)完成附件的上傳,可以進(jìn)行秒傳操作。 如果文件不存在或者分片為上傳完畢,則返回false并返回已經(jīng)上傳的分片信息。 * @param chunkDTO * @return */ @Override public FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO) { //1.檢查文件是否已上傳過 //1.1)檢查在磁盤中是否存在 String fileFolderPath = getFileFolderPath(chunkDTO.getIdentifier()); logger.info("fileFolderPath-->{}", fileFolderPath); String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename()); File file = new File(filePath); boolean exists = file.exists(); //1.2)檢查Redis中是否存在,并且所有分片已經(jīng)上傳完成。 Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded"); if (uploaded != null && uploaded.size() == chunkDTO.getTotalChunks() && exists) { return new FileChunkResultDTO(true); } File fileFolder = new File(fileFolderPath); if (!fileFolder.exists()) { boolean mkdirs = fileFolder.mkdirs(); logger.info("準(zhǔn)備工作,創(chuàng)建文件夾,fileFolderPath:{},mkdirs:{}", fileFolderPath, mkdirs); } // 斷點(diǎn)續(xù)傳,返回已上傳的分片 return new FileChunkResultDTO(false, uploaded); } /** * 上傳分片 * 上傳附件分片 ○ 判斷目錄是否存在,如果不存在則創(chuàng)建目錄。 ○ 進(jìn)行切片的拷貝,將切片拷貝到指定的目錄。 ○ 將該分片寫入redis * @param chunkDTO */ @Override public void uploadChunk(FileChunkDTO chunkDTO) { //分塊的目錄 String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier()); logger.info("分塊的目錄 -> {}", chunkFileFolderPath); File chunkFileFolder = new File(chunkFileFolderPath); if (!chunkFileFolder.exists()) { boolean mkdirs = chunkFileFolder.mkdirs(); logger.info("創(chuàng)建分片文件夾:{}", mkdirs); } //寫入分片 try ( InputStream inputStream = chunkDTO.getFile().getInputStream(); FileOutputStream outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunkDTO.getChunkNumber())) ) { IOUtils.copy(inputStream, outputStream); logger.info("文件標(biāo)識(shí):{},chunkNumber:{}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber()); //將該分片寫入redis long size = saveToRedis(chunkDTO); } catch (Exception e) { e.printStackTrace(); } } @Override public boolean mergeChunk(String identifier, String fileName, Integer totalChunks) throws IOException { return mergeChunks(identifier, fileName, totalChunks); } /** * 合并分片 * * @param identifier * @param filename */ private boolean mergeChunks(String identifier, String filename, Integer totalChunks) { String chunkFileFolderPath = getChunkFileFolderPath(identifier); String filePath = getFilePath(identifier, filename); // 檢查分片是否都存在 if (checkChunks(chunkFileFolderPath, totalChunks)) { File chunkFileFolder = new File(chunkFileFolderPath); File mergeFile = new File(filePath); File[] chunks = chunkFileFolder.listFiles(); // 切片排序1、2/3、--- List fileList = Arrays.asList(chunks); Collections.sort(fileList, (Comparator<File>) (o1, o2) -> { return Integer.parseInt(o1.getName()) - (Integer.parseInt(o2.getName())); }); try { RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw"); byte[] bytes = new byte[1024]; for (File chunk : chunks) { RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r"); int len; while ((len = randomAccessFileReader.read(bytes)) != -1) { randomAccessFileWriter.write(bytes, 0, len); } randomAccessFileReader.close(); } randomAccessFileWriter.close(); } catch (Exception e) { return false; } return true; } return false; } /** * 檢查分片是否都存在 * @param chunkFileFolderPath * @param totalChunks * @return */ private boolean checkChunks(String chunkFileFolderPath, Integer totalChunks) { try { for (int i = 1; i <= totalChunks + 1; i++) { File file = new File(chunkFileFolderPath + File.separator + i); if (file.exists()) { continue; } else { return false; } } } catch (Exception e) { return false; } return true; } /** * 分片寫入Redis * 判斷切片是否已存在,如果未存在,則創(chuàng)建基礎(chǔ)信息,并保存。 * @param chunkDTO */ private synchronized long saveToRedis(FileChunkDTO chunkDTO) { Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded"); if (uploaded == null) { uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber())); HashMap<String, Object> objectObjectHashMap = new HashMap<>(); objectObjectHashMap.put("uploaded", uploaded); objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks()); objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize()); // objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFilename())); objectObjectHashMap.put("path", chunkDTO.getFilename()); redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap); } else { uploaded.add(chunkDTO.getChunkNumber()); redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded); } return uploaded.size(); } /** * 得到文件的絕對(duì)路徑 * * @param identifier * @param filename * @return */ private String getFilePath(String identifier, String filename) { String ext = filename.substring(filename.lastIndexOf(".")); // return getFileFolderPath(identifier) + identifier + ext; return uploadFolder + filename; } /** * 得到文件的相對(duì)路徑 * * @param identifier * @param filename * @return */ private String getFileRelativelyPath(String identifier, String filename) { String ext = filename.substring(filename.lastIndexOf(".")); return "/" + identifier.substring(0, 1) + "/" + identifier.substring(1, 2) + "/" + identifier + "/" + identifier + ext; } /** * 得到分塊文件所屬的目錄 * * @param identifier * @return */ private String getChunkFileFolderPath(String identifier) { return getFileFolderPath(identifier) + "chunks" + File.separator; } /** * 得到文件所屬的目錄 * * @param identifier * @return */ private String getFileFolderPath(String identifier) { return uploadFolder + identifier.substring(0, 1) + File.separator + identifier.substring(1, 2) + File.separator + identifier + File.separator; // return uploadFolder; } }
package com.ruoyi.web.upload.result; import com.ruoyi.web.upload.dto.ResultCodeEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @Author * @Date Created in 2023/2/23 17:25 * @DESCRIPTION: 全局統(tǒng)一返回結(jié)果 * @Version V1.0 */ @Data @ApiModel(value = "全局統(tǒng)一返回結(jié)果") @SuppressWarnings("all") public class Result<T> { @ApiModelProperty(value = "返回碼") private Integer code; @ApiModelProperty(value = "返回消息") private String message; @ApiModelProperty(value = "返回?cái)?shù)據(jù)") private T data; private Long total; public Result(){} protected static <T> Result<T> build(T data) { Result<T> result = new Result<T>(); if (data != null) result.setData(data); return result; } public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) { Result<T> result = build(body); result.setCode(resultCodeEnum.getCode()); result.setMessage(resultCodeEnum.getMessage()); return result; } public static <T> Result<T> build(Integer code, String message) { Result<T> result = build(null); result.setCode(code); result.setMessage(message); return result; } public static<T> Result<T> ok(){ return Result.ok(null); } /** * 操作成功 * @param data * @param <T> * @return */ public static<T> Result<T> ok(T data){ Result<T> result = build(data); return build(data, ResultCodeEnum.SUCCESS); } public static<T> Result<T> fail(){ return Result.fail(null); } /** * 操作失敗 * @param data * @param <T> * @return */ public static<T> Result<T> fail(T data){ Result<T> result = build(data); return build(data, ResultCodeEnum.FAIL); } public Result<T> message(String msg){ this.setMessage(msg); return this; } public Result<T> code(Integer code){ this.setCode(code); return this; } public boolean isOk() { if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) { return true; } return false; } }
- 前端代碼
- mainjs導(dǎo)入uploader
import uploader from 'vue-simple-uploader' Vue.use(uploader)
安裝uploader和spark-md5的依賴
npm install --save vue-simple-uploader npm install --save spark-md5
創(chuàng)建uploader組件
<template> <div> <uploader :autoStart="false" :options="options" :file-status-text="statusText" class="uploader-example" @file-complete="fileComplete" @complete="complete" @file-success="fileSuccess" @files-added="filesAdded" > <uploader-unsupport></uploader-unsupport> <uploader-drop> <p>將文件拖放到此處以上傳</p> <uploader-btn>選擇文件</uploader-btn> <uploader-btn :attrs="attrs">選擇圖片</uploader-btn> <uploader-btn :directory="true">選擇文件夾</uploader-btn> </uploader-drop> <!-- <uploader-list></uploader-list> --> <uploader-files> </uploader-files> </uploader> <br /> <el-button @click="allStart()" :disabled="disabled">全部開始</el-button> <el-button @click="allStop()" style="margin-left: 4px">全部暫停</el-button> <el-button @click="allRemove()" style="margin-left: 4px">全部移除</el-button> </div> </template> <script> import axios from "axios"; import SparkMD5 from "spark-md5"; import {upload} from "@/api/user"; // import storage from "store"; // import { ACCESS_TOKEN } from '@/store/mutation-types' export default { name: "Home", data() { return { skip: false, options: { target: "http://localhost:9999/upload/chunk", // 開啟服務(wù)端分片校驗(yàn)功能 testChunks: true, parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) { return parsedTimeRemaining .replace(/\syears?/, "年") .replace(/\days?/, "天") .replace(/\shours?/, "小時(shí)") .replace(/\sminutes?/, "分鐘") .replace(/\sseconds?/, "秒"); }, // 服務(wù)器分片校驗(yàn)函數(shù) checkChunkUploadedByResponse: (chunk, message) => { const result = JSON.parse(message); if (result.data.skipUpload) { this.skip = true; return true; } return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0; }, // headers: { // // 在header中添加的驗(yàn)證,請(qǐng)根據(jù)實(shí)際業(yè)務(wù)來 // "Access-Token": storage.get(ACCESS_TOKEN), // }, }, attrs: { accept: "image/*", }, statusText: { success: "上傳成功", error: "上傳出錯(cuò)了", uploading: "上傳中...", paused: "暫停中...", waiting: "等待中...", cmd5: "計(jì)算文件MD5中...", }, fileList: [], disabled: true, }; }, watch: { fileList(o, n) { this.disabled = false; }, }, methods: { // fileSuccess(rootFile, file, response, chunk) { // // console.log(rootFile); // // console.log(file); // // console.log(message); // // console.log(chunk); // const result = JSON.parse(response); // console.log(result.success, this.skip); // // if (result.success && !this.skip) { // axios // .post( // "http://127.0.0.1:9999/upload/merge", // { // identifier: file.uniqueIdentifier, // filename: file.name, // totalChunks: chunk.offset, // }, // // { // // headers: { "Access-Token": storage.get(ACCESS_TOKEN) } // // } // ) // .then((res) => { // if (res.data.success) { // console.log("上傳成功"); // } else { // console.log(res); // } // }) // .catch(function (error) { // console.log(error); // }); // } else { // console.log("上傳成功,不需要合并"); // } // if (this.skip) { // this.skip = false; // } // }, fileSuccess(rootFile, file, response, chunk) { // console.log(rootFile); // console.log(file); // console.log(message); // console.log(chunk); const result = JSON.parse(response); console.log(result.success, this.skip); const user = { identifier: file.uniqueIdentifier, filename: file.name, totalChunks: chunk.offset, } if (result.success && !this.skip) { upload(user).then((res) => { if (res.code == 200) { console.log("上傳成功"); } else { console.log(res); } }) .catch(function (error) { console.log(error); }); } else { console.log("上傳成功,不需要合并"); } if (this.skip) { this.skip = false; } }, fileComplete(rootFile) { // 一個(gè)根文件(文件夾)成功上傳完成。 // console.log("fileComplete", rootFile); // console.log("一個(gè)根文件(文件夾)成功上傳完成。"); }, complete() { // 上傳完畢。 // console.log("complete"); }, filesAdded(file, fileList, event) { // console.log(file); file.forEach((e) => { this.fileList.push(e); this.computeMD5(e); }); }, computeMD5(file) { let fileReader = new FileReader(); let time = new Date().getTime(); let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; let currentChunk = 0; const chunkSize = 1024 * 1024; let chunks = Math.ceil(file.size / chunkSize); let spark = new SparkMD5.ArrayBuffer(); // 文件狀態(tài)設(shè)為"計(jì)算MD5" file.cmd5 = true; //文件狀態(tài)為“計(jì)算md5...” file.pause(); loadNext(); fileReader.onload = (e) => { spark.append(e.target.result); if (currentChunk < chunks) { currentChunk++; loadNext(); // 實(shí)時(shí)展示MD5的計(jì)算進(jìn)度 console.log( `第${currentChunk}分片解析完成, 開始第${ currentChunk + 1 } / ${chunks}分片解析` ); } else { let md5 = spark.end(); console.log( `MD5計(jì)算完畢:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${ file.size } 用時(shí):${new Date().getTime() - time} ms` ); spark.destroy(); //釋放緩存 file.uniqueIdentifier = md5; //將文件md5賦值給文件唯一標(biāo)識(shí) file.cmd5 = false; //取消計(jì)算md5狀態(tài) file.resume(); //開始上傳 } }; fileReader.onerror = function () { this.error(`文件${file.name}讀取出錯(cuò),請(qǐng)檢查該文件`); file.cancel(); }; function loadNext() { let start = currentChunk * chunkSize; let end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end)); } }, allStart() { console.log(this.fileList); this.fileList.map((e) => { if (e.paused) { e.resume(); } }); }, allStop() { console.log(this.fileList); this.fileList.map((e) => { if (!e.paused) { e.pause(); } }); }, allRemove() { this.fileList.map((e) => { e.cancel(); }); this.fileList = []; }, }, }; </script> <style> .uploader-example { width: 100%; padding: 15px; margin: 0px auto 0; font-size: 12px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); } .uploader-example .uploader-btn { margin-right: 4px; } .uploader-example .uploader-list { max-height: 440px; overflow: auto; overflow-x: hidden; overflow-y: auto; } </style>
到此這篇關(guān)于springboot項(xiàng)目實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能的文章就介紹到這了,更多相關(guān)springboot斷點(diǎn)續(xù)傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java應(yīng)用層協(xié)議WebSocket實(shí)現(xiàn)消息推送
后端向前端推送消息就需要長(zhǎng)連接,首先想到的就是websocket,下面這篇文章主要給大家介紹了關(guān)于java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的詳細(xì)流程,需要的朋友可以參考下2023-02-02SpringBoot中配置雙數(shù)據(jù)源的實(shí)現(xiàn)示例
在許多應(yīng)用程序中,可能會(huì)遇到需要連接多個(gè)數(shù)據(jù)庫(kù)的情況,本文主要介紹了SpringBoot中配置雙數(shù)據(jù)源的實(shí)現(xiàn)示例,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08mybatis-plus報(bào)錯(cuò)Not Found TableInfoCache異常問題
在集成百度uid-generator過程中,MyBatis-Plus報(bào)錯(cuò)NotFoundTableInfoCache異常,解決方法:檢查實(shí)體類是否繼承了官方model,確保實(shí)體類對(duì)應(yīng)的mapper已正確注入,在使用@Component注解時(shí),應(yīng)保證相關(guān)依賴已注入2024-09-09SpringBoot中過濾器Filter+JWT令牌實(shí)現(xiàn)登錄驗(yàn)證
本文主要介紹了SpringBoot中過濾器Filter+JWT令牌實(shí)現(xiàn)登錄驗(yàn)證,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04javaweb啟動(dòng)時(shí)啟動(dòng)socket服務(wù)端代碼實(shí)現(xiàn)
這篇文章主要介紹了javaweb啟動(dòng)時(shí)啟動(dòng)socket服務(wù)端代碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例
這篇文章主要介紹了Java求素?cái)?shù)和最大公約數(shù)的簡(jiǎn)單代碼示例,其中作者創(chuàng)建的Fraction類可以用來進(jìn)行各種分?jǐn)?shù)運(yùn)算,需要的朋友可以參考下2015-09-09