文章目录

  • 一、介绍
    • 1、对象存储和分布式文件简介
    • 2、MinIO简介
    • 3、MinIO特点
  • 二、MinIO安装入门
    • 1、介绍
    • 2、快速安装
      • 2.1 Windows
      • 2.2 Linux
      • 2.3 MacOS
      • 2.4 Docker(推荐)
    • 3、UI界面界面简单使用
      • 3.1 新建存储桶
      • 3.2 添加访问规则
      • 3.3 上传文件
      • 3.4 访问文件
  • 三、SpringBoot整合MinIO
    • 1、引入项目依赖
    • 2、配置文件设置
    • 3、配置类设置
    • 4、工具类整合
      • 4.1 简单整合MinIO上传工具
      • 4.2 MinIO工具类

一、介绍

1、对象存储和分布式文件简介

阿里云OSS对象存储:https://help.aliyun.com/product/31815.html

对象存储服务OSS(Object Storage Service)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。对象存储最大的优势就在于它可以存储大容量的非结构化数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。对于大多数的企业来说,这可以说是最为理想的存储媒介了

分布式文件系统是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连

2、MinIO简介

MinIO 是一个基于 Go 实现的高性能、兼容 S3 协议的对象存储。它采用 GNU AGPL v3 开源协议,项目地址是 https://github.com/minio/minio,官网是 https://min.io,中文官网是www.minio.org.cn
它适合存储海量的非结构化的数据,例如说图片、音频、视频等常见文件,备份数据、容器、虚拟机镜像等等,小到 1 KB,大到 5 TB 都可以支持。

3、MinIO特点

minio原理和使用

  • 高性能:作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GG/s的写速率

  • 可扩容:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心

  • 云原生:容器化、基于K8S的编排、多租户支持

  • Amazon S3兼容:Minio使用Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK和AWS CLI访问Minio服务器。

  • 可对接后端存储: 除了Minio自己的文件系统,还支持DAS、 JBODs、NAS、Google云存储和Azure Blob存储。

  • SDK支持: 基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持

  • Lambda计算: Minio服务器通过其兼容AWS SNS / SQS的事件通知服务触发Lambda功能。支持的目标是消息队列,如Kafka,NATS,AMQP,MQTT,Webhooks以及Elasticsearch,Redis,Postgres和MySQL等数据库。

  • 有操作页面

  • 功能简单: 这一设计原则让MinIO不容易出错、更快启动

  • 支持纠删码:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据

    纠删码是一种用来重建丢失或损坏数据的数学算法。MinIO 使用 Reed-Solomon 码将需要存储的对象切分为可变数据块和奇偶校验块。例如,在由 12 个驱动器构成的存储架构中,对象分片范围可以是 6 个数据块、6 个奇偶校验块到 10 个数据块、2 个奇偶校验块。

二、MinIO安装入门

1、介绍

中文开发文档:http://docs.minio.org.cn/docs/

由于 MinIO 是 Go 写的,所以就一个运行程序,因此安装部署 MinIO 就非常简单。在文档 https://min.io/download 中,有 Windows、Linux、MacOS、Docker、Kubernetes、Source 六种安装方式。

2、快速安装

2.1 Windows

需要在 Windows PowerShell 中执行。

## 国外资源,龟速下载,建议科学上网
setx MINIO_ROOT_USER admin
Invoke-WebRequest -Uri "https://dl.min.io/server/minio/release/windows-amd64/minio.exe" -OutFile "C:\minio.exe"
setx MINIO_ROOT_PASSWORD password
C:\minio.exe server F:\Data --console-address ":9001" ## F:\Data 存储目录;--console-address 是 UI 界面的端口

2.2 Linux

## 国外资源,龟速下载
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server ./minio --console-address ":9001" ## /Users/yunai/minio 存储目录;--console-address 是 UI 界面的端口

2.3 MacOS

## 国外资源,龟速下载
wget https://dl.min.io/server/minio/release/darwin-amd64/minio
chmod +x minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server F:\Data --console-address ":9001" ## F:\Data 存储目录;--console-address 是 UI 界面的端口

2.4 Docker(推荐)

linux单个docker启动

docker pull minio/minio
# --console-address 是 UI 界面的端口
docker run --name minio -p 9000:9000 -p 9001:9001 -d --restart=always -e "MINIO_ACCESS_KEY=admin" -e "MINIO_SECRET_KEY=password" -v "~/minio/data":"/data" -v "~/minio/config":"/root/.minio" minio/minio server /data --console-address ":9001"

linux docker-compose启动(推荐),首先编写docker-compose.yml文件,最后在该目录下docker-compose up -d成功启动

version: '3'
services:minio:image: minio/miniohostname: "minio"ports:- "9000:9000" # api 端口- "9001:9001" # 控制台端口environment:MINIO_ACCESS_KEY: admin    #管理后台用户名MINIO_SECRET_KEY: password #管理后台密码,最小8个字符volumes:- /home/deepsoft/minio/data:/data               #映射当前目录下的data目录至容器内/data目录- /home/deepsoft/minio/config:/root/.minio/     #映射配置目录command: server --console-address ':9001' /data  #指定容器中的目录 /dataprivileged: truerestart: alwayslogging:options:max-size: "50M" # 最大文件上传限制max-file: "10"driver: json-filenetworks:- minio
networks:minio:name: minio

启动成功后打开防火墙,浏览http://localhost:9001即可进入可视化页面

3、UI界面界面简单使用

https://docs.min.io/minio/baremetal/console/minio-console.html#

3.1 新建存储桶

首先登录http://localhost:9001,点击 [Create Bucket] 按钮,新建一个 Bucket 存储桶,用于稍后文件的上传

这里存储桶名称长度必须⾄少为3且不超过63个字符,不得包含⼤写字符或下划线,必须以⼩写字母或数字开头。

3.2 添加访问规则

默认配置下,访问存储桶是需要请求授权的。但是在实际场景下,我们往往希望允许直接访问,此时就需要添加一条 readonly 或readwrite访问规则;或者直接在[Access Policy]直接设置public(不安全)

① 点击右上角的 [Manage] 设置图标,然后选择 [Access Rules] 菜单。

② 点击 [Add Access Rule] 按钮,添加一条 Prefix 为 /或者* ,Access 为 readwrite的规则。

3.3 上传文件

点击 [Upload] 按钮,点击 [Upload File] 选项,选择一个文件上传

3.4 访问文件

文件的访问地址的格式为 <http://127.0.0.1:9000/{bucket}/{name}>,注意是 9000 端口。比如我的是http://192.168.31.34:9000/textbook/yuanshen.png

三、SpringBoot整合MinIO

1、引入项目依赖

注意7.x和8.x版本有一定差异,这里在pom.xml引入最新版

<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.4.0</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、配置文件设置

application.yml中设置

# Tomcat
server:port: 8888spring:# 配置文件上传大小限制servlet:multipart:max-file-size: 100MBmax-request-size: 100MB# Minio配置
minio:url: http://localhost:9000accessKey: minioadminsecretKey: minioadminbucketName: test

3、配置类设置

读取配置文件的信息,这里的存储桶名称可以设置多个

@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig
{/*** 服务地址*/private String url;/*** 用户名*/private String accessKey;/*** 密码*/private String secretKey;/*** 存储桶名称*/private String TextBookBucketName;@Beanpublic MinioClient getMinioClient(){return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();}
}

设置返回类

@Data
public class FileVO {/*** 展示url*/private String previewUrl;/*** 存数据库的uri*/private String uri;
}

4、工具类整合

4.1 简单整合MinIO上传工具

@RestController
@RequestMapping("/file")
public class FileController{@Resourceprivate MinioClient minioClient;@Resourceprivate MinioConfig minioConfig;/*** 上传文件*/@PostMapping("/upload")public CommonResult upload(@RequestParam("file") MultipartFile file) throws Exception {//可以直接原来的文件名,也可以自定义//String originalFilename = file.getOriginalFilename();// 文件名String originalFilename = file.getOriginalFilename();// 新的文件名 = 时间戳.后缀名String fileName = System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf("."));// 上传minioClient.putObject(PutObjectArgs.builder()// 存储桶.bucket(minioConfig.getTextBookBucketName())// 文件名 ,可以添加路径,会自动创建.object(fileName)// 文件内容.stream(file.getInputStream(), file.getSize(), -1)// 文件类型.contentType(file.getContentType()).build());FileVO fileVO = new FileVO();fileVO.setUri("/" + minioConfig.getTextBookBucketName()+ "/" + fileName);fileVO.setPreviewUrl(minioConfig.getUrl()+"/" + fileVO.getUri());// 拼接路径return new CommonResult(fileVO);}/*** 删除文件*/@DeleteMapping("/delete")public void delete(@RequestParam("path") String path) throws Exception {minioClient.removeObject(RemoveObjectArgs.builder()// 存储桶.bucket(minioConfig.getTextBookBucketName())// 文件名,可以文件名.object(path).build());}
}

4.2 MinIO工具类

首先创建工具类,这里很多操作都在工具类里了,需要用到的地方通过MinioUtils.xxx()方法调用即可。另外,工具类中传递的endpoint、bucketName、accessKey、ecretKey等参数,都是在minio后台可以拿到的,没有的话也可以自己设置,同时要保证后台的存储桶是开放的,默认代码创建的都是私有的

@Slf4j
public class MinIOUtils {private static MinioClient minioClient;private static String endpoint;private static String bucketName;private static String accessKey;private static String secretKey;private static Integer imgSize;private static Integer fileSize;private static final String SEPARATOR = "/";public MinIOUtils() {}public MinIOUtils(String endpoint, String bucketName, String accessKey, String secretKey, Integer imgSize, Integer fileSize) {MinIOUtils.endpoint = endpoint;MinIOUtils.bucketName = bucketName;MinIOUtils.accessKey = accessKey;MinIOUtils.secretKey = secretKey;MinIOUtils.imgSize = imgSize;MinIOUtils.fileSize = fileSize;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工具类]>>>> 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 {return minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());}/*** 获得所有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) {log.error("[Minio工具类]>>>> 判断文件是否存在, 异常:", 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) {log.error("[Minio工具类]>>>> 判断文件夹是否存在,异常:", 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 ObjectWriteResponse uploadFile(String bucketName, String objectName,String fileName) throws Exception {return minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());}/*** 通过流上传文件** @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());}/*** 创建文件夹或目录* @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("[Minio工具类]>>>> 批量删除文件,异常:", 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  ******************************/}

创建controller层,这里要首先初始化静态类才能使用

@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {@Resourceprivate MinioClient minioClient;@Autowiredprivate MinioConfig minioConfig;@PostConstructpublic void initializeMinIO(){// 新建minIOnew MinIOUtils(minioConfig.getUrl(),minioConfig.getTextBookBucketName(),minioConfig.getAccessKey(),minioConfig.getSecretKey(),1000,1000);}/*** 上传文件*/@PostMapping("/resourceFileUpload")public CommonResult resourceFileUpload(@RequestParam("dirPath")String dirPath,@RequestParam("file") MultipartFile file) throws Exception {if(file == null){return new CommonResult<>("参数错误");}// 路径String path;if(StringUtils.isBlank(dirPath)){path = file.getOriginalFilename();}else{path = dirPath + "/" + file.getOriginalFilename();}// 上传MinIOUtils.uploadFile(minioConfig.getTextBookBucketName(), file, path, file.getContentType());FileVO fileVO = new FileVO();fileVO.setUri("/" + minioConfig.getTextBookBucketName()+ "/" + path);fileVO.setPreviewUrl(minioConfig.getUrl()+"/" + fileVO.getUri());log.info("文件上传成功!");return new CommonResult<>(fileVO);}
}

参考文章

SpringBoot+Minio搭建

16 分钟搭建高性能的文件服务器

MinIO分布式文件服务器搭建与入门相关推荐

  1. 【tool】企业级开源分布式文件服务器搭建(FastDFS)

    项目介绍 FastDFS介绍 FastDFS是一个以C语言开发的开源轻量级分布式文件系统,由阿里巴巴开发并开源.它对文件进行管理,功能包括:文件存储.文件同步.文件访问(上传.下载)等.解决了大容量存 ...

  2. linux部署Minio(分布式文件服务器)

    MinIO 是一款高性能.分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件.即X86等低成本机器也能够很好的运行MinIO. MinIO与传统的存储和其他的对象存储不同的是:它 ...

  3. Minio分布式集群搭建

    一.分布式Minio快速入门         分布式Minio可以让你将多块硬盘(甚至在不同的机器上)组成一个对象存储服务.由于硬盘分布在不同的节点上,分布式Minio避免了单点故障. 1.分布式Mi ...

  4. minio分布式搭建_分布式存储Minio集群环境搭建

    MinIO 分布式集群搭建 分布式 Minio 可以让你将多块硬盘(甚至在不同的机器上)组成一个对象存储服务.由于硬盘分布在不同的节点上,分布式 Minio 避免了单点故障. Minio 分布式模式可 ...

  5. Hadoop入门基础教程 Hadoop之完全分布式环境搭建

    上一篇我们完成了Hadoop伪分布式环境的搭建,伪分布式模式也叫单节点集群模式, NameNode.SecondaryNameNode.DataNode.JobTracker.TaskTracker所 ...

  6. MinIO纠错码、分布式MinIO集群搭建及启动

    文章目录 前言 一.MinIO纠删码 二.分布式集群部署 1.分布式存储可靠性常用的方法 2.分布式MinIO 3.分布式MinIO集群搭建 3.1 下载MinIO 3.2 为每一台虚拟机创建目录并上 ...

  7. 手把手教你搭建MinIO分布式集群

    手把手教你搭建MinIO分布式集群 要求: MinIO集群规格:2节点2个磁盘. Minio域名:test_minio.com 数据目录:/opt/minio/data1,/opt/minio/dat ...

  8. Minio分布式集群搭建部署

    分布式 Minio 可以让你将多块硬盘或者多台服务器组成一个对象存储服务.由于硬盘分布在不同的节点上,分布式 Minio 避免了单点故障.Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你 ...

  9. 烂泥:文件服务器搭建与使用详解,minio文件服务器搭建(单机版)

    minio服务端搭建 mkdir /usr/local/minio mkdir /usr/local/minio/etc mkdir /usr/local/minio/data cd /usr/loc ...

  10. minio分布式集群搭建完全教程(纠删码,数据恢复)

    minio存储 单机测试 minio对象存储: 编译安装: minio 服务器安装: git clone https://gitee.com/mirrors/minio.git cd minio go ...

最新文章

  1. 【每日DP】day13、P3147 [USACO16OPEN]262144 (区间DP,2048游戏)难度⭐⭐⭐★
  2. 让你的 conda “回滚”到以前版本的环境
  3. webpack打包HTML配置自动,十三、HtmlWebpackPlugin的使用 ------- 2019-04-25
  4. 使用阿里云OSS上传文件
  5. hdu 2112 HDU Today 最短路(Dijkstra算法)
  6. java配置运行环境和配置
  7. Java笔记-多线程协调及ReentrantLock的使用
  8. 继京东27亿买饭店之后 头条要花90亿买广场?张一鸣曾表示年轻人应住市区
  9. EventBus全面讲解和案例
  10. 05 基本数据类型+五大数据类型
  11. Lightroom Classic 教程,如何在 Lightroom 中裁剪并修齐照片?
  12. 如何通过OWA登录界面修改域用户的密码
  13. dll文件保存到服务器,dll是什么文件?dll文件怎么打开?
  14. hⅰgh怎么读音发音英语_英语发音规则---gh
  15. 笔记(五)Home Assistant Lovelace UI基本用法
  16. python学习笔记六
  17. 无为的生活中有所感悟的一篇劝勉高考生的文章
  18. 19 | 三方协议怎么签?
  19. python中,Microsoft Visual C++ 14.0 or greater is required问题解决方案
  20. lsdyna如何设置set中的node_list_如何使用定速巡航功能 图文解说一看就会!

热门文章

  1. python基础题目练习,购买猕猴桃
  2. 基站定位(Google API)
  3. 塑料制品生产的工艺流程
  4. oracle表空间配额和unlimited tablespace权限
  5. 2023届计算机保研面试基础专业问题(数据结构、算法、计算机语言、计算机网络、数据库、操作系统、数学)
  6. 什么是黑盒测试?它的常用方法有哪些?
  7. java 假币问题_假币问题-题解(Java代码)
  8. linux系统修改启动logo的步骤
  9. 河北农业大学能不能学计算机,河北农业大学(专业学位)计算机技术考研难吗
  10. 云计算的特点,主要有哪些?