Spring Boot 整合 Minio

devtools/2024/11/20 18:51:14/

一、导入依赖

<!-- MinIO 客户端 -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.7</version>
</dependency><!-- OkHttp 是一个高效的网络库 -->
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version>
</dependency>

二、添加yaml配置

server:port: 3333spring:servlet:multipart:max-request-size: 200MBmax-file-size: 200MBminio:url: http://127.0.0.1:19000 #换成自己的minio服务端地址accessKey: minioadmin # 用户名secretKey: minioadmin # 密码bucketName: demo  # bucketName指的就是之前创建的MinIO桶Bucket

 三、创建配置类

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @ClassName MinioConfig* @Description 文件* @Date 2024/9/21 10:21*/
@Data
@Configuration
public class MinioConfig {/** 访问地址 */@Value("${minio.url}")private String endpoint;/** 唯一标识账户 */@Value("${minio.accessKey}")private String accessKey;/** 账户密码 */@Value("${minio.secretKey}")private String secretKey;/** 默认储存桶 */@Value("${minio.bucketName}")private String bucketName;/*** 标记此方法为一个 Bean,Spring 会在上下文中管理这个 Bean*/@Beanpublic MinioClient minioClient(){使用 MinioClient 的构建器模式创建一个 MinioClient 实例return MinioClient.builder()//设置 Minio 服务的端点地址.endpoint(endpoint)// 设置访问 Minio 服务所需的访问密钥和秘密密钥.credentials(accessKey, secretKey)// 构建并返回 MinioClient 实例.build();}
}

四、创建工具类

java">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.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;/*** Minio工具类*/
@Log4j2
@Component
@RequiredArgsConstructor
public class MinioUtils {/*** 定义一个私有的、不可变的 MinioClient 实例变量 minioClient*/private final MinioClient minioClient;/*** 启动SpringBoot容器的时候初始化Bucket(桶)* 如果没有Bucket(桶)则创建** @param bucketName Bucket(桶)名称*/@SneakyThrows(Exception.class)private void createBucket(String bucketName) {if (!bucketExists(bucketName)) {//使用 minioClient 的 makeBucket 方法创建一个新的桶。//MakeBucketArgs.builder().bucket(bucketName).build() 创建了一个请求参数对象,指定了要创建的桶的名称。minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 判断Bucket(桶)是否存在** @param bucketName Bucket(桶)名称* @return true:存在  false:不存在*/@SneakyThrows(Exception.class)public boolean bucketExists(String bucketName) {//调用 minioClient 对象的 bucketExists 方法,检查指定的桶是否存在。//BucketExistsArgs.builder().bucket(bucketName).build() 创建了一个请求参数对象,用于传递桶名称。return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 获得Bucket(桶)的策略** @param bucketName Bucket(桶)名称* @return 指定桶的访问策略*/@SneakyThrows(Exception.class)public String getBucketPolicy(String bucketName) {//通过 MinIO 客户端 API 获取指定桶的访问策略。return minioClient.getBucketPolicy(//使用构建者模式创建一个 GetBucketPolicyArgs 对象,以便设置请求参数。GetBucketPolicyArgs.builder()//设置要获取策略的桶的名称。.bucket(bucketName)//构建并返回 GetBucketPolicyArgs 对象,该对象将作为参数传递给 getBucketPolicy 方法。.build());}/*** 获得所有Bucket(桶)列表** @return 所有桶的列表*/@SneakyThrows(Exception.class)public List<Bucket> getAllBuckets() {//调用 MinIO 客户端的 listBuckets 方法,返回当前用户拥有的所有桶的列表。return minioClient.listBuckets();}/*** 根据bucketName获取其相关信息** @param bucketName Bucket(桶)名称* @return Optional<Bucket>*/@SneakyThrows(Exception.class)public Optional<Bucket> getBucket(String bucketName) {//查找并返回指定名称的桶,如果不存在,则返回 Optional.empty()。//调用 getAllBuckets() 方法获取所有桶,然后使用 Java 8 的 Stream API 进行处理。//filter 方法用于筛选出名称与 bucketName 匹配的桶。//findFirst 方法用于返回第一个匹配的桶,返回值是一个 Optional<Bucket>,表示可能存在的桶。return getAllBuckets().stream().filter(bucket -> bucket.name().equals(bucketName)).findFirst();}/*** 根据bucketName删除Bucket(桶)  true:删除成功  false:删除失败** @param bucketName Bucket(桶)名称*/@SneakyThrows(Exception.class)public void removeBucket(String bucketName) {//调用 MinIO 客户端的 removeBucket 方法,通过构建 RemoveBucketArgs 对象来指定要删除的桶名。minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());}/*** 判断文件是否存在** @param bucketName Bucket(桶)名称* @param objectName 文件名称* @return true:存在  false:不存在*/public boolean isObjectExist(String bucketName, String objectName) {boolean exist = true;try {//调用 MinIO 客户端的 statObject 方法,构建 StatObjectArgs 对象以检查对象的状态。如果对象存在,该方法不会抛出异常。minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());} catch (Exception e) {log.error("【Minio工具类】>>>> 判断文件是否存在, 异常:", e);exist = false;}return exist;}/*** 判断文件夹是否存在** @param bucketName Bucket(桶)名称* @param objectName 文件夹名称* @return true:存在  false:不存在*/public boolean isFolderExit(String bucketName, String objectName) {boolean exist = true;try {//使用 listObjects 方法列出指定桶中以 objectName 为前缀的对象。recursive(false) 表示不进行递归查找。Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());//遍历结果,检查每个 Item 是否为目录且名称与 objectName 匹配。如果找到匹配的目录,将 exist 设为 true。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 列表*/@SneakyThrows(Exception.class)public List<Item> getAllObjectsByPrefix(String bucketName,String prefix,boolean recursive) {List<Item> list = new ArrayList<>();// 创建一个空的 Item 列表,用于存储结果Iterable<Result<Item>> objectsIterator = minioClient.listObjects(// 获取指定桶中以 prefix 开头的对象列表ListObjectsArgs.builder()// 构建 ListObjectsArgs 对象.bucket(bucketName)// 设置桶名.prefix(prefix)// 设置对象前缀.recursive(recursive)// 设置是否递归查找.build());// 构建参数if (objectsIterator != null) { // 检查对象迭代器是否不为 nullfor (Result<Item> o : objectsIterator) { // 遍历迭代器中的每个结果Item item = o.get();// 获取当前结果中的 Item 对象list.add(item);// 将 Item 添加到列表中}}return list;// 返回包含所有找到的 Item 的列表}/*** 获取文件流** @param bucketName 存储桶* @param objectName 文件名* @return 二进制流*/@SneakyThrows(Exception.class)public InputStream getObject(String bucketName, String objectName) {return minioClient.getObject(// 调用 MinIO 客户端的 getObject 方法,获取指定对象的输入流GetObjectArgs.builder()// 构建 GetObjectArgs 对象.bucket(bucketName)// 设置桶名.object(objectName)// 设置对象名.build()); // 构建参数并调用 getObject}/*** 断点下载** @param bucketName 存储桶* @param objectName 文件名称* @param offset     起始字节的位置* @param length     要读取的长度* @return 二进制流*/@SneakyThrows(Exception.class)public InputStream getObject(String bucketName, String objectName, long offset, long length) {return minioClient.getObject(// 调用 MinIO 客户端的 getObject 方法,获取指定对象的输入流GetObjectArgs.builder()// 构建 GetObjectArgs 对象.bucket(bucketName) // 设置桶名.object(objectName)// 设置对象名.offset(offset)// 设置读取的起始偏移量.length(length) // 设置要读取的字节长度.build());// 构建参数并调用 getObject}/*** 获取路径下文件列表** @param bucketName 存储桶* @param prefix     文件名称* @param recursive  是否递归查找,false:模拟文件夹结构查找* @return 二进制流*/public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) {return minioClient.listObjects(// 调用 MinIO 客户端的 listObjects 方法,返回对象列表ListObjectsArgs.builder() // 构建 ListObjectsArgs 对象.bucket(bucketName) // 设置桶名.prefix(prefix)// 设置对象前缀.recursive(recursive)// 设置是否递归查找.build());// 构建参数并调用 listObjects}/*** 使用MultipartFile进行文件上传** @param bucketName  存储桶* @param file        文件名* @param objectName  对象名* @param contentType 类型* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) {InputStream inputStream = file.getInputStream(); // 获取文件的输入流,以便上传return minioClient.putObject(// 调用 MinIO 客户端的 putObject 方法,上传文件PutObjectArgs.builder()// 构建 PutObjectArgs 对象.bucket(bucketName)// 设置桶名.object(objectName)// 设置对象名.contentType(contentType)// 设置文件的内容类型.stream(inputStream, inputStream.available(), -1) // 设置输入流及其可用字节数,-1 表示不限制.build());// 构建参数并调用 putObject}/*** 图片上传** @param bucketName  存储桶* @param imageBase64 图像的 Base64 编码字符串* @param imageName   接收图像的原始名称* @return*/public ObjectWriteResponse uploadImage(String bucketName, String imageBase64, String imageName) {if (!StringUtils.isEmpty(imageBase64)) {// 检查 Base64 字符串是否非空InputStream in = base64ToInputStream(imageBase64);// 将 Base64 字符串转换为输入流String newName = System.currentTimeMillis() + "_" + imageName + ".jpg";// 生成新文件名,包含当前时间戳和原始名称String year = String.valueOf(new Date().getYear());// 获取当前年份String month = String.valueOf(new Date().getMonth());// 获取当前月份return uploadFile(bucketName, year + "/" + month + "/" + newName, in); // 调用 uploadFile 方法上传文件,路径为 年/月/新文件名}return null;// 如果 Base64 字符串为空,返回 null}// BASE64Decoder在jdk8以上的版本移除了,报错最简单解决换成jdk8就行了public static InputStream base64ToInputStream(String base64) {ByteArrayInputStream stream = null;// 声明 ByteArrayInputStream 变量,用于保存字节流try {// 使用 BASE64Decoder 解码 Base64 字符串并去除首尾空格,得到字节数组byte[] bytes = new BASE64Decoder().decodeBuffer(base64.trim());// 将字节数组转换为 ByteArrayInputStream,以便返回stream = new ByteArrayInputStream(bytes);} catch (Exception e) {e.printStackTrace();}return stream;// 返回字节输入流,如果出错则返回 null}/*** 上传本地文件** @param bucketName 存储桶* @param objectName 对象名称* @param fileName   本地文件路径* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) {return minioClient.uploadObject(// 调用 MinIO 客户端的 uploadObject 方法进行文件上传UploadObjectArgs.builder()// 使用 UploadObjectArgs 构建上传参数.bucket(bucketName)// 设置存储桶名称.object(objectName)// 设置对象名称(在存储桶中的文件名).filename(fileName)// 设置要上传的本地文件名.build()); // 构建并返回 UploadObjectArgs 对象,然后执行上传}/*** 通过流上传文件** @param bucketName  存储桶* @param objectName  文件对象* @param inputStream 文件流* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) {return minioClient.putObject( // 调用 MinIO 客户端的 putObject 方法进行文件上传PutObjectArgs.builder()// 使用 PutObjectArgs 构建上传参数.bucket(bucketName)// 设置存储桶名称.object(objectName)// 设置对象名称(在存储桶中的文件名).stream(inputStream, inputStream.available(), -1)// 设置输入流及其大小,-1 表示使用默认值.build()); // 构建并返回 PutObjectArgs 对象,然后执行上传}/*** 创建文件夹或目录** @param bucketName 存储桶* @param objectName 目录路径* @return*/@SneakyThrows(Exception.class)public ObjectWriteResponse createDir(String bucketName, String objectName) {return minioClient.putObject(// 调用 MinIO 客户端的 putObject 方法进行上传PutObjectArgs.builder()// 使用 PutObjectArgs 构建上传参数.bucket(bucketName)// 设置存储桶名称.object(objectName) // 设置对象名称(在存储桶中的“目录”名称).stream(new ByteArrayInputStream(new byte[]{}), 0, -1)// 使用空的字节数组流,表示创建一个空对象,0 表示长度,-1 表示使用默认值.build());// 构建并返回 PutObjectArgs 对象,然后执行上传}/*** 获取文件信息, 如果抛出异常则说明文件不存在** @param bucketName 存储桶* @param objectName 文件名称* @return*/@SneakyThrows(Exception.class)public String getFileStatusInfo(String bucketName, String objectName) {return minioClient.statObject( // 调用 MinIO 客户端的 statObject 方法获取对象状态信息StatObjectArgs.builder() // 使用 StatObjectArgs 构建请求参数.bucket(bucketName) // 设置存储桶名称.object(objectName)// 设置对象名称.build() // 构建并返回 StatObjectArgs 对象).toString();// 调用 toString 方法,将获取的状态信息转换为字符串并返回}/*** 拷贝文件** @param bucketName    存储桶* @param objectName    文件名* @param srcBucketName 目标存储桶* @param srcObjectName 目标文件名*/@SneakyThrows(Exception.class)public ObjectWriteResponse copyFile(String bucketName, String objectName, String srcBucketName, String srcObjectName) {return minioClient.copyObject(// 调用 MinIO 客户端的 copyObject 方法进行对象复制CopyObjectArgs.builder()// 使用 CopyObjectArgs 构建复制参数.source(CopySource.builder()// 设置复制源.bucket(bucketName) // 源存储桶名称.object(objectName)// 源对象名称.build())// 构建并返回 CopySource 对象.bucket(srcBucketName)// 设置目标存储桶名称.object(srcObjectName) // 设置目标对象名称.build() // 构建并返回 CopyObjectArgs 对象);// 执行对象复制并返回 ObjectWriteResponse}/*** 删除文件** @param bucketName 存储桶* @param objectName 文件名称*/@SneakyThrows(Exception.class)public void removeFile(String bucketName, String objectName) {minioClient.removeObject(// 调用 MinIO 客户端的 removeObject 方法以删除指定对象RemoveObjectArgs.builder()// 使用 RemoveObjectArgs 构建删除参数.bucket(bucketName) // 设置要删除对象的存储桶名称.object(objectName)// 设置要删除的对象名称.build());// 构建并返回 RemoveObjectArgs 对象,并执行删除操作}/*** 批量删除文件** @param bucketName 存储桶* @param keys       需要删除的文件列表* @return*/public void removeFiles(String bucketName, List<String> keys) {List<DeleteObject> objects = new LinkedList<>();// 创建一个 LinkedList 用于存储待删除对象keys.forEach(s -> {// 遍历对象键列表objects.add(new DeleteObject(s)); // 将每个键封装成 DeleteObject 并添加到列表中try {// 尝试执行删除操作removeFile(bucketName, s);// 调用 removeFile 方法删除指定对象} catch (Exception e) {log.error("【Minio工具类】>>>> 批量删除文件,异常:", e);}});}/*** 获取文件外链* 使用了 .expiry(expires) 方法,指定了预签名 URL 的过期时间。* 适用于需要限制 URL 有效期的场景。* 适用于需要设定过期时间的情况** @param bucketName 存储桶* @param objectName 文件名* @param expires    过期时间 <=7 秒 (外链有效时间(单位:秒))* @return url*/@SneakyThrows(Exception.class)public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) {// 构建获取预签名对象 URL 的参数GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()// 使用 GetPresignedObjectUrlArgs 的构建器.expiry(expires)// 设置 URL 的过期时间(以秒为单位).bucket(bucketName) // 设置存储桶名称.object(objectName)// 设置对象名称.build();// 构建并返回 GetPresignedObjectUrlArgs 对象return minioClient.getPresignedObjectUrl(args);// 调用 MinIO 客户端获取预签名 URL 并返回}/*** 获得文件外链* 使用了 .method(Method.GET) 方法,指定了 HTTP 请求方法为 GET。* 适用于需要明确请求类型的场景,但没有设置过期时间。* 适用于无需设定过期时间的访问** @param bucketName* @param objectName* @return url*/@SneakyThrows(Exception.class)public String getPresignedObjectUrl(String bucketName, String objectName) {// 构建获取预签名对象 URL 的参数GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()// 使用 GetPresignedObjectUrlArgs 的构建器.bucket(bucketName) // 设置存储桶名称.object(objectName)// 设置对象名称.method(Method.GET)// 设置对象名称.build();// 构建并返回 GetPresignedObjectUrlArgs 对象return minioClient.getPresignedObjectUrl(args);// 调用 MinIO 客户端获取预签名 URL 并返回}/*** 将URLDecoder编码转成UTF8** @param str* @return* @throws UnsupportedEncodingException*/public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {// 将字符串中的不合法 URL 编码转换为合法编码String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");// 使用正则表达式,将不符合 URL 编码规范的百分号(%)替换为 %25return URLDecoder.decode(url, "UTF-8");// 使用 URLDecoder 解码字符串,返回 UTF-8 编码的结果}}

一些解释

1.@Log4j2:这是一个 Lombok 提供的注解,用于自动生成一个 Log4j2 日志记录器。使用此注解后,你可以直接调用 log 对象来记录日志,而无需手动创建日志实例。

2.@Component:这是 Spring 框架的注解,表示该类是一个 Spring 组件,Spring 会自动检测并将其注册为一个 Bean。可以用于自动装配和依赖注入。

3.@RequiredArgsConstructor:也是 Lombok 提供的注解,自动生成一个构造函数,该构造函数接受所有被 final 修饰的字段或带有 @NonNull 注解的字段。这简化了依赖注入的过程,使代码更加简洁。

4.@SneakyThrows(Exception.class) :是 Lombok 提供的一个注解,用于在方法上处理检查性异常。

作用:

自动处理异常:在使用 @SneakyThrows 注解的方法中,如果抛出任何类型的检查性异常(即编译器要求显式捕获或声明的异常),Lombok 会自动将其包装成运行时异常(RuntimeException),从而避免了需要显式捕获或声明这些异常的麻烦。

注意事项:

尽管 @SneakyThrows 使代码更简洁,但在调试或异常处理时,要小心隐式处理异常可能导致的问题。如果可能,考虑在应用程序中适当处理异常,以提高代码的可读性和可维护性。

5‌‌.Optional‌是Java 8引入的一个新的容器对象,它提供了非常丰富的API,主要是为了解决空指针异常的问题。Optional类允许你创建一个可能为null的值的容器,从而避免了直接使用null值可能导致的空指针异常。它提供了多种方法来操作这个容器,包括判断值是否存在(isPresent())、获取值(get())、转换值(map())、过滤值(filter())等。

6.OkHttp 是一个高效的 HTTP 客户端库,用于发送和接收网络请求,它支持同步和异步调用,自动处理常见的网络问题如重定向、HTTPS握手和连接池。它广泛用于 Android 和 Java 应用程序中,以提高网络通信的效率和稳定性。

五、创建controller层

java">/*** Minio控制层*/
@Log4j2
@RestController
@RequestMapping("/minio")
public class MinioController {@Autowiredprivate MinioUtils minioUtils;@Autowiredprivate MinioConfig minioConfig;/*** 文件上传** @param file*/@PostMapping("/upload")public String upload(@RequestParam("file") MultipartFile file) {try {//文件名String fileName = file.getOriginalFilename();String newFileName = System.currentTimeMillis() + "." + StringUtils.substringAfterLast(fileName, ".");//类型String contentType = file.getContentType();minioUtils.uploadFile(minioConfig.getBucketName(), file, newFileName, contentType);return "上传成功";} catch (Exception e) {log.error("上传失败",e);return "上传失败";}}/*** 删除** @param fileName*/@DeleteMapping("/")public void delete(@RequestParam("fileName") String fileName) {minioUtils.removeFile(minioConfig.getBucketName(), fileName);}/*** 获取文件信息** @param fileName* @return*/@GetMapping("/info")public String getFileStatusInfo(@RequestParam("fileName") String fileName) {return minioUtils.getFileStatusInfo(minioConfig.getBucketName(), fileName);}/*** 获取文件外链** @param fileName* @return*/@GetMapping("/url")public String getPresignedObjectUrl(@RequestParam("fileName") String fileName) {return minioUtils.getPresignedObjectUrl(minioConfig.getBucketName(), fileName);}/*** 文件下载** @param fileName* @param response*/@GetMapping("/download")public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) {try {// 从 MinIO 获取指定的对象(文件)的输入流InputStream fileInputStream = minioUtils.getObject(minioConfig.getBucketName(), fileName);// 设置响应头,指明这是一个附件下载,并指定下载文件的名称response.setHeader("Content-Disposition", "attachment;filename=" + fileName);// 设置响应的内容类型为强制下载response.setContentType("application/force-download");// 设置响应的字符编码为 UTF-8response.setCharacterEncoding("UTF-8");// 将输入流中的文件内容复制到响应输出流中IOUtils.copy(fileInputStream, response.getOutputStream());response.flushBuffer(); // 刷新响应输出流} catch (Exception e) {log.error("下载失败");System.out.println(e);}}


http://www.ppmy.cn/devtools/135551.html

相关文章

mysql 的乐观锁和 mvcc 是一回事吗

MySQL 的乐观锁和 MVCC&#xff08;多版本并发控制&#xff09;是两个不同的概念&#xff0c;尽管它们都涉及到并发控制和数据的一致性&#xff0c;但在设计目的和实现方式上存在本质区别。 1. 乐观锁 概念 乐观锁是一种用于解决并发更新冲突的控制机制。它假设数据在大部分情况…

【知识堂】奇点

“奇点”在不同领域有不同的含义。以下是几个常见的定义&#xff1a; 1. 数学中的奇点 在数学中&#xff0c;“奇点”通常指的是一个函数、曲线或空间中表现出不正常行为的点。在更详细的层面&#xff0c;奇点是指某个数学对象在该点附近无法用常规的数学工具来描述或者解析。…

【一句话经验】亚马逊云EC2 ubuntu24.04.1开启ROOT登录Permission denied (publickey)

按照常规的方法SSH登录会一直报错&#xff1a; Permission denied (publickey) 因为亚马逊云的默认配置不是在/etc/ssh/sshd_config&#xff0c;而是在引入的文件里了&#xff0c;所以在instance控制台输入这行命令来解除登录限制&#xff1a; sudo sed -i s/^PasswordAuthe…

C++Qt编写校园导航系统

系统要求 &#xff08;1&#xff09;有界面&#xff0c; 显示各个景点以及景点之间的路径和路径长度&#xff0c;边上有几个按钮和文本框&#xff0c;在文本框中输入遍历的起点&#xff0c;输入就最短路径的起点和终点&#xff0c; 点击按钮后在界面上用不同颜色标注出路径或铺…

Android OpenGL ES详解——Renderer接口介绍

OpenGL是一个跨平台的图形库&#xff0c;用于渲染2D和3D图形。在Android上&#xff0c;使用OpenGL可以创建高性能的图形应用程序&#xff0c;包括游戏、模拟器、虚拟现实应用等。 Android中使用OpenGL的一般步骤如下&#xff1a; 初始化OpenGL环境&#xff1a;在Activity的onC…

nfs服务器--RHCE

一&#xff0c;简介 NFS&#xff08;Network File System&#xff0c;网络文件系统&#xff09;是FreeBSD支持的文件系统中的一种&#xff0c;它允许网络中的计 算机&#xff08;不同的计算机、不同的操作系统&#xff09;之间通过TCP/IP网络共享资源&#xff0c;主要在unix系…

【SQL】E-R模型(实体-联系模型)

目录 一、介绍 1、实体集 定义和性质 属性 E-R图表示 2. 联系集 定义和性质 属性 E-R图表示 一、介绍 实体-联系数据模型&#xff08;E-R数据模型&#xff09;被开发来方便数据库的设计&#xff0c;它是通过允许定义代表数据库全局逻辑结构的企业模式&#xf…

利用MQTT和SSL/TLS实现数据加密

随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;越来越多的设备接入网络&#xff0c;进行数据交换和处理。在这个过程中&#xff0c;确保数据传输的安全性成为了至关重要的环节。本文将介绍如何利用MQTT协议和SSL/TLS加密技术&#xff0c;构建安全的物联网通…