Java大文件上傳、分片上傳、多文件上傳、斷點續傳、上傳文件minio、分片上傳minio等解決方案

来源:https://www.cnblogs.com/angelasp/p/18264714
-Advertisement-
Play Games

根據不同場景使用不同方案進行實現尤為必要。通常開發過程中,文件較小,直接將文件轉化為位元組流上傳到伺服器,但是文件較大時,用普通的方法上傳,顯然效果不是很好,當文件上傳一半中斷再次上傳時,發現需要重新開始,這種體驗不是很爽,下麵介紹幾種好一點兒的上傳方式。 這裡講講如何在Spring bo... ...


  • 上傳說明

           文件上傳花樣百出,根據不同場景使用不同方案進行實現尤為必要。通常開發過程中,文件較小,直接將文件轉化為位元組流上傳到伺服器,但是文件較大時,用普通的方法上傳,顯然效果不是很好,當文件上傳一半中斷再次上傳時,發現需要重新開始,這種體驗不是很爽,下麵介紹幾種好一點兒的上傳方式。

這裡講講如何在Spring boot 編寫上傳代碼,如有問題可以在下留言,我併在文章末尾附上Java上傳源碼供大家下載。

    • 分片上傳

      分片上傳,就是將所要上傳的文件,按照一定的大小,將整個文件分
隔成多個數據塊(我們稱之為Part)來進行分別上傳,上傳完之後再
由服務端對所有上傳的文件進行彙總整合成原始的文件。

    • 斷點續傳

          斷點續傳是在下載/上傳時,將下載/上傳任務(一個文件或一個壓縮
包)人為的劃分為幾個部分,每一個部分採用一個線程進行上傳/下載,
如果碰到網路故障,可以從已經上傳/下載的部分開始繼續上傳/下載
未完成的部分,而沒有必要從頭開始上傳/下載。

  • Redis啟動安裝

Redis安裝包分為 Windows 版和 Linux 版:
Windows版下載地址:https://github.com/microsoftarchive/redis/releases
Linux版下載地址: https://download.redis.io/releases/
我當前使用的Windows版本:

 

 

  • minio下載啟動

windows版本可以參考我之前的文檔:window10安裝minio_minio windows安裝-CSDN博客

啟動會提示:

 

以上是密碼設置問題需要修改如下:

set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678

啟動成功後會輸出相應地址

  • 上傳後端Java代碼

   後端採用Spring boot項目結構,主要代碼如下:

  1   /**
  2      * 單文件上傳
  3      * 直接將傳入的文件通過io流形式直接寫入(伺服器)指定路徑下
  4      *
  5      * @param file 上傳的文件
  6      * @return
  7      */
  8     @Override
  9     public ResultEntity<Boolean> singleFileUpload(MultipartFile file) {
 10         //實際情況下,這些路徑都應該是伺服器上面存儲文件的路徑
 11         String filePath = System.getProperty("user.dir") + "\\file\\";
 12         File dir = new File(filePath);
 13         if (!dir.exists()) dir.mkdir();
 14  
 15         if (file == null) {
 16             return ResultEntity.error(false, "上傳文件為空!");
 17         }
 18         InputStream fileInputStream = null;
 19         FileOutputStream fileOutputStream = null;
 20         try {
 21             String filename = file.getOriginalFilename();
 22             fileOutputStream = new FileOutputStream(filePath + filename);
 23             fileInputStream = file.getInputStream();
 24  
 25             byte[] buf = new byte[1024 * 8];
 26             int length;
 27             while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入位元組流裡面的數據
 28                 fileOutputStream.write(buf, 0, length);//通過fos文件輸出位元組流寫出去
 29             }
 30             log.info("單文件上傳完成!文件路徑:{},文件名:{},文件大小:{}", filePath, filename, file.getSize());
 31             return ResultEntity.success(true, "單文件上傳完成!");
 32         } catch (IOException e) {
 33             return ResultEntity.error(true, "單文件上傳失敗!");
 34         } finally {
 35             try {
 36                 if (fileOutputStream != null) {
 37                     fileOutputStream.close();
 38                     fileOutputStream.flush();
 39                 }
 40                 if (fileInputStream != null) {
 41                     fileInputStream.close();
 42                 }
 43             } catch (Exception e) {
 44                 e.printStackTrace();
 45             }
 46         }
 47     }
 48  
 49     /**
 50      * 多文件上傳
 51      * 直接將傳入的多個文件通過io流形式直接寫入(伺服器)指定路徑下
 52      * 寫入指定路徑下是通過多線程進行文件寫入的,文件寫入線程執行功能就和上面單文件寫入是一樣的
 53      *
 54      * @param files 上傳的所有文件
 55      * @return
 56      */
 57     @Override
 58     public ResultEntity<Boolean> multipleFileUpload(MultipartFile[] files) {
 59         //實際情況下,這些路徑都應該是伺服器上面存儲文件的路徑
 60         String filePath = System.getProperty("user.dir") + "\\file\\";
 61         File dir = new File(filePath);
 62         if (!dir.exists()) dir.mkdir();
 63  
 64         if (files.length == 0) {
 65             return ResultEntity.error(false, "上傳文件為空!");
 66         }
 67         ArrayList<String> uploadFiles = new ArrayList<>();
 68         try {
 69  
 70             ArrayList<Future<String>> futures = new ArrayList<>();
 71             //使用多線程來完成對每個文件的寫入
 72             for (MultipartFile file : files) {
 73                 futures.add(partMergeTask.submit(new MultipleFileTaskExecutor(filePath, file)));
 74             }
 75  
 76             //這裡主要用於監聽各個文件寫入線程是否執行結束
 77             int count = 0;
 78             while (count != futures.size()) {
 79                 for (Future<String> future : futures) {
 80                     if (future.isDone()) {
 81                         uploadFiles.add(future.get());
 82                         count++;
 83                     }
 84                 }
 85                 Thread.sleep(1);
 86             }
 87             log.info("多文件上傳完成!文件路徑:{},文件信息:{}", filePath, uploadFiles);
 88             return ResultEntity.success(true, "多文件上傳完成!");
 89         } catch (Exception e) {
 90             log.error("多文件分片上傳失敗!", e);
 91             return ResultEntity.error(true, "多文件上傳失敗!");
 92         }
 93  
 94     }
 95  
 96     /**
 97      * 單文件分片上傳
 98      * 直接將傳入的文件分片通過io流形式寫入(伺服器)指定臨時路徑下
 99      * 然後判斷是否分片都上傳完成,如果所有分片都上傳完成的話,就把臨時路徑下的分片文件通過流形式讀入合併並從新寫入到(伺服器)指定文件路徑下
100      * 最後刪除臨時文件和臨時文件夾,臨時文件夾是通過文件的uuid進行命名的
101      *
102      * @param filePart  分片文件
103      * @param partIndex 當前分片值
104      * @param partNum   所有分片數
105      * @param fileName  當前文件名稱
106      * @param fileUid   當前文件uuid
107      * @return
108      */
109     @Override
110     public ResultEntity<Boolean> singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
111         //實際情況下,這些路徑都應該是伺服器上面存儲文件的路徑
112         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑
113         String tempPath = filePath + "temp\\" + fileUid;//臨時文件存放路徑
114         File dir = new File(tempPath);
115         if (!dir.exists()) dir.mkdirs();
116  
117         //生成一個臨時文件名
118         String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";
119         try {
120             //將分片存儲到臨時文件夾中
121             filePart.transferTo(new File(tempFileNamePath));
122  
123             File tempDir = new File(tempPath);
124             File[] tempFiles = tempDir.listFiles();
125  
126             one:
127             if (partNum.equals(Objects.requireNonNull(tempFiles).length)) {
128                 //需要校驗一下,表示已有非同步程式正在合併了;如果是分散式這個校驗可以加入redis的分散式鎖來完成
129                 if (isMergePart.get(fileUid) != null) {
130                     break one;
131                 }
132                 isMergePart.put(fileUid, tempFiles.length);
133                 System.out.println("所有分片上傳完成,預計總分片:" + partNum + "; 實際總分片:" + tempFiles.length);
134  
135                 FileOutputStream fileOutputStream = new FileOutputStream(filePath + fileName);
136                 //這裡如果分片很多的情況下,可以採用多線程來執行
137                 for (int i = 0; i < partNum; i++) {
138                     //讀取分片數據,進行分片合併
139                     FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + fileName + "_" + i + ".part");
140                     byte[] buf = new byte[1024 * 8];//8MB
141                     int length;
142                     while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入位元組流裡面的數據
143                         fileOutputStream.write(buf, 0, length);//通過fos文件輸出位元組流寫出去
144                     }
145                     fileInputStream.close();
146                 }
147                 fileOutputStream.flush();
148                 fileOutputStream.close();
149  
150                 // 刪除臨時文件夾裡面的分片文件 如果使用流操作且沒有關閉輸入流,可能導致刪除失敗
151                 for (int i = 0; i < partNum; i++) {
152                     boolean delete = new File(tempPath + "\\" + fileName + "_" + i + ".part").delete();
153                     File file = new File(tempPath + "\\" + fileName + "_" + i + ".part");
154                 }
155                 //在刪除對應的臨時文件夾
156                 if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {
157                     tempDir.delete();
158                 }
159                 isMergePart.remove(fileUid);
160             }
161  
162         } catch (Exception e) {
163             log.error("單文件分片上傳失敗!", e);
164             return ResultEntity.error(false, "單文件分片上傳失敗");
165         }
166         //通過返回成功的分片值,來驗證分片是否有丟失
167         return ResultEntity.success(true, partIndex.toString());
168     }
169  
170     /**
171      * 多文件分片上傳
172      * 先將所有文件分片讀入到(伺服器)指定臨時路徑下,每個文件的分片文件的臨時文件夾都是已文件的uuid進行命名的
173      * 然後判斷對已經上傳所有分片的文件進行合併,此處是通過多線程對每一個文件的分片文件進行合併的
174      * 最後對已經合併完成的分片臨時文件和文件夾進行刪除
175      *
176      * @param filePart  分片文件
177      * @param partIndex 當前分片值
178      * @param partNum   總分片數
179      * @param fileName  當前文件名稱
180      * @param fileUid   當前文件uuid
181      * @return
182      */
183     @Override
184     public ResultEntity<String> multipleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
185         //實際情況下,這些路徑都應該是伺服器上面存儲文件的路徑
186         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑
187         String tempPath = filePath + "temp\\" + fileUid;//臨時文件存放路徑
188         File dir = new File(tempPath);
189         if (!dir.exists()) dir.mkdirs();
190         //生成一個臨時文件名
191         String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";
192         try {
193             filePart.transferTo(new File(tempFileNamePath));
194  
195             File tempDir = new File(tempPath);
196             File[] tempFiles = tempDir.listFiles();
197             //如果臨時文件夾中分片數量和實際分片數量一致的時候,就需要進行分片合併
198             one:
199             if (partNum.equals(tempFiles.length)) {
200                 //需要校驗一下,表示已有非同步程式正在合併了;如果是分散式這個校驗可以加入redis的分散式鎖來完成
201                 if (isMergePart.get(fileUid) != null) {
202                     break one;
203                 }
204                 isMergePart.put(fileUid, tempFiles.length);
205                 System.out.println(fileName + ":所有分片上傳完成,預計總分片:" + partNum + "; 實際總分片:" + tempFiles.length);
206  
207                 //使用多線程來完成對每個文件的合併
208                 Future<Integer> submit = partMergeTask.submit(new PartMergeTaskExecutor(filePath, tempPath, fileName, partNum));
209                 System.out.println("上傳文件名:" + fileName + "; 總大小:" + submit.get());
210                 isMergePart.remove(fileUid);
211             }
212         } catch (Exception e) {
213             log.error("{}:多文件分片上傳失敗!", fileName, e);
214             return ResultEntity.error("", "多文件分片上傳失敗");
215         }
216         //通過返回成功的分片值,來驗證分片是否有丟失
217         return ResultEntity.success(partIndex.toString(), fileUid);
218     }
219  
220     /**
221      * 多文件(分片)秒傳
222      * 通過對比已有的文件分片md5值和需要上傳文件分片的MD5值,
223      * 在文件分片合併的時候,對已有的文件進行地址索引,對沒有的文件進行臨時文件寫入
224      * 最後合併的時候根據不同的文件分片進行文件讀取寫入
225      *
226      * @param filePart  上傳沒有的分片文件
227      * @param fileInfo  當前分片文件相關信息
228      * @param fileOther 已存在文件分片相關信息
229      * @return
230      */
231     @Override
232     public ResultEntity<String> multipleFilePartFlashUpload(MultipartFile filePart, String fileInfo, String fileOther) {
233         DiskFileIndexVo upFileInfo = JSONObject.parseObject(fileInfo, DiskFileIndexVo.class);
234         List<DiskFileIndexVo> notUpFileInfoList = JSON.parseArray(fileOther, DiskFileIndexVo.class);
235         //實際情況下,這些路徑都應該是伺服器上面存儲文件的路徑
236         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑
237         //正常情況下,這個臨時文件也應該放入(伺服器)非臨時文件夾中,這樣方便下次其他文件上傳查找是否曾經上傳過類似的
238         //當前demo是單獨存放在臨時文件夾中,文件合併完成之後直接刪除的
239         String tempPath = filePath + "temp\\" + upFileInfo.getFileUid();//臨時文件存放路徑
240  
241         File dir = new File(tempPath);
242         if (!dir.exists()) dir.mkdirs();
243         //生成一個臨時文件名
244         String tempFileNamePath = tempPath + "\\" + upFileInfo.getFileName() + "_" + upFileInfo.getPartIndex() + ".part";
245  
246         try {
247             filePart.transferTo(new File(tempFileNamePath));
248  
249             File tempDir = new File(tempPath);
250             File[] tempFiles = tempDir.listFiles();
251             notUpFileInfoList = notUpFileInfoList.stream().filter(e ->
252                     upFileInfo.getFileUid().equals(e.getFileUid())).collect(Collectors.toList());
253             //如果臨時文件夾中分片數量和實際分片數量一致的時候,就需要進行分片合併
254             one:
255             if ((upFileInfo.getPartNum() - notUpFileInfoList.size()) == tempFiles.length) {
256                 //需要校驗一下,表示已有非同步程式正在合併了;如果是分散式這個校驗可以加入redis的分散式鎖來完成
257                 if (isMergePart.get(upFileInfo.getFileUid()) != null) {
258                     break one;
259                 }
260                 isMergePart.put(upFileInfo.getFileUid(), tempFiles.length);
261                 System.out.println(upFileInfo.getFileName() + ":所有分片上傳完成,預計總分片:" + upFileInfo.getPartNum()
262                         + "; 實際總分片:" + tempFiles.length + "; 已存在分片數:" + notUpFileInfoList.size());
263  
264                 //使用多線程來完成對每個文件的合併
265                 Future<Integer> submit = partMergeTask.submit(
266                         new PartMergeFlashTaskExecutor(filePath, upFileInfo, notUpFileInfoList));
267                 isMergePart.remove(upFileInfo.getFileUid());
268             }
269         } catch (Exception e) {
270             log.error("{}:多文件(分片)秒傳失敗!", upFileInfo.getFileName(), e);
271             return ResultEntity.error("", "多文件(分片)秒傳失敗!");
272         }
273         //通過返回成功的分片值,來驗證分片是否有丟失
274         return ResultEntity.success(upFileInfo.getPartIndex().toString(), upFileInfo.getFileUid());
275     }
276  
277     /**
278      * 根據傳入需要上傳的文件片段的md5值來對比伺服器中的文件的md5值,將已有對應的md5值的文件過濾出來,
279      * 通知前端或者自行出來這些文件,即為不需要上傳的文件分片,並將已有的文件分片地址索引返回給前端進行出來
280      *
281      * @param upLoadFileListMd5 原本需要上傳文件的索引分片信息
282      * @return
283      */
284     @Override
285     public ResultEntity<List<DiskFileIndexVo>> checkDiskFile(List<DiskFileIndexVo> upLoadFileListMd5) {
286         List<DiskFileIndexVo> notUploadFile;
287         try {
288             //後端伺服器已經存在的分片md5值集合
289             List<DiskFileIndexVo> diskFileMd5IndexList = diskFileIndexVos;
290  
291             notUploadFile = upLoadFileListMd5.stream().filter(uf -> diskFileMd5IndexList.stream().anyMatch(
292                     df -> {
293                         if (df.getFileMd5().equals(uf.getFileMd5())) {
294                             uf.setFileIndex(df.getFileName());//不需要上傳文件的地址索引
295                             return true;
296                         }
297                         return false;
298                     })).collect(Collectors.toList());
299             log.info("過濾出不需要上傳的文件分片:{}", notUploadFile);
300         } catch (Exception e) {
301             log.error("上傳文件檢測異常!", e);
302             return ResultEntity.error("上傳文件檢測異常!");
303         }
304         return ResultEntity.success(notUploadFile);
305     }
306  
307     /**
308      * 根據文件uuid(md5生成的)來判斷此文件在伺服器中是否未上傳完整,
309      * 如果沒上傳完整,則返回相關上傳進度等信息
310      *
311      * @param pointFileIndexVo
312      * @return
313      */
314     @Override
315     public ResultEntity<PointFileIndexVo> checkUploadFileIndex(PointFileIndexVo pointFileIndexVo) {
316         try {
317             List<String> list = uploadProgress.get(pointFileIndexVo.getFileMd5());
318             if (list == null) list = new ArrayList<>();
319             pointFileIndexVo.setParts(list);
320             System.out.println("已上傳部分:" + list);
321             return ResultEntity.success(pointFileIndexVo);
322         } catch (Exception e) {
323             log.error("上傳文件檢測異常!", e);
324             return ResultEntity.error("上傳文件檢測異常!");
325         }
326     }
327  
328     /**
329      * 單文件(分片)斷點上傳
330      *
331      * @param filePart 需要上傳的分片文件
332      * @param fileInfo 當前需要上傳的分片文件信息,如uuid,文件名,文件總分片數量等
333      * @return
334      */
335     @Override
336     public ResultEntity<String> singleFilePartPointUpload(MultipartFile filePart, String fileInfo) {
337         PointFileIndexVo pointFileIndexVo = JSONObject.parseObject(fileInfo, PointFileIndexVo.class);
338         //實際情況下,這些路徑都應該是伺服器上面存儲文件的路徑
339         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑
340         String tempPath = filePath + "temp\\" + pointFileIndexVo.getFileMd5();//臨時文件存放路徑
341         File dir = new File(tempPath);
342         if (!dir.exists()) dir.mkdirs();
343  
344         //生成一個臨時文件名
345         String tempFileNamePath = tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + pointFileIndexVo.getPartIndex() + ".part";
346         try {
347             //將分片存儲到臨時文件夾中
348             filePart.transferTo(new File(tempFileNamePath));
349  
350             List<String> partIndex = uploadProgress.get(pointFileIndexVo.getFileMd5());
351             if (Objects.isNull(partIndex)) {
352                 partIndex = new ArrayList<>();
353             }
354             partIndex.add(pointFileIndexVo.getPartIndex().toString());
355             uploadProgress.put(pointFileIndexVo.getFileMd5(), partIndex);
356  
357             File tempDir = new File(tempPath);
358             File[] tempFiles = tempDir.listFiles();
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Spring Cloud是一個相對比較成熟的微服務框架。雖然,Spring Cloud於2016年才推出1.0的release版本, 時間最短, 但是相比Dubbo等RPC框架, Spring Cloud提供的全套的分散式系統解決方案。 Spring Cloud是一系列框架的有序集合。它利用Spri ...
  • Redis 的單線程與多線程之爭 為什麼 Redis 使用單線程 Redis 單線程為什麼還那麼快 Redis 6.0 引入多線程的原因 Redis 的網路模型 結語 ...
  • 一、如何使用代理方式打開網頁 在 playwright.chromium.launch() 中傳入 proxy 參數即可,示例代碼如下: 1、同步寫法: from playwright.sync_api import sync_playwright proxy = {'server': 'http: ...
  • Charapter 1: 端倪 最近一直在用Pyglet做一個小的案例,但是實際運行起來時發現了嚴重的記憶體泄漏。經調查後發現平均每秒會爆出80-120不等的頁面錯誤。且可以觀察到記憶體正在不斷地以0.2-0.4mb不等的速度增長。 能夠複原此問題的代碼如下: # 導入庫 import pyglet w ...
  • 壓縮 PDF 文件能有效減小文件大小並提高文件傳輸的效率,同時還能節省電腦存儲空間。除了使用一些專業工具對PDF文件進行壓縮,我們還可以通過 Python 來執行該操作,實現自動化、批量處理PDF文件。 本文將分享一個簡單有效的使用 Python 壓縮 PDF 文件的方法。需要用到 Spire.P ...
  • 本文摘要:本文首先對I2C協議的通信模式和AT24C16-EEPROM晶元時序控制進行分析和理解,設計了一個i2c通信方案。人為按下寫操作按鍵後,FPGA(Altera EP4CE10)對EEPROM指定地址寫入位元組數據,並接後按下讀操作按鍵,讀取該地址上的一個位元組數據在數位管低兩位顯示出來。其中包 ...
  • 刷機 溫馨提示:如果你不知道root的意義在哪,建議不要解鎖和root,到時候救磚或者回鎖都挺麻煩。 刷全量包 最新版的系統沒有更新推送,所以去一加社區[0]找了個全量包來刷,。安裝方式可以看帖子里的內容,說的比較詳細,這裡截圖一部分: 解鎖bootloader 在系統與更新-》開發者選項 里勾選O ...
  • 多線程在訪問同一個共用變數時很可能會出現併發問題,特別是在多線程對共用變數進入寫入時,那麼除了加鎖還有其他方法避免併發問題嗎?本文將詳細講解 ThreadLocal 的使用及其源碼。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...