如果需要要处理大量文件,又没有文件系统,那么把文件存储在minio中是一个很不错的选择,下面记录上传、对文件进行加密(MD5,sha1)、下载的整体过程,避免存储相同内容文件。
例如:
两个文件test1.pdf 和 test2.pdf,两个名称不相同,但是内容相同,那么minio中只要存储一条就可以。只要内容相同,即使名称相同,也只要存储一条就可以了。
最终效果:
上传:
下载:
开始前
如果你有镜像,则可以略过,
Minio镜像:镜像
如果你还想安装客户端也可以参考: 客户端安装
我们先看看 attachment 和 myfile 关系 是多对一,
attachment:
- name:文件名称
- ext: 后缀
- file-id 对应的myfile
myfile:
- md5: 编码信息
- mime: 文件类型
- name: 文件编码后的名称
- path: 路径
- quote_number: 对应的attachment数量
- sha1:编码信息
上传
前台核心代码:
支持拖动和点击进行上传,采用nz-zorro第三方库的组件。
// html
<nz-upload
nzType="drag"
[nzMultiple]="false"
nzAccept="application/pdf"
[nzCustomRequest] = uploadToMinio>
<p class="ant-upload-drag-icon">
<span nz-icon nzType="inbox"></span>
</p>
<p class="ant-upload-text">上传附件</p>
<p class="ant-upload-hint">
支持拖动和点击
</p>
</nz-upload>
uploadToMinio处理文件上传的回调函数,上传文件、处理响应,
// Component
uploadToMinio = (item: NzUploadXHRArgs): Subscription => {
return this.attachmentService.uploadToMinio(item.postFile as File).subscribe({
next: (response) => {
// 上传成功之后
if (response.type === HttpEventType.Response) {
item.onSuccess(response.status, item.file, null);
this.commonService.success(() => {
this.onClose();
}, '上传成功');
}
},
});
};
//Service
uploadToMinio(file: File): Observable<HttpEvent<Attachment>> {
const formData: FormData = new FormData();
formData.append('file', file);
return this.httpClient.post<Attachment>(`upload/uploadToMinio`, formData, {reportProgress: true, observe: 'events'});
}
将文件对象 file 添加到 FormData 中,使用键 'file' 来标识这个文件字段。在后端接收时,可以根据这个键名获取文件内容。
后台:
请先看流程图在审阅代码,代码没有模块化分离,希望读者更容易理解
@RestController
@RequestMapping("/upload")
public class UploadController {
static final Logger logger = LoggerFactory.getLogger(UploadController.class);
private String bucketName = "miniobrower";
private String attachmentPath = "attachment" ;
private String endpoint = "http://127.0.0.1:9000";
private String accessKey = "UjXVlLV37Twzg0Hf****";
private String secretKey = "sc0o244O3HrhjcOpqd5koPe00ePQlkeNvo5*****";
MinioClient minioClient;
@PostMapping("uploadToMinio")
@JsonView(AttachmentController.UploadJsonView.class)
public Attachment uploadToMinio(@RequestParam("file") MultipartFile multipartFile) throws Exception {
Boolean found = this.bucketExists(bucketName);
if (!found) {
logger.debug("创建桶:" + bucketName);
try {
this.getMinioClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
logger.error("创建桶出错:" + e);
throw new RuntimeException(e);
}
}
// 存储路径
Path saveFilePath = Paths.get(attachmentPath +"/"
+ new SimpleDateFormat("yyyyMMdd").format(new Date())
+ "/" + multipartFile.getOriginalFilename());
logger.debug("判断上传的文件是否为空");
if (multipartFile.isEmpty()) {
throw new RuntimeException("上传的附件不能为空" + multipartFile);
}
logger.debug("新建附件对象");
Attachment attachment = new Attachment();
logger.debug("获取文件名");
String fileName = multipartFile.getOriginalFilename();
logger.debug("从文件名中截取拓展名");
// 从"."最后一次出现的位置的下一位开始截取,获取扩展名
assert fileName != null;
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
logger.debug("对文件进行sha1,md5加密");
String sha1ToMultipartFile = Utils.encrypt(multipartFile, "SHA-1");
String md5ToMultipartFile = Utils.encrypt(multipartFile, "MD5");
logger.debug("设置文件信息");
attachment.setName(fileName);
attachment.setExt(ext);
// 根据加密信息 去数据库中查询是否有当前文件信息
MyFile oldFile = this.myFileRepository.findTopBySha1AndMd5(sha1ToMultipartFile, md5ToMultipartFile);
// 判断是否保存file
if (oldFile == null) {
logger.debug("设置保存文件名");
String saveName = Utils.md5(md5ToMultipartFile + System.currentTimeMillis()) + "." + ext;
/**
* 对file 进行赋值 都是Set方法,就不展示了
* @param file 要保存的文件信息,包含信息如下:
* @param getContentType 文件类型
* @param saveFilePath 保存的路径
* @param sha1ToMultipartFile sha1的加密信息
* @param md5ToMultipartFile md5的加密信息
* @param saveName 文件的名称
*/
MyFile file = new MyFile();
setMyFile(file, multipartFile.getContentType(), saveFilePath, sha1ToMultipartFile, md5ToMultipartFile, saveName);
logger.debug(multipartFile.getOriginalFilename() + "文件转换字节");
byte[] byteData = multipartFile.getBytes();
logger.debug(multipartFile.getOriginalFilename() + "文件存入到minio中");
this.uploadByte(bucketName, saveFilePath.toString(), byteData, "application/pdf");
// 保存文件信息
oldFile = this.myFileRepository.save(file);
} else {
// 如果走到这里来了,说明有相同文件,就没必要存入到minio中,+1 用来记录
oldFile.setQuoteNumber(oldFile.getQuoteNumber() + 1);
// 保存文件信息
oldFile = this.myFileRepository.save(oldFile);
}
attachment.setFile(oldFile);
// 保存附件信息
this.attachmentRepository.save(attachment);
return attachment;
}
/**
* 建立连接
*/
public MinioClient getMinioClient() {
this.minioClient = MinioClient.builder()
.endpoint(this.endpoint)
.credentials(this.accessKey, this.secretKey)
.build();
return this.minioClient;
}
/**
* 判断桶名是否存在
* @param bucketName 桶名
*/
public Boolean bucketExists(String bucketName) {
try {
logger.debug("当前桶是否存在" + bucketName);
return this.getMinioClient().bucketExists(
BucketExistsArgs.builder().bucket(bucketName).build()
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 上传到minio中
* @param bucket 桶名
* @param prefix 路径
* @param data 文件的字节
* @param contentType 文件的类型
*/
public void uploadByte(String bucket, String prefix, byte[] data, String contentType) {
try {
this.getMinioClient()
.putObject(PutObjectArgs.builder()
.bucket(bucket)
.object(prefix)
.stream(new ByteArrayInputStream(data), data.length, -1)
.contentType(contentType)
.build());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
下载
前台
下载比较简单,
我们只需要知道文件的唯一标识id,发起请求响应后台,
类型是responseType: 'blob'
表示服务器返回的数据类型为 blob
download(attachment: Attachment): void {
this.httpClient.get(`upload/download/${attachment.id}`, {responseType: 'blob'})
.subscribe(blob => {
saveAs(blob, attachment.name);
});
}
比较注意的是:saveAs(blob, attachment.name)
,保存到本地,其中blob表示需要保存的文件。
saveAs是FileSaver.js库中的函数,如果你没有当前环境你可以使用当前命令进行安装。
npm install file-saver --save
后台:
后台响应后会根据id 去数据库中查询attachment,通过attachment对应的myfile实体,myfile实体中存有路径(path)字段。
这时我们知道了: 桶名称、文件路径,就可以调用Minion的api,GetObjectArgs进行下载了,返回该文件的流,最后逐步读取流中的内容,outputStream.write(buffer, 0, bytesRead)
返回给前台
@GetMapping("download/{id}")
public void download(@PathVariable Long id, HttpServletResponse response) throws IOException {
OutputStream outputStream = response.getOutputStream();
// 根据id 去数据库中查询是否有 该attachment
Attachment attachment = this.attachmentRepository.findById(id).orElseThrow(EntityNotFoundException::new);
logger.debug("下载文件:" + attachment.getName());
try {
InputStream inputStream = this.getMinioClient().getObject(
GetObjectArgs.builder()
.bucket(bucketName) // 指定bucket名称
.object(attachment.getFile().getPath()) // 指定文件名称
.build());
// 输出流
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// 关闭流
inputStream.close();
outputStream.close();
} catch (Exception e) {
this.logger.error("在执行getObjectContent时发生异常:" + e.getMessage());
throw new RuntimeException(e);
}
}
文献
如果你还需要更多功能,可以参考:
minio官网api: https://min.io/docs/minio/linux/developers/go/API.html
希望这篇文章对你有一定帮助。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。