Minio
Minio
1 基于centos7,docker部署
# 直接使用centos7部署,但是只能通过私有ip访问本机,指定公网ip不行
cd /usr/local/
# 获取minio
wget -q http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
# 授予文件权限
chmod +x minio
#启动minio server服务,指定数据存储目录/mnt/data,最好是指定控制台端口,这个方式创建的minio只能通过本机访问。
./minio server /mnt/data --console-address :9090
#docker 成功
#9090是console中控制台访问,9000是容器中api的端口,java代码用的是api的端口
docker run -d -p 9000:9000 -p 9090:9090 --name docker_minio
-e "MINIO_ROOT_USER=fengpeng"
-e "MINIO_ROOT_PASSWORD=fengpeng"
-v /usr/local/minio/data:/data
-v /usr/local/minio/config:/root/.minio
#/data 目录设置为MinIO服务器要管理的对象存储数据的目录(-v /usr/local/minio/data:/data )
# minio/minio 是一个 Docker 镜像,用于启动 MinIO 服务器
minio/minio server /data
--console-address ":9090"
# 如果有错误,通过docker logs 容器ip查看日志
完整语句
docker run -d -p 9000:9000 -p 9090:9090 --name docker_minio -e "MINIO_ROOT_USER=fengpeng" -e "MINIO_ROOT_PASSWORD=fengpeng" -v /usr/local/minio/data:/data -v /usr/local/minio/config:/root/.minio minio/minio server /data --console-address ":9090"
2 springboot整合minio
pom.xml
<!--minio依赖-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.1</version>
</dependency>
<!--minio相关依赖-->
<dependency>
<groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId>
<version>0.9.5</version>
</dependency>
<!--minio相关依赖-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>5.0.0-alpha.10</version>
</dependency>
application.yml
#minio配置
minio:
endpoint: http://8.137.103.17:9000
accessKey:
secretKey:
bucketName: test
MinioController
package com.feng.controller;
import com.alibaba.fastjson2.JSON;
import com.feng.utils.DataResult;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author fengpeng
* @version V1.0
* Copyright (c) 2024, fengpeng@hwadee.com All Rights Reserved.
* @ProjectName:big_event
* @Title: MinioController
* @Package com.feng.controller
* @Description: minio控制类
* @date 2024/1/25 9:35
*/
@RestController
@RequestMapping("/minio")
@Slf4j
public class MinioController {
@Autowired
private MinioClient minioClient;
@Value("${minio.bucketName}")
private String bucketName;
@Value("${minio.endpoint}")
private String endpoint;
@GetMapping("/list")
public List<Object> list() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
//获取bucket列表
Iterable<Result<Item>> myObjects = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).build());
Iterator<Result<Item>> iterator = myObjects.iterator();
List<Object> items = new ArrayList<>();
String format = "{'fileName':'%s', 'fileSize':'%s'}";
while (iterator.hasNext()){
Item item = iterator.next().get();
items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size()))));
}
return items;
}
@PostMapping("/upload")
public DataResult upload(@RequestParam(name = "file",required = false) MultipartFile[] files) throws Exception {
if (Objects.isNull(files) || files.length == 0){
return DataResult.error("请选择文件");
}
List<String> orgFileNameList = new ArrayList<>(files.length);
for (MultipartFile multipartFile : files) {
//获取文件名
String orgFileName = multipartFile.getOriginalFilename();
//用时间作为不重复的名字
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String fileName = sdf.format(date) + orgFileName.substring(orgFileName.lastIndexOf("."));
orgFileNameList.add(fileName);
try {
InputStream in = multipartFile.getInputStream();
minioClient.putObject(
PutObjectArgs.builder().bucket(bucketName)
.object(fileName)
.stream(in, multipartFile.getSize(),-1)
.contentType(multipartFile.getContentType())
.build());
in.close();
} catch (Exception e) {
log.error(e.getMessage());
return DataResult.error("上传失败");
}
}
/*Map<String, Object> data = new HashMap<>();
data.put("bucketName", bucketName);
//默认获取第一个文件
data.put("fileName", orgFileNameList.get(0));*/
/*记得将minio的console的bucket权限设置为public*/
String fileUrl = endpoint + "/" + bucketName + "/" + orgFileNameList.get(0);
return DataResult.success(fileUrl);
}
@GetMapping("/download")
public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) throws Exception {
InputStream in = null;
try {
//获取对象信息
StatObjectResponse stat = minioClient.statObject
(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
//设置响应头
response.setContentType(stat.contentType());
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
//文件下载
in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
IOUtils.copy(in, response.getOutputStream());
}catch (Exception e){
log.error(e.getMessage());
}finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
}
@DeleteMapping("/delete/{fileName}")
public DataResult delete(@PathVariable("fileName") String fileName) throws Exception {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
}catch (Exception e){
log.error(e.getMessage());
return DataResult.error("删除文件失败");
}
return DataResult.success();
}
private static String formatFileSize(long fileS) {
DecimalFormat df = new DecimalFormat("#.00");
String fileSizeString = "";
String wrongSize = "0B";
if (fileS == 0) {
return wrongSize;
}
if (fileS < 1024) {
fileSizeString = df.format((double) fileS) + " B";
} else if (fileS < 1048576) {
fileSizeString = df.format((double) fileS / 1024) + " KB";
} else if (fileS < 1073741824) {
fileSizeString = df.format((double) fileS / 1048576) + " MB";
} else {
fileSizeString = df.format((double) fileS / 1073741824) + " GB";
}
return fileSizeString;
}
}
前端代码
UserAvatar.vue
<script setup>
import { Plus, Upload } from '@element-plus/icons-vue'
import {ref} from 'vue'
import avatar from '@/assets/default.png'
const uploadRef = ref()
import {useTokenStore} from '@/stores/token.js'
const tokenStore = useTokenStore()
import useUserInfoStore from '@/stores/userInfo.js'
const userInfoStore = useUserInfoStore()
//用户头像地址
const imgUrl= ref(userInfoStore.info.userPic)
//图片上传成功的回调函数
const uploadSuccess = (res) => {
imgUrl.value = res.data
}
import {updateAvatarUpdateService} from '@/api/user.js'
import {ElMessage} from 'element-plus'
//头像修改
const updateAvatar = async() => {
//调用接口
let url = imgUrl.value
let res = await updateAvatarUpdateService(url.slice(url.lastIndexOf('/') + 1))
ElMessage.success(res.message ? res.message : '修改成功')
//修改pinia中的数据
userInfoStore.info.userPic = imgUrl.value
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>更换头像</span>
</div>
</template>
<el-row>
<el-col :span="12">
<el-upload
ref="uploadRef"
class="avatar-uploader"
:show-file-list="false"
:auto-upload="true"
action="/api/minio/upload"
name="file"
:headers="{'Authorization': tokenStore.token}"
:on-success="uploadSuccess"
>
<!-- 选择图片后,自动上传到服务器,返回图片在服务器上的地址 -->
<img v-if="imgUrl" :src="imgUrl" class="avatar" />
<!-- 默认图片 -->
<img v-else :src="avatar" width="278" />
</el-upload>
<br />
<el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()">
选择图片
</el-button>
<el-button type="success" :icon="Upload" size="large" @click="updateAvatar">
上传头像
</el-button>
</el-col>
</el-row>
</el-card>
</template>
<style lang="scss" scoped>
.avatar-uploader {
:deep() {
.avatar {
width: 278px;
height: 278px;
display: block;
}
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 278px;
height: 278px;
text-align: center;
}
}
}
</style>
注意
不设置成Public,无法直接通过返回的图片url访问bucket中的图片
岁月如歌,,,