华为云云服务器评测|基于云服务器的minio部署手册
【软件安装版本】【集群安装(是)(否)】
版本
创建人
修改人
创建时间
备注
1.0
jz
jz
2023.9.2
minio华为云耀服务器
一. 部署规划与架构
1. 规划:(集群:网络规划,服务器规划)
安装方式 : 非集群安装
服务器 : 华为云:192.168.0.147
端口号 : A.minio管理界面:9000,33806 B.demo应用:8081
2. 架构(集群:拓扑图)
3. 支撑业务
小文件服务器:图片,文件,视频的存储管理。
二. 运行环境安装
硬件
华为云耀服务器,2核2G 3m网络
2. 操作系统
为了springboot的demo运行,需要提前安装好Java运行环境。这里不再重述
3. 环境配置
软件安装路径:/opt/software/minio
mkdir -p /opt/software/minio
数据文件路径:/opt/data/minio
日志文件路径: /opt/data/minio/logs
mkdir -p /opt/data/minio/logs
三. 单机部署步骤
安装包获取与安装
下载地址;https://min.io/download#/linux
cd /opt/software/minio/
wget https://dl.min.io/server/minio/release/linux-amd64/minio
安装成功:
查看下载文件:
2. 配置修改
# 启动的时候看提示 新版本
MINIO_ROOT_USER=username
MINIO_ROOT_PASSWORD=password
# 如果MinIO版本比较旧,修改用户名密码为
MINIO_ACCESS_KEY=username
MINIO_SECRET_KEY=password
注意:创建的文件是个新文件。
修改环境变量
增加两行
export MINIO_ROOT_USER= username
export MINIO_ROOT_PASSWORD= password
# 如果MinIO版本比较旧,修改用户名密码为
e xport MINIO_ACCESS_KEY=username
e xport MINIO_SECRET_KEY=password
配置文件生效
3. 检测依赖环境是否就绪
4.安装
赋予文件安装权限:
以守护进程方式启动,指定端口为9000固定端口,数据文件路径/opt/data/minio/
nohup /opt/software /minio/minio server --console-address :33806 --address 0.0.0.0:9 000 / opt / data/ minio > /opt/data/minio/logs/minio.log &
ps -ef |grep minio
more minio.log
Minio启动成功
5. 云服务器开通公网端口号:9000
登录华为云账号:https://auth.huaweicloud.com/
配置到服务器
进入控制台,更改安全组
选择刚才创建的安全组
验证
浏览器输入:公网ip:9000
会自动跳转到33806端口上。
用username/password登录
四.应用部署
1. 安装java
请自行安装jdk文件,保证java -version可用。
2.编写项目
Pom文件中增加配置
<!--MinIO JAVA SDK--> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>${minio.version}</version> </dependency>
Yaml中增加配置
minio: endpoint: http://192.168.0.213:9000 #MinIO服务所在地址 bucketName: mall #存储桶名称 bucketImg: img # 图片桶 bucketVideo: video # 视频桶 accessKey: fileadmin #访问的key secretKey: fileadmin #访问的秘钥
编写工具类
package top.fairy.global.globalfairytoppi4j.utils; import io.minio.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import top.fairy.global.globalfairytoppi4j.beans.ResultEntity; import top.fairy.global.globalfairytoppi4j.config.MinioClientConfig; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Map; @Slf4j @Component public class MinioUtil { /** * Minio文件上传 * * @param file 文件实体 * @param fileName 修饰过的文件名 非源文件名 * @param bucketName 所存文件夹(桶名) * @return */ public ResultEntity<Map<String, Object>> minioUpload(MultipartFile file, String fileName, String bucketName) { ResultEntity<Map<String, Object>> resultEntity = new ResultEntity(); try { MinioClient minioClient = MinioClientConfig.getMinioClient(); // fileName为空,说明要使用源文件名上传 if (fileName == null) { fileName = file.getOriginalFilename(); fileName = fileName.replaceAll(" ", "_"); } InputStream inputStream = file.getInputStream(); PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(fileName) .stream(inputStream, file.getSize(), -1).contentType(file.getContentType()).build(); //文件名称相同会覆盖 minioClient.putObject(objectArgs); return resultEntity; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 检查存储桶是否存在 * * @param bucketName 存储桶名称 * @return */ public boolean bucketExists(String bucketName) { boolean flag = false; try { flag = MinioClientConfig.bucketExists(bucketName); if (flag) { return true; } } catch (Exception e) { e.printStackTrace(); return false; } return false; } /** * 获取文件流 * * @param fileName 文件名 * @param bucketName 桶名(文件夹) * @return */ public InputStream getFileInputStream(String fileName, String bucketName) { try { MinioClient minioClient = MinioClientConfig.getMinioClient(); return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build()); } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage()); } return null; } /** * @param bucketName: * @author * @description: 创建桶 * @date 2022/8/16 14:36 */ public void createBucketName(String bucketName) { try { if (StringUtils.isBlank(bucketName)) { return; } MinioClient minioClient = MinioClientConfig.getMinioClient(); boolean isExist = MinioClientConfig.bucketExists(bucketName); if (isExist) { log.info("Bucket {} already exists.", bucketName); } else { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage()); } } /** * 下载文件 * * @param originalName 文件路径 */ public InputStream downloadFile(String bucketName, String originalName, HttpServletResponse response) { try { MinioClient minioClient = MinioClientConfig.getMinioClient(); InputStream file = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(originalName).build()); String filename = new String(originalName.getBytes("ISO8859-1"), StandardCharsets.UTF_8); if (StringUtils.isNotBlank(originalName)) { filename = originalName; } response.setHeader("Content-Disposition", "attachment;filename=" + filename); ServletOutputStream servletOutputStream = response.getOutputStream(); int len; byte[] buffer = new byte[1024]; while ((len = file.read(buffer)) > 0) { servletOutputStream.write(buffer, 0, len); } servletOutputStream.flush(); file.close(); servletOutputStream.close(); return file; } catch (Exception e) { e.printStackTrace(); return null; } } /** * @param bucketName: * @description: 删除桶 * @date 2022/8/16 14:36 */ public void deleteBucketName(String bucketName) { try { if (StringUtils.isBlank(bucketName)) { return; } MinioClient minioClient = MinioClientConfig.getMinioClient(); boolean isExist = MinioClientConfig.bucketExists(bucketName); if (isExist) { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); } } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage()); } } /** * @param bucketName: * @description: 删除桶下面所有文件 * @date 2022/8/16 14:36 */ public void deleteBucketFile(String bucketName) { try { if (StringUtils.isBlank(bucketName)) { return; } MinioClient minioClient = MinioClientConfig.getMinioClient(); boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (isExist) { minioClient.deleteBucketEncryption(DeleteBucketEncryptionArgs.builder().bucket(bucketName).build()); } } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage()); } } /** * 根据文件路径得到预览文件绝对地址 * * @param bucketName * @param fileName * @return */ public String getPreviewFileUrl(String bucketName, String fileName) { try { MinioClient minioClient = MinioClientConfig.getMinioClient(); return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).build()); } catch (Exception e) { e.printStackTrace(); return ""; } } }
编写配置文件
package top.fairy.global.globalfairytoppi4j.config; import io.minio.BucketExistsArgs; import io.minio.MinioClient; import io.minio.messages.Bucket; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.List; @Slf4j @Component @Configuration public class MinioClientConfig { @Autowired private StorageProperty storageProperty; private static MinioClient minioClient; /** * @description: 获取minioClient * @date 2021/6/22 16:55 * @return io.minio.MinioClient */ public static MinioClient getMinioClient(){ return minioClient; } /** * 判断 bucket是否存在 * * @param bucketName: * 桶名 * @return: boolean * @date : 2020/8/16 20:53 */ @SneakyThrows(Exception.class) public static boolean bucketExists(String bucketName) { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } /** * 获取全部bucket * * @param : * @return: java.util.List<io.minio.messages.Bucket> * @date : 2020/8/16 23:28 */ @SneakyThrows(Exception.class) public static List<Bucket> getAllBuckets() { return minioClient.listBuckets(); } /** * 初始化minio配置 * * @param : * @return: void * @date : 2020/8/16 20:56 */ @PostConstruct public void init() { try { minioClient = MinioClient.builder() .endpoint(storageProperty.getEndpoint()) .credentials(storageProperty.getAccessKey(), storageProperty.getSecretKey()) .build(); } catch (Exception e) { e.printStackTrace(); log.error("初始化minio配置异常: 【{}】", e.fillInStackTrace()); } } }
编写业务类
package top.fairy.global.globalfairytoppi4j.action; import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import io.minio.*; import io.minio.errors.*; import io.minio.messages.Bucket; import io.minio.messages.Item; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import top.fairy.global.globalfairytoppi4j.api.CommonResult; import top.fairy.global.globalfairytoppi4j.beans.BucketPolicyConfigDto; import top.fairy.global.globalfairytoppi4j.beans.FileVo; import top.fairy.global.globalfairytoppi4j.beans.MinioUploadDto; import top.fairy.global.globalfairytoppi4j.beans.PageUtil; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; /** * MinIO对象存储管理 * Created by macro on 2019/12/25. */ //@Api(tags = "MinioController", description = "MinIO对象存储管理") @Controller @RequestMapping("/minio") public class MinioController { private static final Logger LOGGER = LoggerFactory.getLogger(MinioController.class); @Value("${minio.endpoint}") private String ENDPOINT; @Value("${minio.bucketName}") private String BUCKET_NAME; @Value("${minio.bucketImg}") private String BUCKET_IMG; @Value("${minio.bucketVideo}") private String BUCKET_VIDEO; @Value("${minio.accessKey}") private String ACCESS_KEY; @Value("${minio.secretKey}") private String SECRET_KEY; // @ApiOperation("文件上传") @RequestMapping(value = "/upload", method = RequestMethod.POST) @ResponseBody public CommonResult upload(@RequestPart("file") MultipartFile file) { try { //创建一个MinIO的Java客户端 MinioClient minioClient =MinioClient.builder() .endpoint(ENDPOINT) .credentials(ACCESS_KEY,SECRET_KEY) .build(); boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET_NAME).build()); if (isExist) { LOGGER.info("存储桶已经存在!"); } else { //创建存储桶并设置只读权限 minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET_NAME).build()); BucketPolicyConfigDto bucketPolicyConfigDto = createBucketPolicyConfigDto(BUCKET_NAME); SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder() .bucket(BUCKET_NAME) .config(JSONUtil.toJsonStr(bucketPolicyConfigDto)) .build(); minioClient.setBucketPolicy(setBucketPolicyArgs); } String filename = file.getOriginalFilename(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); // 设置存储对象名称 String objectName = sdf.format(new Date()) + "/" + filename; // 使用putObject上传一个文件到存储桶中 PutObjectArgs putObjectArgs = PutObjectArgs.builder() .bucket(BUCKET_NAME) .object(objectName) .contentType(file.getContentType()) .stream(file.getInputStream(), file.getSize(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build(); minioClient.putObject(putObjectArgs); LOGGER.info("文件上传成功!"); MinioUploadDto minioUploadDto = new MinioUploadDto(); minioUploadDto.setName(filename); minioUploadDto.setUrl(ENDPOINT + "/" + BUCKET_NAME + "/" + objectName); return CommonResult.success(minioUploadDto); } catch (Exception e) { e.printStackTrace(); LOGGER.info("上传发生错误: {}!", e.getMessage()); } return CommonResult.failed(); } private BucketPolicyConfigDto createBucketPolicyConfigDto(String bucketName) { BucketPolicyConfigDto.Statement statement = BucketPolicyConfigDto.Statement.builder() .Effect("Allow") .Principal("*") .Action("s3:GetObject") .Resource("arn:aws:s3:::"+bucketName+"/*.**").build(); return BucketPolicyConfigDto.builder() .Version("2012-10-17") .Statement(CollUtil.toList(statement)) .build(); } /** * 获取文件列表 * * @param pageNum 页码 * @param pageSize 一页的数量 * @return * @throws Exception */ @ResponseBody @RequestMapping(value = "/fileList", method = RequestMethod.GET, produces = "application/json;charset=UTF-8") public String getFileList(Integer pageNum, Integer pageSize) throws Exception { String bucketName = "img"; //创建一个MinIO的Java客户端 MinioClient minioClient =MinioClient.builder() .endpoint(ENDPOINT) .credentials(ACCESS_KEY,SECRET_KEY) .build(); DecimalFormat df = new DecimalFormat("0.00"); List<Bucket> buckets = minioClient.listBuckets(); List<FileVo> list = new ArrayList<>(32); if (!buckets.isEmpty()) { buckets.forEach(s -> { try { // 得到bucket下的文件 Iterable<Result<Item>> results = minioClient.listObjects(s.name()); // 循环遍历获取每一个文件对象 results.forEach(g -> { try { FileVo fileVo = new FileVo(); fileVo.setBucketName(s.name()); // 文件夹名称 fileVo.setFileName(g.get().objectName()); // 文件名称 fileVo.setUpdateTime(localDateTime2Date(g.get().lastModified().toLocalDateTime())); // 文件上传时间 Long size = g.get().size(); if (size > (1024 * 1024)) { fileVo.setFileSize(df.format(((double) size / 1024 / 1024)) + "MB"); // 文件大小,如果超过1M,则把单位换成MB } else if (size > 1024) { fileVo.setFileSize(df.format(((double) size / 1024)) + "KB"); // 文件大小,如果没超过1M但是超过1000字节,则把单位换成KB } else { fileVo.setFileSize( size + "bytes"); // // 文件大小,如果没超过1000字节,则把单位换成bytes } list.add(fileVo); } catch (ErrorResponseException e) { e.printStackTrace(); } catch (InsufficientDataException e) { e.printStackTrace(); } catch (InternalException e) { e.printStackTrace(); } catch (InvalidBucketNameException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidResponseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (XmlParserException e) { e.printStackTrace(); } catch (ServerException e) { e.printStackTrace(); } }); } catch (XmlParserException e) { e.printStackTrace(); } }); } JSONObject res = new JSONObject(); res.put("code", 200); res.put("message", "获取文件列表成功"); // 按最后上传时间排序 list.sort(new Comparator<FileVo>() { @Override public int compare(FileVo o1, FileVo o2) { return o2.getUpdateTime().compareTo(o1.getUpdateTime()); } }); // 分页 List returnList = PageUtil.startPage(list, pageNum, pageSize); res.put("list", returnList); ObjectMapper mapper = new ObjectMapper(); String s = mapper.writeValueAsString(res); return s; } private Date localDateTime2Date(LocalDateTime toLocalDateTime) { Date date = Date.from( toLocalDateTime.atZone( ZoneId.systemDefault()).toInstant()); return date; } // @ApiOperation("文件删除")文件删除 @RequestMapping(value = "/delete", method = RequestMethod.POST) @ResponseBody public CommonResult delete(@RequestParam("objectName") String objectName) { try { MinioClient minioClient = MinioClient.builder() .endpoint(ENDPOINT) .credentials(ACCESS_KEY,SECRET_KEY) .build(); minioClient.removeObject(RemoveObjectArgs.builder().bucket(BUCKET_NAME).object(objectName).build()); return CommonResult.success(null); } catch (Exception e) { e.printStackTrace(); } return CommonResult.failed(); } }
业务类service,dao入库等操作请自行补充。
源码下载地址:
global-fairy-top-pi4j: java语言读取dth11温湿度传感器参数,通过接口对外提供
具体版本参考:v0.3.1
打包部署
Idea中maven package项目之后,拷贝jar包到云服务器
启动
nohup java -jar global-fairy-top-pi4j-0.0.1-SNAPSHOT.jar &
测试:
localhost:8082/minio/fileList
常见问题
部署demo前请先安装mysql,并配置好地址。