当前位置: 首页 > news >正文

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的集成更加优雅,也让代码具备更好的复用性和可维护性。这种思维方式同样适用于其他第三方服务的对接。

相关文章:

Spring Boot 3 整合 MinIO 实现分布式文件存储

引言 文件存储已成为一个做任何应用都不可回避的需求。传统的单机文件存储方案在面对大规模数据和高并发访问时往往力不从心&#xff0c;而分布式文件存储系统则提供了更好的解决方案。本篇文章我将基于Spring Boot 3 为大家讲解如何基于MinIO来实现分布式文件存储。 分布式存…...

ubuntu20 安装python2

1. 确保启用了 Universe 仓库 在某些情况下&#xff0c;python2-minimal 包可能位于 Universe 仓库中。你可以通过以下命令启用 Universe 仓库并更新软件包列表&#xff1a; bash复制 sudo add-apt-repository universe sudo apt update 然后尝试安装&#xff1a; bash复制…...

2025.3.3总结

周一这天&#xff0c;我约了绩效教练&#xff0c;主要想了解专业类绩效的考核方式以及想知道如何拿到一个更好的绩效。其他的岗位并不是很清楚&#xff0c;但是专业类的岗位&#xff0c;目前采取绝对考核&#xff0c;管理层和专家岗采取相对考核&#xff0c;有末尾淘汰。 通过…...

多线程-JUC源码

简介 JUC的核心是AQS&#xff0c;大部分锁都是基于AQS扩展出来的&#xff0c;这里先结合可重入锁和AQS&#xff0c;做一个讲解&#xff0c;其它的锁的实现方式也几乎类似 ReentrantLock和AQS AQS的基本结构 AQS&#xff0c;AbstractQueuedSynchronizer&#xff0c;抽象队列…...

ICLR 2025|香港浸会大学可信机器学习和推理课题组专场

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; AITIME 01 ICLR 2025预讲会团队专场 AITIME 02 专场信息 01 Noisy Test-Time Adaptation in Vision-Language Models 讲者&#xff1a;曹晨涛&#xff0c;HKBU TMLR Group一年级博士生&#xff0c;目前关注基础…...

docker引擎备份及解决拉取失败的问题

总结一下本文&#xff0c;docker引擎不是越多越好&#xff0c;此外阿里云的容器引擎加速可适用大多数情况。 docker引擎备份 仅使用阿里云 docker引擎备份&#xff0c;唯一使用的镜像地址是我的阿里云docker镜像加速地址&#xff0c;效果好&#xff08;注意下面的阿里云镜像加…...

Django项目实战

1、安装django 查看包安装的位置 pip镜像源 镜像源名称镜像地址​清华源​https://pypi.tuna.tsinghua.edu.cn/simple​阿里云​https://mirrors.aliyun.com/pypi/simple​腾讯云​https://mirrors.cloud.tencent.com/pypi/simple​华为云​https://repo.huaweicloud.co…...

【ThreeJS Basics 1-6】Camera

文章目录 Camera 相机PerspectiveCamera 透视相机正交相机用鼠标控制相机大幅度转动&#xff08;可以看到后面&#xff09; 控制组件FlyControls 飞行组件控制FirstPersonControls 第一人称控制PointerLockControls 指针锁定控制OrbitControls 轨道控制TrackballControls 轨迹球…...

SpringBoot-模拟SSE对话交互

SpringBoot-模拟SSE对话交互 后端使用SSE进行会话&#xff0c;前端使用Html模拟大模型的问答交互->【前端】【后端】 1-学习目的 本项目代码仓库&#xff1a;https://gitee.com/enzoism/springboot_sse 1-核心知识点 1&#xff09;什么是SSE协议->客户端发起一次请求&am…...

删除链表的倒数第N个节点 力扣19

一、题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&a…...

IvorySQL v4 逻辑复制槽同步功能解析:高可用场景下的数据连续性保障

功能简介 IvorySQL v4 基于 PostgreSQL 17&#xff0c;引入了逻辑复制槽同步至热备份数据库的功能。这一改进有效解决了旧版本中主数据库与备份数据库切换后逻辑复制中断的问题。对于那些追求数据高可用性和业务连续性的数据库来说&#xff0c;这无疑是一个重大的利好消息。它…...

vxe-table开启表尾和el-collapse-transition不兼容,动画卡顿

调用控制台 发现是el-collapse-transition内置的计算高度函数计算的高度总是会高一点 直接放弃使用el-collapse-transition 使用下面的div包裹住vxe-table 我的table是渲染出来的会有多个 <el-button click"group.messShow !group.messShow" type"text&q…...

康谋分享 | 3DGS:革新自动驾驶仿真场景重建的关键技术

随着自动驾驶技术的迅猛发展&#xff0c;构建高保真、动态的仿真场景成为了行业的迫切需求。传统的三维重建方法在处理复杂场景时常常面临效率和精度的挑战。在此背景下&#xff0c;3D高斯点阵渲染&#xff08;3DGS&#xff09;技术应运而生&#xff0c;成为自动驾驶仿真场景重…...

golang学习笔记——go语言安装及系统环境变量设置

文章目录 go语言安装go envgo getgoproxy测试安装 Go 插件安装 Go 插件依赖工具参考资料用户环境变量和系统环境变量用户环境变量系统环境变量示例设置环境变量的步骤设置用户环境变量设置系统环境变量 验证环境变量总结 2024年最火的5大Go框架1. Gin&#xff1a;高并发接口的“…...

Redis|集群 Cluster

文章目录 是什么能干嘛集群算法-分片-槽位slotredis集群的槽位slotredis集群的分片分片槽位的优势slot槽位映射——业界的3种解决方案小厂&#xff1a;哈希取余分区中厂&#xff1a;一致性哈希算法分区大厂&#xff1a;哈希槽分区 面试题&#xff1a;为什么 Redis 集群的最大槽…...

解锁MacOS开发:环境配置与应用开发全攻略

✨✨✨这里是小韩学长yyds的BLOG(喜欢作者的点个关注吧) ✨✨✨想要了解更多内容可以访问我的主页 小韩学长yyds-CSDN博客 目录 引言 一、MacOS 开发环境配置 &#xff08;一&#xff09;必备工具安装 &#xff08;二&#xff09;集成开发环境&#xff08;IDE&#xff09;选…...

如何通过卷积神经网络(CNN)有效地提取图像的局部特征,并在CIFAR-10数据集上实现高精度的分类?

目录 1. CNN 提取图像局部特征的原理 2. 在 CIFAR - 10 数据集上实现高精度分类的步骤 2.1 数据准备 2.2 构建 CNN 模型 2.3 定义损失函数和优化器 2.4 训练模型 2.5 测试模型 3. 提高分类精度的技巧 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#…...

监听 RabbitMQ 延时交换机的消息数、OpenFeign 路径参数传入斜杠无法正确转义

背景 【MQ】一套为海量消息和高并发热点消息&#xff0c;提供高可用精准延时服务的解决方案 我现在有一个需求&#xff0c;就是监听 RabbitMQ 一个延时交换机的消息数&#xff0c;而 RabbitTemplate 是不存在对应的方法来获取的。 而我们在 RabbitMQ 的控制台却可以发现延时交…...

希音(Shein)前端开发面试题集锦和参考答案

用 Node 写过什么工具或 npm 包 在实际开发中,使用 Node 编写过多种实用工具和 npm 包。 自动化构建工具 开发了一个简单的自动化构建工具,用于处理前端项目的资源压缩和合并。在前端项目中,为了优化性能,需要对 CSS 和 JavaScript 文件进行压缩,减少文件体积,同时将多个…...

python全栈-Linux基础

python全栈-Linux基础 文章目录 Linux安装/配置网络配置配置Linux远程登录配置虚拟机内部ip配置真机的ip安装XShell和Xftp目录结构用户和用户组用户管理添加用户useradd查看用户id修改用户usermod (选项)(参数)用户密码设置passed (选项)(参数)删除用户userdel [选项] 用户名 用…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

谷歌浏览器插件

项目中有时候会用到插件 sync-cookie-extension1.0.0&#xff1a;开发环境同步测试 cookie 至 localhost&#xff0c;便于本地请求服务携带 cookie 参考地址&#xff1a;https://juejin.cn/post/7139354571712757767 里面有源码下载下来&#xff0c;加在到扩展即可使用FeHelp…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

DiscuzX3.5发帖json api

参考文章&#xff1a;PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下&#xff0c;适配我自己的需求 有一个站点存在多个采集站&#xff0c;我想通过主站拿标题&#xff0c;采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...