From 3009ae206f9d7777e7d42efaabdffcf6c7cbf839 Mon Sep 17 00:00:00 2001 From: zzs Date: Fri, 29 Nov 2024 14:26:52 +0800 Subject: [PATCH] feat:upload and play video --- .../ensign-module-infra-biz/pom.xml | 8 + .../crm/module/infra/config/MinioConfig.java | 31 +++ .../controller/admin/file/FileController.java | 6 + .../admin/video/VideoController.java | 32 +++ .../infra/service/file/FileService.java | 15 ++ .../infra/service/file/FileServiceImpl.java | 31 ++- .../crm/module/infra/utils/MinioUtils.java | 210 ++++++++++++++++++ .../src/main/resources/application-local.yaml | 6 + .../src/main/resources/application.yaml | 4 +- 9 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/config/MinioConfig.java create mode 100644 ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/video/VideoController.java create mode 100644 ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/utils/MinioUtils.java diff --git a/ensign-module-infra/ensign-module-infra-biz/pom.xml b/ensign-module-infra/ensign-module-infra-biz/pom.xml index 88e5fc7..0d1db58 100644 --- a/ensign-module-infra/ensign-module-infra-biz/pom.xml +++ b/ensign-module-infra/ensign-module-infra-biz/pom.xml @@ -118,6 +118,14 @@ tika-core + + + io.minio + minio + 8.5.14 + + + diff --git a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/config/MinioConfig.java b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/config/MinioConfig.java new file mode 100644 index 0000000..3dc77e9 --- /dev/null +++ b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/config/MinioConfig.java @@ -0,0 +1,31 @@ +package com.ensign.crm.module.infra.config; + +import io.minio.MinioClient; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @Description: minio配置 + * @Date: 29/11/2024 9:57 am + * @Created: by ZZSLL + */ + +@Data +@Configuration +@ConfigurationProperties(prefix = "minio") +public class MinioConfig { + private String endpoint; + private String accessKey; + private String secretKey; + private String bucketName; + + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + } +} diff --git a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/file/FileController.java b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/file/FileController.java index 40beee6..7fadb61 100644 --- a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/file/FileController.java +++ b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/file/FileController.java @@ -100,4 +100,10 @@ public class FileController { return success(BeanUtils.toBean(pageResult, FileRespVO.class)); } + @PostMapping("/upload/minio") + @Operation(summary = "上传Minio") + public CommonResult uploadMinio(MultipartFile file) { + return success(fileService.createMinioFile(file)); + } + } diff --git a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/video/VideoController.java b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/video/VideoController.java new file mode 100644 index 0000000..ccb09ae --- /dev/null +++ b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/controller/admin/video/VideoController.java @@ -0,0 +1,32 @@ +package com.ensign.crm.module.infra.controller.admin.video; + +import com.ensign.crm.framework.common.pojo.CommonResult; +import com.ensign.crm.module.infra.service.file.FileService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.security.PermitAll; + +/** + * @Description: 播放minio中视频 + * @Date: 29/11/2024 10:27 am + * @Created: by ZZSLL + */ + +@RestController +@RequestMapping("/infra/video") +@Tag(name = "管理后台 - 视频") +public class VideoController { + + @Autowired + private FileService fileService; + + @GetMapping("/play") + @Operation(summary = "播放视频") + @PermitAll // 仅测试 + public CommonResult play(@RequestParam String bucket, @RequestParam String path) { + return CommonResult.success(fileService.readAsVideo(bucket, path)); + } +} diff --git a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileService.java b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileService.java index e29df49..cf31a5e 100644 --- a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileService.java +++ b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileService.java @@ -5,6 +5,7 @@ import com.ensign.crm.module.infra.controller.admin.file.vo.file.FileCreateReqVO import com.ensign.crm.module.infra.controller.admin.file.vo.file.FilePageReqVO; import com.ensign.crm.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import com.ensign.crm.module.infra.dal.dataobject.file.FileDO; +import org.springframework.web.multipart.MultipartFile; /** * 文件 Service 接口 @@ -63,4 +64,18 @@ public interface FileService { */ FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception; + /** + * 上传文件至minio + * @return 上传后完整路径 + */ + String createMinioFile(MultipartFile file); + + /** + * 视频播放 + * + * @param bucket minio桶名 + * @param path 路径 + * @return stream + */ + String readAsVideo(String bucket, String path); } diff --git a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileServiceImpl.java b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileServiceImpl.java index 1188ab9..16509c7 100644 --- a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileServiceImpl.java +++ b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/service/file/FileServiceImpl.java @@ -5,16 +5,20 @@ import cn.hutool.core.util.StrUtil; import com.ensign.crm.framework.common.pojo.PageResult; import com.ensign.crm.framework.common.util.io.FileUtils; import com.ensign.crm.framework.common.util.object.BeanUtils; -import com.ensign.crm.module.infra.framework.file.core.client.FileClient; -import com.ensign.crm.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; -import com.ensign.crm.module.infra.framework.file.core.utils.FileTypeUtils; import com.ensign.crm.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import com.ensign.crm.module.infra.controller.admin.file.vo.file.FilePageReqVO; import com.ensign.crm.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import com.ensign.crm.module.infra.dal.dataobject.file.FileDO; import com.ensign.crm.module.infra.dal.mysql.file.FileMapper; +import com.ensign.crm.module.infra.framework.file.core.client.FileClient; +import com.ensign.crm.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; +import com.ensign.crm.module.infra.framework.file.core.utils.FileTypeUtils; +import com.ensign.crm.module.infra.utils.MinioUtils; +import io.minio.MinioClient; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; @@ -26,6 +30,7 @@ import static com.ensign.crm.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXIS * * @author ENSIGN */ +@Slf4j @Service public class FileServiceImpl implements FileService { @@ -35,6 +40,12 @@ public class FileServiceImpl implements FileService { @Resource private FileMapper fileMapper; + @Resource + private MinioUtils minioUtils; + + @Resource + private MinioClient minioClient; + @Override public PageResult getFilePage(FilePageReqVO pageReqVO) { return fileMapper.selectPage(pageReqVO); @@ -114,4 +125,18 @@ public class FileServiceImpl implements FileService { object -> object.setConfigId(fileClient.getId())); } + @Override + public String createMinioFile(MultipartFile file) { + return minioUtils.upload(file); + } + + @Override + public String readAsVideo(String bucket, String path) { + try { + return minioUtils.preview(path); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } } diff --git a/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/utils/MinioUtils.java b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/utils/MinioUtils.java new file mode 100644 index 0000000..460aa64 --- /dev/null +++ b/ensign-module-infra/ensign-module-infra-biz/src/main/java/com/ensign/crm/module/infra/utils/MinioUtils.java @@ -0,0 +1,210 @@ +package com.ensign.crm.module.infra.utils; + +import cn.hutool.core.io.FastByteArrayOutputStream; +import com.ensign.crm.module.infra.config.MinioConfig; +import io.minio.*; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import io.minio.messages.Item; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @Description: Minio工具 + * @Date: 29/11/2024 9:58 am + * @Created: by ZZSLL + */ + +@Component +@Slf4j +public class MinioUtils { + + @Autowired + private MinioConfig prop; + + @Resource + private MinioClient minioClient; + + /** + * 查看存储bucket是否存在 + * @return boolean + */ + public Boolean bucketExists(String bucketName) { + boolean found; + try { + found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return found; + } + + /** + * 创建存储bucket + * @return Boolean + */ + public Boolean makeBucket(String bucketName) { + try { + minioClient.makeBucket(MakeBucketArgs.builder() + .bucket(bucketName) + .build()); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + /** + * 删除存储bucket + * @return Boolean + */ + public Boolean removeBucket(String bucketName) { + try { + minioClient.removeBucket(RemoveBucketArgs.builder() + .bucket(bucketName) + .build()); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + /** + * 获取全部bucket + */ + public List getAllBuckets() { + try { + return minioClient.listBuckets(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + + + /** + * 文件上传 + * + * @param file 文件 + * @return Boolean + */ + public String upload(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + if (StringUtils.isBlank(originalFilename)){ + throw new RuntimeException("文件名为空"); + } + String dateFolder = new SimpleDateFormat("yyyyMMdd").format(new Date()); + String fileHash; + try { + fileHash = DigestUtils.md5Hex(file.getInputStream()); + } catch (IOException e) { + throw new RuntimeException("计算文件哈希值失败", e); + } + String fileExtension = StringUtils.substringAfterLast(originalFilename, "."); + String objectName = String.format("/%s/%s.%s", dateFolder, fileHash, fileExtension); + try { + PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName) + .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build(); + minioClient.putObject(objectArgs); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new RuntimeException("上传Minio失败"); + } + return objectName; + } + + /** + * 预览图片 + * @param fileName + * @return + */ + public String preview(String fileName){ + // 查看文件地址 + GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build(); + try { + return minioClient.getPresignedObjectUrl(build); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 文件下载 + * @param fileName 文件名称 + * @param res response + * @return Boolean + */ + public void download(String fileName, HttpServletResponse res) { + GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName()) + .object(fileName).build(); + try (GetObjectResponse response = minioClient.getObject(objectArgs)){ + byte[] buf = new byte[1024]; + int len; + try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){ + while ((len=response.read(buf))!=-1){ + os.write(buf,0,len); + } + os.flush(); + byte[] bytes = os.toByteArray(); + res.setCharacterEncoding("utf-8"); + res.addHeader("Content-Disposition", "attachment;fileName=" + fileName); + try (ServletOutputStream stream = res.getOutputStream()){ + stream.write(bytes); + stream.flush(); + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + /** + * 查看文件对象 + * @return 存储bucket内文件对象信息 + */ + public List listObjects() { + Iterable> results = minioClient.listObjects( + ListObjectsArgs.builder().bucket(prop.getBucketName()).build()); + List items = new ArrayList<>(); + try { + for (Result result : results) { + items.add(result.get()); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + return items; + } + + /** + * 删除 + * @param fileName + * @return + * @throws Exception + */ + public boolean remove(String fileName){ + try { + minioClient.removeObject( RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build()); + }catch (Exception e){ + return false; + } + return true; + } +} diff --git a/ensign-server/src/main/resources/application-local.yaml b/ensign-server/src/main/resources/application-local.yaml index d63757a..c66f457 100644 --- a/ensign-server/src/main/resources/application-local.yaml +++ b/ensign-server/src/main/resources/application-local.yaml @@ -249,3 +249,9 @@ justauth: prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 +minio: + endpoint: http://127.0.0.1:9000 + bucketName: video + accessKey: qTgkym1JPFY53UGEiDP8 + secretKey: swuXwUczZtLt08Qx6ffDzQEhQVEilGCfmcae7bLh + diff --git a/ensign-server/src/main/resources/application.yaml b/ensign-server/src/main/resources/application.yaml index 1185acd..b95d179 100644 --- a/ensign-server/src/main/resources/application.yaml +++ b/ensign-server/src/main/resources/application.yaml @@ -12,8 +12,8 @@ spring: servlet: # 文件上传相关配置项 multipart: - max-file-size: 16MB # 单个文件大小 - max-request-size: 32MB # 设置总上传的文件大小 + max-file-size: 5GB # 单个文件大小 + max-request-size: 1TB # 设置总上传的文件大小 # Jackson 配置项 jackson: