这几天想玩玩Minio,整体来说简单使用起来不复杂(当然也有可能是我配置的太少了)
Minio下载
我是通过Dokcer在虚拟机上下载的(Docker真好用啊)
拉取Minio镜像
docker
docker pull minio/minio
启动Minio容器
docker
docker run -d --name minio -p 9000:9000 -p 9001:9001
-v /root/minio/data:/data
-v /root/minio/config:/root/.minio
-e MINIO_ROOT_USER=账号名 -e MINIO_ROOT_PASSWORD=密码
minio/minio server /data --console-address ":9001"
Minio需要暴露两个端口,9000是API接口,9001是浏览器界面,后端通过9000端口发送请求,9001端口是可视化界面
还需要设置账号和密码MINIO_ROOT_USER
MINIO_ROOT_PASSWORD
(密码至少8位 )
以及配置数据卷,Bucket(MinIo中的文件夹)会建立在/data目录下
启动完成后可以打开9001端口查看
后端配置
Minio通过okhttp发送请求,okhttp版本过低会报错
xml
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.5</version>
</dependency>
编写配置文件
access-key
和secret-key
需要在9001端口上手动获取,登录Minio后,点击AccessKey
点击CreateAccessKey
点击Create
将accessKey
和secretKey
记住就行
yaml
minio:
endpoint: ip:9000
access-key: eSKt88NNU3PNKs8htCtf
secret-key: YadifAfciM8Q5OaShJcSmG0NkEm5dN58UJYFPmO7
编写配置类(记得加上set和get,配置数据注入依赖这两)
java
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIoConfig {
public String endpoint;
public String accessKey;
public String secretKey;
@Bean
public MinioClient minioClient(){
return MinioClient
.builder()
.endpoint(endpoint)
.credentials(accessKey,secretKey)
.build();
}
}
此时就可以通过自动注入获取到MinioClient对象,完成文件的上传下载等
Minio中的几乎所有方法都是通过构造器模式编写的,参数大多都是BucketExistsArgs.builder().build();
java
private final MinioClient minio;
// 通过构造器注入
public MinioService(MinioClient minio) {
this.minio = minio;
}
// 创建Bucket 相当于创建一个文件夹
public boolean createBucket(String bucketName) {
boolean exist = minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!exist){
minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
return exist;
}
// 在指定桶中上传文件
public boolean uploadFile(String bucketName,MultipartFile file,String newFileName) {
InputStream inputStream = null;
try{
inputStream = file.getInputStream();
minio.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(newFileName)
.stream(inputStream,file.getSize(),-1)
.contentType(file.getContentType())
.build());
} catch (Exception e){
return false;
}finally {
inputStream.close();
}
return true;
}
// 根据文件名获取对应桶中的文件
public InputStream downloadFile(String bucketName,String objectName) {
return minio.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
// 删除指定桶中的文件
public boolean deleteFile (String bucketName,String objectName){
try{
minio.removeObject(
RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()
);
}
catch (Exception e){
return false;
}
return true;
}
// 获取指定桶中的全部数据
public List<String> getAllFilesInBucket(String bucketName){
Iterable<Result<Item>> results = minio.listObjects(ListObjectsArgs
.builder()
.bucket(bucketName)
.build());
List<String> items = new ArrayList<>();
for (Result<Item> result : results) {
Item item = result.get();
items.add(item.objectName());
}
return items;
}
编写完Service类后可以通过访问Controller去创建文件删除文件等操作
因为上传文件名不应该重复,又不确定Minio是否会对文件名重复进行什么操作,所以我将上传的文件通过UUID进行重命名,并通过建立一张sql表存储新文件名和旧文件名之间的对应关系
java
// 获取指定桶中的全部文件
@GetMapping("/fileList")
public Result<List<String>> fileList() {
List<String> fileList = minIo.getAllFilesInBucket("test");
return Result.success(fileNameConvert(fileList));
}
// 上传文件是旧的文件名,通过SQL查询将文件名转换成新文件名
public List<String> fileNameConvert(List<String> fileList){
for(int i = 0;i<fileList.size();i++){
String oldNameByNewName = sqlMapper.getOldNameByNewName(fileList.get(i));
fileList.set(i,oldNameByNewName);
}
return fileList;
}
// 获取指定桶中的指定文件
// 文件的下载需要通过HttpServletResponse类完成,返回数据无所谓(写不写return都行)
@GetMapping("/getFile")
public Result<String> getFile(@RequestParam("fileName") String fileName, HttpServletResponse response) {
MinIoFile file = sqlMapper.getFile(fileName, "1");
InputStream inputStream = minIo.downloadFile("test", file.getNewFileName());
response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(fileName, "UTF-8"));
ServletOutputStream outputStream = response.getOutputStream();
inputStream.transferTo(outputStream);
return Result.success("ok");
}
// 文件上传
@Transactional
@PostMapping("/hello/upload")
public Result<String> upload(@RequestParam("file") MultipartFile multipartFile){
String oldFileName = multipartFile.getOriginalFilename();
String[] files = oldFileName.split("\\.");
String endName = files[files.length-1];
if (!endName.equals("md")){
return Result.error("返回一个md结尾的数据");
}
String uuid = UUID.randomUUID().toString();
// 文件存储,原名: 新名 每次查询时从数据库中查找对应的原名
String newName = uuid +"."+endName;
// 将数据存储到SQL中
MinIoFile minIoFile = new MinIoFile();
minIoFile.setOldFileName(oldFileName);
minIoFile.setNewFileName(newName);
minIoFile.setIsDelete(0);
minIoFile.setCreateTime(new java.sql.Date(System.currentTimeMillis()));
String userId = "1";
minIoFile.setCreateUser(userId);
boolean createFileSuccess = sqlMapper.createNewFile(minIoFile);
if (!createFileSuccess){
throw new BaseException("创建文件失败");
}
try {
minIo.uploadFile("test",multipartFile,newName);
} catch (Exception e) {
throw new BaseException("创建文件失败");
}
return Result.success("创建文件成功");
}
创表语句
sql
create table file(
id int primary key auto_increment,
old_file_name varchar(50) not null,
new_file_name varchar(50) not null unique,
is_delete int default 0,
create_time datetime,
create_user varchar(50)
)
Mapper接口
java
@Insert("insert into file values(null,#{oldFileName},#{newFileName},#{isDelete},#{createTime},#{createUser})")
boolean createNewFile (MinIoFile minIoFile);
@Select("select * from file where old_file_name = #{oldFileName} and create_user = #{userId}")
MinIoFile getFile(@Param("oldFileName") String oldFileName,@Param("userId") String userId);
@Select("select old_file_name from file where new_file_name = #{newFileName}")
String getOldNameByNewName(String newFileName);
前端
前端直接调用后端Controller接口就可以,没有什么特殊写法,所以我界面写的很简陋
唯二需要注意的是后端接收不到file的问题和Liunx系统下换行符是\r\n需要用正则匹配替换成\n
因为我上传下载的都是纯文本文件,如果有需要的话可以整个富文本框架展示
html
<body>
<button id="fileList">查看文件列表</button>
<input type="file" id="file">
<button id="uploadFile">上传文件</button>
<input type="text" id="fileName" placeholder="输入文件名">
<button id="downloadFile">查看文件</button>
<p></p>
<script>
const fileListButton = document.querySelector('#fileList')
const uploadFileButton = document.querySelector('#uploadFile')
const p = document.querySelector('p')
fileListButton.addEventListener("click", () => {
axios({
method: 'get',
url: "http://localhost:8080/fileList"
}).then((res) => {
console.log(res)
})
})
uploadFileButton.addEventListener("click", () => {
const file = document.querySelector('#file').files[0]
const data = new FormData();
data.append('file', file);
axios({
method: 'post',
url: 'http://localhost:8080/upload',
data: data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
})
const downloadFileButton = document.querySelector('#downloadFile')
downloadFileButton.addEventListener("click", () => {
const fileName = document.querySelector('#fileName').value
axios({
method: 'get',
url: "http://localhost:8080/getFile",
params: { fileName: fileName }
}).then((res) => {
const str = res.data.replace('/\r\n/g', '\n')
console.log(str)
p.innerText = str
console.log(res)
})
})
</script>
</body>
总结
用起来还是十分方便的,我没有做JWT之类区分用户,正常来说估计需要通过用户信息创建不同的桶实现数据隔离(多租户),以及前端界面的展示,数据的流式上传下载等
Minio还有许多功能比如设置桶的权限,文件访问权限等,大伙可以自行研究