Spring Boot 3 整合 MinIO 實現分布式文件存儲

引言

文件存儲已成為一個做任何應用都不可回避的需求。傳統的單機文件存儲方案在面對大規模數據和高并發訪問時往往力不從心,而分布式文件存儲系統則提供了更好的解決方案。本篇文章我將基于Spring Boot 3 為大家講解如何基于MinIO來實現分布式文件存儲。

分布式存儲的出現

在探討核心內容之前,我們不妨先回顧分布式存儲技術是如何伴隨系統架構演變發展的。在單體架構早期,文件直接存儲于應用服務器中,這種方式簡單直接,存取便捷。然而,隨著業務規模擴大和用戶量激增,系統架構逐步向分布式或微服務方向演進。此時,若仍將文件存儲在應用服務器中,在負載均衡機制下可能導致文件訪問異常 —— 用戶上傳的文件可能因路由到其他服務節點而無法訪問。
在這里插入圖片描述面對這個挑戰,我們可以借鑒"分層解決"的架構思想:將文件存儲從應用服務中剝離,集中在獨立的存儲服務中統一管理。這便是分布式文件存儲系統的雛形。

技術選型

在了解了分布式存儲的演進背景后,讓我們來梳理當前主流的分布式存儲解決方案。

其他

  • FastDFS -> 架構老舊,社區活躍度低,文檔資料匱乏
  • Ambry -> 過度依賴 LinkedIn 技術棧,通用性不足
  • MooseFS -> 部署配置繁瑣,運維門檻高
  • MogileFS -> 性能一般,擴展性受限
  • LeoFS -> 更新維護緩慢,生態系統不完善
  • openstack -> 架構復雜重量級,不適合輕量級應用
  • TFS -> 主要服務于阿里內部,外部支持有限
  • ceph -> 學習曲線陡峭,配置調優復雜
  • GlusterFS -> 架構復雜,問題定位困難
  • OSS -> 商業收費服務,成本隨數據量增長

?MinIO

MinIO 是一款輕量級的分布式對象存儲系統,完全兼容 Amazon S3 云存儲服務接口。其部署維護簡單,性能卓越,成為我們的首選方案。

MinIO安裝

MinIO 提供了多種部署方式,包括單機部署和分布式部署。本文主要關注 Spring BootMinIO 的整合實踐,因此我們選擇使用Docker(Ps:沒安裝Docker的同學速速去安裝,或者用別的方式只要本地部署的能跑就行)進行快速部署。

首先,通過命令拉取鏡像。

docker pull minio/minio

接著在 本地創建一個存儲文件的映射目錄 D:\minio\data(Ps:我當前演示的環境是win系統,大家根據自己的操作系統建個目錄就行),使用以下命令啟動 MinIO:

🔑 補充一個小細節:MinIO 的安全限制要求用戶名長度至少需要 3 個字符,密碼長度至少需要 8 個字符。

    docker run -d --name minio -p 9000:9000 -p 9001:9001 -v D:\minio\data:/data -e "MINIO_ROOT_USER=root" -e "MINIO_ROOT_PASSWORD=12345678" minio/minio server /data --console-address ":9001" --address ":9000"

參數說明:

  • -d: 后臺運行容器
  • --name: 容器名稱
  • -p: 端口映射,9000用于API訪問,9001用于控制臺訪問
  • -v: 目錄映射,將本地目錄映射到容器的 /data
  • -e: 環境變量,設置管理員賬號和密碼
  • --console-address: 指定控制臺端口
  • --restart=always: 容器自動重啟策略
  • --address ":9000": 顯式指定 API 端口

運行成功后訪問 http://localhost:9001,使用執行命令中的憑據(Ps:大家在使用時可以修改為自己的用戶名和密碼)登錄:

  • 用戶名:root
  • 密碼:12345678
    在這里插入圖片描述登錄系統后,界面會提示創建。熟悉云服務商OSS服務的讀者對此概念應該不陌生。對初次接觸的讀者,可以將理解為一個命名空間或文件夾,您可以創建多個,每個內還能包含多層級的文件夾和文件。

在這里插入圖片描述這里我演示下控制臺如何建桶和上傳文件,方便大家理解文件在MinIO上的存儲結構。
在這里插入圖片描述只需要輸入名稱就可以,建好之后可以看到的使用狀態。
在這里插入圖片描述點擊它進入的內部,這里大家需要關注一個設置- Access Policy,默認是Private。這個設置需要根據業務的實際情況來,如果你的業務是需要提供一些不需要鑒權的公共訪問的文件,就設為public;反之,就保持private。我這里把它修改為public
在這里插入圖片描述然后點擊右上角的上傳按鈕進入上傳頁可以向桶內上傳文件。
在這里插入圖片描述上傳成功后可以在桶內看到文件。
在這里插入圖片描述點擊文件可查看詳情,支持預覽、刪除、分享等多種功能。這些操作較為直觀,安裝后各位讀者可自行體驗。本文重點關注不在控制臺的操作,就不做過多贅述了。

🔑這里再強調一點:存儲在里的文件通過API訪問的端口和控制臺是不一樣的。如果你對這里感覺迷惑,可以回看一下上面我貼上的docker運行命令里配置了兩個端口-90009001。如果要通過API訪問查看這個文件的話,通過拼接地址/端口號/桶名/文件路徑查看,那么剛測試上傳的文件的訪問API就是http://localhost:9000/test/1.gif,在瀏覽器地址欄輸入后可以看到。
在這里插入圖片描述# Spring Boot整合MinIO

這部分對于新建項目就不贅述了,直接說下我使用的 Spring boot 版本為3.2.3,供大家參考。

1.引入依賴

pom.xml引入minIO的依賴,版本大家自己使用你當前最新的版本即可。

<!-- minio -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>${latest.version}</version>
</dependency>

2.添加配置

在yml配置文件中配置連接信息。

# minIO配置
minio:endpoint: http://127.0.0.1:9000     # MinIO服務地址fileHost: http://127.0.0.1:9000     # 文件地址hostbucketName: wechat                  # 存儲桶bucket名稱accessKey: root                     # 用戶名secretKey: 12345678                 # 密碼

3.編寫配置類

import com.pitayafruits.utils.MinIOUtils;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@Data
public class MinIOConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.fileHost}")private String fileHost;@Value("${minio.bucketName}")private String bucketName;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinIOUtils creatMinioClient() {return new MinIOUtils(endpoint, fileHost, bucketName, accessKey, secretKey);}
}

4.引入工具類

這個工具類封裝了MinIO的核心功能,為您提供了很多開箱即用的功能。通過引入它,可以輕松實現文件上傳、下載等操作,讓大家將更多精力集中在業務開發上。

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;/*** MinIO工具類*/
@Slf4j
public class MinIOUtils {private static MinioClient minioClient;private static String endpoint;private static String fileHost;private static String bucketName;private static String accessKey;private static String secretKey;private static final String SEPARATOR = "/";public MinIOUtils() {}public MinIOUtils(String endpoint, String fileHost, String bucketName, String accessKey, String secretKey) {MinIOUtils.endpoint = endpoint;MinIOUtils.fileHost = fileHost;MinIOUtils.bucketName = bucketName;MinIOUtils.accessKey = accessKey;MinIOUtils.secretKey = secretKey;createMinioClient();}/*** 創建基于Java端的MinioClient*/public void createMinioClient() {try {if (null == minioClient) {log.info("開始創建 MinioClient...");minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();createBucket(bucketName);log.info("創建完畢 MinioClient...");}} catch (Exception e) {log.error("MinIO服務器異常:{}", e);}}/*** 獲取上傳文件前綴路徑* @return*/public static String getBasisUrl() {return endpoint + SEPARATOR + bucketName + SEPARATOR;}/******************************  Operate Bucket Start  ******************************//*** 啟動SpringBoot容器的時候初始化Bucket* 如果沒有Bucket則創建* @throws Exception*/private static void createBucket(String bucketName) throws Exception {if (!bucketExists(bucketName)) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/***  判斷Bucket是否存在,true:存在,false:不存在* @return* @throws Exception*/public static boolean bucketExists(String bucketName) throws Exception {return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 獲得Bucket的策略* @param bucketName* @return* @throws Exception*/public static String getBucketPolicy(String bucketName) throws Exception {String bucketPolicy = minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());return bucketPolicy;}/*** 獲得所有Bucket列表* @return* @throws Exception*/public static List<Bucket> getAllBuckets() throws Exception {return minioClient.listBuckets();}/*** 根據bucketName獲取其相關信息* @param bucketName* @return* @throws Exception*/public static Optional<Bucket> getBucket(String bucketName) throws Exception {return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();}/*** 根據bucketName刪除Bucket,true:刪除成功; false:刪除失敗,文件或已不存在* @param bucketName* @throws Exception*/public static void removeBucket(String bucketName) throws Exception {minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());}/******************************  Operate Bucket End  ******************************//******************************  Operate Files Start  ******************************//*** 判斷文件是否存在* @param bucketName 存儲桶* @param objectName 文件名* @return*/public static boolean isObjectExist(String bucketName, String objectName) {boolean exist = true;try {minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());} catch (Exception e) {exist = false;}return exist;}/*** 判斷文件夾是否存在* @param bucketName 存儲桶* @param objectName 文件夾名稱* @return*/public static boolean isFolderExist(String bucketName, String objectName) {boolean exist = false;try {Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());for (Result<Item> result : results) {Item item = result.get();if (item.isDir() && objectName.equals(item.objectName())) {exist = true;}}} catch (Exception e) {exist = false;}return exist;}/*** 根據文件前置查詢文件* @param bucketName 存儲桶* @param prefix 前綴* @param recursive 是否使用遞歸查詢* @return MinioItem 列表* @throws Exception*/public static List<Item> getAllObjectsByPrefix(String bucketName,String prefix,boolean recursive) throws Exception {List<Item> list = new ArrayList<>();Iterable<Result<Item>> objectsIterator = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());if (objectsIterator != null) {for (Result<Item> o : objectsIterator) {Item item = o.get();list.add(item);}}return list;}/*** 獲取文件流* @param bucketName 存儲桶* @param objectName 文件名* @return 二進制流*/public static InputStream getObject(String bucketName, String objectName) throws Exception {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 斷點下載* @param bucketName 存儲桶* @param objectName 文件名稱* @param offset 起始字節的位置* @param length 要讀取的長度* @return 二進制流*/public InputStream getObject(String bucketName, String objectName, long offset, long length)throws Exception {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());}/*** 獲取路徑下文件列表* @param bucketName 存儲桶* @param prefix 文件名稱* @param recursive 是否遞歸查找,false:模擬文件夾結構查找* @return 二進制流*/public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,boolean recursive) {return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());}/*** 使用MultipartFile進行文件上傳* @param bucketName 存儲桶* @param file 文件名* @param objectName 對象名* @param contentType 類型* @return* @throws Exception*/public static ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,String objectName, String contentType) throws Exception {InputStream inputStream = file.getInputStream();return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType).stream(inputStream, inputStream.available(), -1).build());}/*** 上傳本地文件* @param bucketName 存儲桶* @param objectName 對象名稱* @param fileName 本地文件路徑*/public static String uploadFile(String bucketName, String objectName,String fileName, boolean needUrl) throws Exception {minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());if (needUrl) {String imageUrl = fileHost+ "/"+ bucketName+ "/"+ objectName;return imageUrl;}return "";}/*** 通過流上傳文件** @param bucketName 存儲桶* @param objectName 文件對象* @param inputStream 文件流*/public static ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());}public static String uploadFile(String bucketName, String objectName, InputStream inputStream, boolean needUrl) throws Exception {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());if (needUrl) {String imageUrl = fileHost+ "/"+ bucketName+ "/"+ objectName;return imageUrl;}return "";}/*** 創建文件夾或目錄* @param bucketName 存儲桶* @param objectName 目錄路徑*/public static ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());}/*** 獲取文件信息, 如果拋出異常則說明文件不存在** @param bucketName 存儲桶* @param objectName 文件名稱*/public static String getFileStatusInfo(String bucketName, String objectName) throws Exception {return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).toString();}/*** 拷貝文件** @param bucketName 存儲桶* @param objectName 文件名* @param srcBucketName 目標存儲桶* @param srcObjectName 目標文件名*/public static ObjectWriteResponse copyFile(String bucketName, String objectName,String srcBucketName, String srcObjectName) throws Exception {return minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object(objectName).build()).bucket(srcBucketName).object(srcObjectName).build());}/*** 刪除文件* @param bucketName 存儲桶* @param objectName 文件名稱*/public static void removeFile(String bucketName, String objectName) throws Exception {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 批量刪除文件* @param bucketName 存儲桶* @param keys 需要刪除的文件列表* @return*/public static void removeFiles(String bucketName, List<String> keys) {List<DeleteObject> objects = new LinkedList<>();keys.forEach(s -> {objects.add(new DeleteObject(s));try {removeFile(bucketName, s);} catch (Exception e) {log.error("批量刪除失敗!error:{}",e);}});}/*** 獲取文件外鏈* @param bucketName 存儲桶* @param objectName 文件名* @param expires 過期時間 <=7 秒 (外鏈有效時間(單位:秒))* @return url* @throws Exception*/public static String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();return minioClient.getPresignedObjectUrl(args);}/*** 獲得文件外鏈* @param bucketName* @param objectName* @return url* @throws Exception*/public static String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(Method.GET).build();return minioClient.getPresignedObjectUrl(args);}/*** 將URLDecoder編碼轉成UTF8* @param str* @return* @throws UnsupportedEncodingException*/public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");return URLDecoder.decode(url, "UTF-8");}/******************************  Operate Files End  ******************************/}

5.開發測試

我剛好在做練手項目,這里寫個上傳頭像的接口。

import com.pitayafruits.base.BaseInfoProperties;
import com.pitayafruits.config.MinIOConfig;
import com.pitayafruits.grace.result.GraceJSONResult;
import com.pitayafruits.grace.result.ResponseStatusEnum;
import com.pitayafruits.utils.MinIOUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("file")
public class FileController extends BaseInfoProperties {@Resourceprivate MinIOConfig minIOConfig;@PostMapping("uploadFace")public GraceJSONResult upload(@RequestParam MultipartFile file,String userId) throws Exception {if (StringUtils.isBlank(userId)) {return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);}String filename = file.getOriginalFilename();if (StringUtils.isBlank(filename)) {return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);}filename = "face" +  "/" + userId + "/" + filename;MinIOUtils.uploadFile(minIOConfig.getBucketName(), filename, file.getInputStream());String faceUrl = minIOConfig.getFileHost()+ "/"+ minIOConfig.getBucketName()+ "/"+ filename;return GraceJSONResult.ok(faceUrl);}}

可以看到通過工具類只需要一行代碼就可以實現上傳文件,我們只需要在調用的時候做好文件的業務隔離即可。完成了接口的開發,這里我來通過Apifox調用測試一下。
在這里插入圖片描述通過瀏覽器訪問返回的圖片鏈接會自動下載,我們再登錄控制臺看對應的桶下的這個路徑,也可以看到這個文件。
在這里插入圖片描述

小結

我們在集成第三方服務時應遵循一個核心原則:將API操作封裝成通用工具類。這不僅讓MinIO的集成更加優雅,也讓代碼具備更好的復用性和可維護性。這種思維方式同樣適用于其他第三方服務的對接。

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

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

相關文章

3月5日作業

代碼作業&#xff1a; #!/bin/bash# 清空目錄函數 safe_clear_dir() {local dir"$1"local name"$2"if [ -d "$dir" ]; thenwhile true; doread -p "檢測到 $name 目錄已存在&#xff0c;請選擇操作&#xff1a; 1) 清空目錄內容 2) 保留目…

達夢數據庫關于參數PK_WITH_CLUSTER的改動分析

目錄 1、PK_WITH_CLUSTER取值為0 2、PK_WITH_CLUSTER取值為1 達夢數據庫的參數PK_WITH_CLUSTER在最近使用過程中發現與前期使用的版本存在差異&#xff0c;特此測試分析一下。具體哪個版本改動的暫未得知。 PK_WITH_CLUSTER&#xff0c;默認值為0&#xff0c;動態會話級參數。…

android11使用gpio口控制led狀態燈

目錄 一、簡介 二、解決方法 A、底層驅動 B、上層調用 C、驗證 一、簡介 1、需求&#xff1a;這里是用2個gpio口來控制LED燈&#xff0c;開機時默認亮藍燈&#xff0c;按開機鍵&#xff0c;休眠亮紅燈&#xff0c;喚醒亮藍燈。 原理圖&#xff1a; 這里由于主板上電阻R63…

windows 利用nvm 管理node.js 2025最新版

1.首先在下載nvm 下載鏈接 2. 下載最新版本的nvm 3. 同意協議 注意&#xff1a;選擇安裝路徑 之后一直下一步即可 可以取消勾選 open with Powershell 勾選后它會自動打開Powershell 這里選用cmd 輸入以下命令查看是否安裝成功 nvm version 查看已經安裝的版本 我之前自…

深入淺出:UniApp 從入門到精通全指南

https://juejin.cn/post/7440119937644101684 uni-app官網 本文是關于 UniApp 從入門到精通的全指南&#xff0c;涵蓋基礎入門&#xff08;環境搭建、創建項目、項目結構、編寫運行&#xff09;、核心概念與進階知識&#xff08;組件與開發、頁面路由與導航、數據綁定與響應式…

MySQL ——數據的增刪改查

一、DML語言 1.1 insert插入數據 語法&#xff1a;insert [into] 表名 [字段名] values(值列表)&#xff1b; 插入一行數據 第一種&#xff1a;insert into file1(id,name,age) values (1,‘aa’,11); 第二種&#xff1a;insert into file1 values(1,‘aa’,11); 插入多行數…

【CF記錄】貪心——A. Scrambled Scrabble

https://codeforces.com/contest/2045/problem/A 思路&#xff1a; 由于Y有兩種選擇&#xff0c;NG也是&#xff0c;那我們可以枚舉以下情況&#xff1a;選i個Y做輔音&#xff0c;j個NG做輔音 然后貪心選擇最長的即可&#xff0c;觀察到S最長為5000&#xff0c;即使是也不會…

C語言【指針篇】(四)

前言&#xff1a;正文1. 字符指針變量2. 數組指針變量2.1 數組指針變量是什么?2.2 數組指針變量怎么初始化 3. 二維數組傳參的本質4. 函數指針變量4.1 函數指針變量的創建4.2 函數指針變量的使用4.3 兩段有趣的代碼4.3.1 typedef關鍵字 5. 函數指針數組6. 轉移表 總結 前言&am…

React + TypeScript 實戰指南:用類型守護你的組件

TypeScript 為 React 開發帶來了強大的類型安全保障&#xff0c;這里解析常見的一些TS寫法&#xff1a; 一、組件基礎類型 1. 函數組件定義 // 顯式聲明 Props 類型并標注返回值 interface WelcomeProps {name: string;age?: number; // 可選屬性 }const Welcome: React.FC…

【玩轉正則表達式】將正則表達式中的分組(group)與替換進行結合使用

在文本處理和數據分析領域&#xff0c;正則表達式&#xff08;Regular Expressions&#xff0c;簡稱regex&#xff09;是一種功能強大的工具。它不僅能夠幫助我們匹配和搜索字符串中的特定模式&#xff0c;還能通過分組&#xff08;Grouping&#xff09;和替換&#xff08;Subs…

Flutter 學習之旅 之 flutter 不使用插件,簡單實現一個 Toast 功能

Flutter 學習之旅 之 flutter 不使用插件&#xff0c;簡單實現一個 Toast 功能 目錄 Flutter 學習之旅 之 flutter 不使用插件&#xff0c;簡單實現一個 Toast 功能 一、簡單介紹 二、簡單介紹 Toast 1. 確保正確配置 navigatorKey 2. 避免重復顯示 Toast 3. 確保 Toast …

《OpenCV》——dlib(人臉應用實例)

文章目錄 dlib庫dlib庫——人臉應用實例——表情識別dlib庫——人臉應用實例——疲勞檢測 dlib庫 dlib庫的基礎用法介紹可以參考這篇文章&#xff1a;https://blog.csdn.net/lou0720/article/details/145968062?spm1011.2415.3001.5331&#xff0c;故此這篇文章只介紹dlib的人…

學習日記-250305

閱讀論文&#xff1a;Leveraging Pedagogical Theories to Understand Student Learning Process with Graph-based Reasonable Knowledge Tracing ps:代碼邏輯最后一點還沒理順&#xff0c;明天繼續 4.2 Knowledge Memory & Knowledge Tracing 代碼研究&#xff1a; 一般…

【AI大模型】DeepSeek + Kimi 高效制作PPT實戰詳解

目錄 一、前言 二、傳統 PPT 制作問題 2.1 傳統方式制作 PPT 2.2 AI 大模型輔助制作 PPT 2.3 適用場景對比分析 2.4 最佳實踐與推薦 三、DeepSeek Kimi 高效制作PPT操作實踐 3.1 Kimi 簡介 3.2 DeepSeek Kimi 制作PPT優勢 3.2.1 DeepSeek 優勢 3.2.2 Kimi 制作PPT優…

【ESP-ADF】在 VSCode 安裝 ESP-ADF 注意事項

1.檢查網絡 如果您在中國大陸安裝&#xff0c;請使用魔法上網&#xff0c;避免無法 clone ESP-ADF 倉庫。 2.VSCode 安裝 ESP-ADF 在 VSCode 左側活動欄選擇 ESP-IDF:explorer&#xff0c;展開 advanced 并點擊 Install ESP-ADF 然后會出現選擇 ESP-ADF 安裝目錄。 如果出現…

關于2023新版PyCharm的使用

考慮到大家AI編程的需要&#xff0c;建議大家安裝新版Python解釋器和新版PyCharm&#xff0c;下載地址都可以官網進行&#xff1a; Python&#xff1a;Download Python | Python.org&#xff08;可以根據需要自行選擇&#xff0c;建議選擇3.11&#xff0c;保持交流版本一致&am…

輕松部署 Stable Diffusion WebUI 并實現局域網共享訪問:解決 Conda Python 版本不為 3.10.6 的難題

這篇博文主要為大家講解關于sd webui的部署問題&#xff0c;大家有什么不懂的可以隨時問我&#xff0c;如果沒有及時回復&#xff0c;可聯系&#xff1a;1198965922 如果后續大家需要了解怎么用代碼調用部署好的webui的接口&#xff0c;可以在評論區留言哦&#xff0c;博主可以…

Leetcode 103: 二叉樹的鋸齒形層序遍歷

Leetcode 103: 二叉樹的鋸齒形層序遍歷 問題描述&#xff1a; 給定一個二叉樹&#xff0c;返回其節點值的鋸齒形層序遍歷&#xff08;即第一層從左到右&#xff0c;第二層從右到左&#xff0c;第三層從左到右&#xff0c;依此類推&#xff09;。 適合面試的解法&#xff1a;廣…

Linux中的進程間通信的方式及其使用場景

在 Linux 系統中&#xff0c;進程間通信&#xff08;Inter-Process Communication, IPC&#xff09;是指不同進程之間傳遞數據、共享信息的機制。Linux 提供了多種進程間通信的方式&#xff0c;每種方式都有不同的特點和使用場景。以下是常見的幾種進程間通信方式及其應用場景&…

springBoot集成emqx 實現mqtt消息的發送訂閱

介紹 我們可以想象這么一個場景&#xff0c;我們java應用想要采集到電表a的每小時的用電信息&#xff0c;我們怎么拿到電表的數據&#xff1f;一般我們會想 直接 java 后臺發送請求給電表&#xff0c;然后讓電表返回數據就可以了&#xff0c;事實上&#xff0c;我們java應用發…