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

搭建高性能分布式存储-minio

文章目录

    • 搭建高性能分布式存储-minio
      • Docker搭建minio(单机部署+纠删码模式)⭐
      • 创建minio的bucket(桶)⭐
      • SpringBoot+minio项目实战 ⭐
        • 1:导入minio的maven依赖
        • 2:编写MinioProperties.class
        • 3:application.yml:
        • 4:MinioConfig.class配置类
        • 5:ResponseResult(统一响应结果)
        • 6:ResponseType(响应类型枚举类)
        • 7:UploadController.class(上传接口)
        • 8:上传service接口
        • 9:上传service接口实现类:

搭建高性能分布式存储-minio

Docker搭建minio(单机部署+纠删码模式)⭐

  • 1:使用minio Docker镜像,在在单机中使用EC纠删码模式部署,8块盘中启动minio服务:
    • 注意:MINIO_ROOT_PASSWORD初始化默认密码长度必须要达到8位。
docker run --name minio -d \-p 9000:9000 \-p 50000:50000 \-v /mnt/minio/data1:/data1 \-v /mnt/minio/data2:/data2 \-v /mnt/minio/data3:/data3 \-v /mnt/minio/data4:/data4 \-v /mnt/minio/data5:/data5 \-v /mnt/minio/data6:/data6 \-v /mnt/minio/data7:/data7 \-v /mnt/minio/data8:/data8\-e MINIO_ROOT_USER="root" \-e MINIO_ROOT_PASSWORD="12345678" \minio/minio:latest server /data{1...8} --console-address ":50000"
  • 2:访问minio控制台:(http://服务器ip:50000/)

创建minio的bucket(桶)⭐

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

SpringBoot+minio项目实战 ⭐

1:导入minio的maven依赖
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.4.3</version>
</dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version>
</dependency>
2:编写MinioProperties.class
package com.boot.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;/*** 高性能分布式存储-minio属性类** @author youzhengjie* @date 2022/10/28 00:22:48*/
@Component
@Data
@ConfigurationProperties(prefix = "minio")
@EnableConfigurationProperties({MinioProperties.class
})
public class MinioProperties {/*** minio的通信端点(url),和控制台端点不一样*/private String endpoint;/*** 说白了就是minio的帐号*/private String accessKey;/*** 说白了就是minio的密码*/private String secretKey;/*** 指定操作哪一个桶*/private String bucketName;
}
3:application.yml:
  • 注意:记得把下面的配置属性修改成你自己的!
minio:endpoint: http://192.168.184.123:9000access-key: rootsecret-key: 12345678bucket-name: security-jwt2-bucket
4:MinioConfig.class配置类
  • 作用是将MinioClient交给Spring管理,方便后续直接@AutoWired注入即可使用)
package com.boot.config;import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** minio配置类** @author youzhengjie* @date 2022/10/28 00:30:31*/
@Configuration
public class MinioConfig {@Autowiredprivate MinioProperties minioProperties;/*** minio-client bean** @return {@link MinioClient}*/@Beanpublic MinioClient minioClient(){return MinioClient.builder()//配置minio的通信端点(url),和控制台端点不一样.endpoint(minioProperties.getEndpoint())//配置minio的帐号密码.credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey()).build();}}
5:ResponseResult(统一响应结果)
package com.boot.data;import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 统一响应结果*/
@JsonInclude(JsonInclude.Include.NON_NULL) //为null的字段不进行序列化
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult<T> {/*** 响应状态码*/private Integer code;/*** 响应状态码对应的信息提示*/private String msg;/*** 返回给前端的数据*/private T data;public ResponseResult(Integer code, String msg) {this.code = code;this.msg = msg;}public ResponseResult(Integer code, T data) {this.code = code;this.data = data;}}
6:ResponseType(响应类型枚举类)
package com.boot.enums;/*** 响应类型枚举类* @author youzhengjie* @date 2022-09-22 22:47:21*/
public enum ResponseType {/*** 文件操作状态*/IMAGE_UPLOAD_SUCCESS(901,"图片上传成功"),IMAGE_UPLOAD_ERROR(902,"图片上传失败"),FILE_FORMAT_UNSUPPORT(903,"不支持该文件格式,上传失败"),FILE_DELETE_SUCCESS(904,"文件删除成功"),FILE_DELETE_ERROR(905,"文件删除失败"),;private int code;private String message;ResponseType(int code, String message) {this.code = code;this.message = message;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
7:UploadController.class(上传接口)
@RestController
@Api("上传接口")
@RequestMapping(path = "/upload")
@Slf4j
public class UploadController {@Autowired@Qualifier("minioUploadServiceImpl") //指定spring注入的实现类private OssUploadService ossUploadService;/*** 上传头像** @param avatarFile 头像文件(名字一定要和el-upload的:name属性一致)* @return {@link ResponseResult}*/@OperationLog("上传头像")@PostMapping(path = "/avatar")@ApiOperation("上传头像")public ResponseResult uploadAvatar(MultipartFile avatarFile){ResponseResult result = ossUploadService.imageUpload(avatarFile);return result;}/*** 文件删除** @param fileFullName 文件名* @return {@link ResponseResult}*/@OperationLog("文件删除")@DeleteMapping(path = "/fileDelete")@ApiOperation("文件删除")public ResponseResult fileDelete(@RequestParam("fileFullName") String fileFullName){return ossUploadService.fileDelete(fileFullName);}/*** 文件下载** @param fileName 文件名称* @param response 响应*/@GetMapping(path = "/fileDownload")@ApiOperation("文件下载")public void fileDownload(@RequestParam("fileName") String fileName, HttpServletResponse response){ossUploadService.fileDownload(fileName,response);}
}
8:上传service接口
package com.boot.service;import com.boot.data.ResponseResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;/*** oss上传service接口* @author youzhengjie* @date 2022-10-06 23:13:28*/
public interface OssUploadService {/*** oss图片上传* @param imageFile* @return 上传结果*/ResponseResult imageUpload(MultipartFile imageFile);/*** oss文件删除* @param fileFullName 文件全名,** @return 删除结果*/ResponseResult fileDelete(String fileFullName);/*** 文件下载** @param fileName 文件名称* @param response 响应*/default void fileDownload(String fileName, HttpServletResponse response){throw new UnsupportedOperationException("该实现类暂不支持文件下载操作,请切换到其他实现类!");}}
9:上传service接口实现类:
package com.boot.service.impl;import com.boot.config.MinioProperties;
import com.boot.data.ResponseResult;
import com.boot.enums.ResponseType;
import com.boot.service.OssUploadService;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;/*** minio上传服务service impl** @author youzhengjie* @date 2022/10/28 13:59:35*/
@Service("minioUploadServiceImpl")
@Slf4j
public class MinioUploadServiceImpl implements OssUploadService {@Autowiredprivate MinioProperties minioProperties;/*** 注入MinioClient,用于操作minio*/@Autowiredprivate MinioClient minioClient;/*** 检查文件是否是图片类型* @param originalFilename* @return true代表是图片,false则不是图片*/private boolean isImage(String originalFilename){//将文件名全部变小写String lowerOriginalFilename = originalFilename.toLowerCase();return lowerOriginalFilename.endsWith(".jpg") ||lowerOriginalFilename.endsWith(".png") ||lowerOriginalFilename.endsWith(".jpeg");}/*** minio图片上传** @param imageFile 图像文件* @return {@link ResponseResult}*/@Overridepublic ResponseResult imageUpload(MultipartFile imageFile) {//封装响应结果ResponseResult<Object> result = new ResponseResult<>();try {//获取上传前的文件原名String oldFileName = imageFile.getOriginalFilename();//如果不是图片则直接返回if(!isImage(oldFileName)){result.setCode(ResponseType.FILE_FORMAT_UNSUPPORT.getCode());result.setMsg(ResponseType.FILE_FORMAT_UNSUPPORT.getMessage());return result;}//以日期作为目录,每一天的图片都会放到不同的目录下,方便管理String fileDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/"));//UUID文件名String uuidFileName = UUID.randomUUID().toString().replaceAll("-", "");//获取文件后缀名 .jpgString fileSuffix= oldFileName.substring(oldFileName.lastIndexOf("."));//上传到minio中的新的图片文件名String newFileName = new StringBuilder().append(fileDir).append(uuidFileName).append(fileSuffix).toString();//获取文件流InputStream inputStream = imageFile.getInputStream();//获取文件大小long size = imageFile.getSize();//获取内容类型String contentType = imageFile.getContentType();//构建文件上传所需要的东西(PutObjectArgs)PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(minioProperties.getBucketName()).object(newFileName).stream(inputStream, size, -1).contentType(contentType).build();//开始进行minio文件上传minioClient.putObject(putObjectArgs);//获取该上传到minio的文件的url(使外网可以访问)String fileUrl=minioProperties.getEndpoint()+"/"+minioProperties.getBucketName()+"/"+newFileName;result.setCode(ResponseType.IMAGE_UPLOAD_SUCCESS.getCode());result.setMsg(ResponseType.IMAGE_UPLOAD_SUCCESS.getMessage());result.setData(fileUrl);return result;}catch (Exception e){e.printStackTrace();result.setCode(ResponseType.IMAGE_UPLOAD_ERROR.getCode());result.setMsg(ResponseType.IMAGE_UPLOAD_ERROR.getMessage());return result;}}/*** minio文件删除** @param fileFullName 文件全名 。格式例如:2022/10/28/4f74aa358a4548d4860c110ebec3831f.jpg* @return {@link ResponseResult}*/@Overridepublic ResponseResult fileDelete(String fileFullName) {//封装响应结果ResponseResult<Object> result = new ResponseResult<>();try {RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(minioProperties.getBucketName()).object(fileFullName).build();minioClient.removeObject(removeObjectArgs);result.setCode(ResponseType.FILE_DELETE_SUCCESS.getCode());result.setMsg(ResponseType.FILE_DELETE_SUCCESS.getMessage());return result;}catch (Exception e){e.printStackTrace();result.setCode(ResponseType.FILE_DELETE_ERROR.getCode());result.setMsg(ResponseType.FILE_DELETE_ERROR.getMessage());return result;}}/*** minio文件下载。** @param fileName 文件名称 。格式例如:2022/10/28/4f74aa358a4548d4860c110ebec3831f.jpg* @param response 响应*/@Overridepublic void fileDownload(String fileName, HttpServletResponse response) {try {// 获取对象信息StatObjectArgs statObjectArgs = StatObjectArgs.builder().bucket(minioProperties.getBucketName()).object(fileName).build();StatObjectResponse statObject = minioClient.statObject(statObjectArgs);/*** 描述: content-type 指示响应内容的格式* content-disposition 指示如何处理响应内容。* 一般有两种方式:* inline:直接在页面显示-预览* attachment:以附件形式下载-下载*/response.setContentType(statObject.contentType());
//            response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));response.setHeader("Content-Disposition", "attachment; filename=" +URLEncoder.encode(fileName, "UTF-8"));InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minioProperties.getBucketName()).object(fileName).build());IOUtils.copy(inputStream,response.getOutputStream());}catch (Exception e){throw new RuntimeException("文件下载失败");}}
}

相关文章:

搭建高性能分布式存储-minio

文章目录 搭建高性能分布式存储-minioDocker搭建minio&#xff08;单机部署纠删码模式&#xff09;⭐创建minio的bucket&#xff08;桶&#xff09;⭐SpringBootminio项目实战 ⭐1&#xff1a;导入minio的maven依赖2&#xff1a;编写MinioProperties.class3&#xff1a;applica…...

leetCode 137. 只出现一次的数字 II(拓展篇) + 模5加法器 + 真值表(数字电路)

leetCode 137. 只出现一次的数字 II 题解可看我的往期文章 leetCode 137. 只出现一次的数字 II 位运算 模3加法器 真值表&#xff08;数字电路&#xff09; 有限状态机-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/134138112?spm1001.2014.3001.5501…...

docker导致root空间满进入不了系统解决方案

由于docker建立镜像会产生很多缓存文件&#xff0c;最终导致root目录满&#xff0c;进入不了系统 df -h docker默认路径是在/var/lib/docker下 可以通过命令查看docker占用空间 docker system df 如果占用空间太大 可用以下命令清理 $ docker image prune -h Flag shorth…...

uni-app遮罩遮住小程序tabbar

uni-app遮罩遮住小程序tabbar 1、用uni-app自带的方法显示隐藏 //通过弹窗显隐控制导航栏的显示和隐藏 const popupChange e >{if(e.show){//隐藏tabbaruni.hideTabBar()}else{//显示tabbaruni.showTabBar()}state.searchIcon e.show } //通过弹窗显隐控制导航栏的显示和…...

Flink on yarn 加载失败plugins失效问题解决

Flink on yarn 加载失败plugins失效问题解决 flink版本&#xff1a;1.13.6 1. 问题 flink 任务运行在yarn集群,plugins加载失效,导致通过扩展资源获取任务参数失效 2. 问题定位 yarn容器的jar包及插件信息,jar包是正常上传 源码定位 加载plugins入口&#xff0c;TaskMana…...

显卡服务器的特点和优势在哪里

随着科技的发展以及人们对于计算机性能的需求提高&#xff0c;显卡服务器是主要使用图形处理器进行计算和运算&#xff0c;拥有更加强大的计算能力&#xff0c;今天小编就来给大家讲一讲显卡服务器的特点和优势是什么&#xff01; 1.高可靠性&#xff1a;显卡服务器采用高品质的…...

c++设计模式二:原型模式

使用场景&#xff1a;当需要构建多个相同的类对象时&#xff0c;而且该类对象结构较为复杂&#xff0c;如果每个都重新组织构建会很麻烦。 其实&#xff0c;就是写一个拷贝构造函数&#xff0c;或者写一个拷贝每个成员变量的clone()方法。 举例说明&#xff1a;比如一个相亲网站…...

【Qt控件之QMessageBox】详解

Qt控件之QMessageBox 描述基于属性的API富文本和文本格式属性严重程度以及图标和Pixmap属性静态函数API 高级用法默认按钮和退出按钮示例使用场景 描述 QMessageBox类提供了一个模态对话框&#xff0c;用于通知用户或向用户提问并接收答案。 消息框显示一个主要文本以提醒用户…...

SSH安全登录远程主机

SSH服务器简介 SSH即Security SHell的意思&#xff0c;它可以将连线的封包进行加密技术&#xff0c;之后进行传输&#xff0c;因此相当的安全。 SSH是一种协议标准&#xff0c;其目的是实现安全远程登录以及其它安全网络服务。 SSH协定&#xff0c;在预设的状态下&#xff0c;…...

揭秘!产品经理提升效率的秘密武器:10款AI生成PPT工具

AI的爆炸式增长表现令人惊艳&#xff0c;现有的各类AI工具正在重塑各行各业&#xff0c;不同程度地提高人们的工作效率&#xff0c;并有望创造新的职业机会。但是&#xff0c;面对市面上数量众多的AI工具&#xff0c;且每周都会蹦出新的产品&#xff0c;即便是以好奇心富称的产…...

Oracle修改带数据的字段类型

insert into TNW_FUND_SELORG(TFDINFOID,TSOINFOID) select TFD_INFO_ID,TSO_INFO_ID from TFD_SEL_FUNDLINK_TO_OLDFUNDWEB_DB /*修改原字段名*/ ALTER TABLE 表名 RENAME COLUMN 字段名 TO 字段名1; /*添加一个和原字段同名的字段*/ ALTER TABLE 表名 ADD 字段名 VARCHAR…...

WebService接口方式和Restful接口这两者有什么区别和相同点

WebService和RESTful接口都是用于在网络上进行通信和数据交换的技术&#xff0c;但它们在设计和使用上有一些重要的区别和相似之处。 相同点&#xff1a; 基于HTTP协议&#xff1a;无论是WebService还是RESTful接口&#xff0c;它们都是通过HTTP协议进行通信的。 支持多种数据…...

jenkins自动化操作步骤(gitblit)

1、登陆地址&#xff1a; http://xxxxxxxxx.org:xxxx/ admin/xxxx 2、创建任务 选择构建一个maven项目 3、配置 最多只保留一天一个任务 选择git仓库和账号密码 选择代码对应分支 build项&#xff1a; 1&#xff09;使用父项目的pom文件&#xff1a;k56-boot/pom.xml 2&…...

centos中mongodb设置服务自启动并 允许远程IP访问

安装mongodb参考 注意的是配置文件需要把journal设置为true 制作为系统服务 创建MongoDB服务文件。运行以下命令创建服务文件/etc/systemd/system/mongod.service&#xff1a; vi /etc/systemd/system/mongod.service [Unit] DescriptionMongoDB Database Server Documenta…...

实时定位和配送追踪:开发万岳同城外卖APP的关键技术特性

随着生活节奏的不断加快&#xff0c;外卖服务已经成为许多人日常生活中不可或缺的一部分。无论是工作日的午餐&#xff0c;还是周末的家庭聚会&#xff0c;外卖APP已经成为满足各种美食需求的首选方式。然而&#xff0c;同城外卖APP的成功不仅仅取决于美味的食物选择&#xff0…...

数据库强化(3.存储过程)

1.什么是存储过程&#xff1f; 存储过程&#xff08;Stored Procedure&#xff09;是一种在数据库中存储复杂程序&#xff0c;以便外部程序调用的一种数据库对象。MySQL 5.0 版本开始支持存储过程。 它是为了完成特定功能的SQL语句集&#xff0c;经编译创建并保存在数据库中&a…...

雅思小作文笔记

mostly from Simon’s methods and techniques remember the task is describe what you see, not give an opinion. Just write a report.no conclusion, just a summary(the overview) Question type 小作文的题目类型大致如上 Simon所述&#xff0c;在描述数字的时候&…...

Java List Set Map

一、List 1.1 ArrayList 1.2 LinkedList 二、Set 2.1 HashSet 2.2 TreeSet 2.3 LinkedHashSet 三、Map 3.1 HashMap 3.2 TreeMap 3.3 LinkedHashMap 四、对比 类型底层结构重复null值场景备注查询删除新增ListArrayList动态数组可允许快速随机访问元素0(1)0(n)尾部增加0&a…...

【数据结构】数组和字符串(十三):链式字符串的基本操作(串长统计、查找、复制、插入、删除、串拼接)

文章目录 4.3 字符串4.3.1 字符串的定义与存储4.3.2 字符串的基本操作&#xff08;链式存储&#xff09;1. 结构体2. 初始化3. 判空4. 串尾添加5. 打印6. 串长统计7. 查找8. 复制9. 插入10. 删除11. 串拼接12. 销毁13. 主函数14. 代码整合 4.3 字符串 字符串(String)是由零个或…...

Python3 获取当前服务器公网 IP 地址

有同学问我如何使用 Python 获取服务器公网的 IP 地址呢&#xff1f;我测试几个发现&#xff0c;方法有很多&#xff0c;好用的就发现一种&#xff0c;即直接使用 Python 自带的 socket 包。 代码示例&#xff1a; # 获取主机 IP dgram socket.socket(socket.AF_INET, socke…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...