Minio上传图片

Author Avatar
ciky 08月 23,2024
  • 在其它设备中阅读本文章
  • 点击生成二维码

上传图片


image20240602142249587.png

1. 准备环境


(1) 在nacos中配置

minio:
  endpoint: http://192.168.65.129:9001
  accessKey: minioadmin190715
  secretKey: minioadmin190715
  bucket:
    files: mediafiles
    videofiles: video

(2) minio配置类

/**
 * @Author: ciky
 * @Description: minio配置类
 * @DateTime: 2024/6/2 14:30
 **/
@Configuration
public class MinioConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Bean
    public MinioClient minioClient(){
        MinioClient minioClient =
                MinioClient.builder()
                        .endpoint(endpoint)
                        .credentials(accessKey, secretKey)
                        .build();

        return minioClient;
    }
}


2. 接口设计分析-JSON


请求地址:/media/upload/coursefile
请求内容:Content-Type: multipart/form-data; //---->对应的类型是(MediaType.MULTIPART_FORM_DATA_VALUE)
form-data; name="filedata"; filename="具体的文件名称"

响应参数:文件信息,如下
JSON
{
  "id": "a16da7a132559daf9e1193166b3e7f52",
  "companyId": 1232141425,
  "companyName": null,
  "filename": "1.jpg",
  "fileType": "001001",
  "tags": "",
  "bucket": "/testbucket/2022/09/12/a16da7a132559daf9e1193166b3e7f52.jpg",
  "fileId": "a16da7a132559daf9e1193166b3e7f52",
  "url": "/testbucket/2022/09/12/a16da7a132559daf9e1193166b3e7f52.jpg",
  "timelength": null,
  "username": null,
  "createDate": "2022-09-12T21:57:18",
  "changeDate": null,
  "status": "1",
  "remark": "",
  "auditStatus": null,
  "auditMind": null,
  "fileSize": 248329
}

3. 响应模型类-DTO

@Data
public class UploadFileResultDto extends MediaFiles {
    //后续可能需要添加其他内容
    //继承MediaFiles
    //方便以后修改
    //MediaFiles是数据库表的po类,不能随便修改
}
/**
 * @Author: ciky
 * @Description: 文件信息
 * @DateTime: 2024/6/2 17:50
 **/
@Data
public class UploadFileParamsDto {
    /**
     * 文件名称
     */
    private String filename;


    /**
     * 文件类型(文档,音频,视频)
     */
    private String fileType;
    /**
     * 文件大小
     */
    private Long fileSize;

    /**
     * 标签
     */
    private String tags;

    /**
     * 上传人
     */
    private String username;

    /**
     * 备注
     */
    private String remark;
}

4. 定义接口层-Controller

image20240602145126264.png


5. 持久层-Mapper

  • 由MybatisPlus提供

6. 业务层-Service


(1) 上传文件到minio

@Autowired
private MediaFileService currentProxy;

@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
    //1.将文件上传到minio
    //获取文件名
    String filename = uploadFileParamsDto.getFilename();
    //得到拓展名
    String extension = filename.substring(filename.lastIndexOf("."));
    //得到mimeType
    String mimeType = getMimeType(extension);	//------------------------------调用抽取的方法1

    String defaultFolderPath = getDefaultFolderPath();	//------------------------------调用抽取的方法2
    //文件的md5值
    String fileMd5 = getFileMd5(new File(localFilePath));	//------------------------------调用抽取的方法3

    //得到objectname
    String objectName = defaultFolderPath + fileMd5 + extension;

    addMediaFilesToMinIO(bucket_mediafiles, localFilePath, mimeType, objectName);	//------------------------------调用抽取的方法4


    //2.将文件信息保存到数据库-----------------------------------------------调用抽取的方法5
    MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);

    if(mediaFiles==null){
        XueChengPlusException.cast("文件上传后保存信息失败");
    }

    //准备返回的对象
    UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
    BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);

    return uploadFileResultDto;
}

/**
 * 根据扩展名获取mimeType	//------------------------------抽取方法1
 */
private String getMimeType(String extension) {
    if (extension == null) {
        extension = "";
    }
    //根据扩展名获取mimeType
    ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
    String mimeType = MediaType.APPLICATION_STREAM_JSON_VALUE;  //通用mimeType(字节流)
    if (extensionMatch != null) {
        mimeType = extensionMatch.getMimeType();
    }
    return mimeType;
}

/**
 * 获取文件默认存储目录路径(年/月/日)	//------------------------------抽取方法2
 */
private String getDefaultFolderPath() {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String folder = sdf.format(new Date()).replace("-", "/") + "/";
    return folder;
}

/**
 * 获取文件的md5值	//------------------------------抽取方法3
 */
private String getFileMd5(File file) {
    try {
        String fileMd5 = DigestUtils.md5Hex(new FileInputStream(file));
        return fileMd5;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

/**
 * 上传文件到minio	//------------------------------抽取方法4
 *
 * @param bucket        桶
 * @param localFilePath 本地文件路径
 * @param mimeType      媒资类型
 * @param objectName    对象名
 * @return
 */
public boolean addMediaFilesToMinIO(String bucket, String localFilePath, String mimeType, String objectName) {
    try {
        minioClient.uploadObject(UploadObjectArgs.builder()
                .bucket(bucket)             //minio中的桶
                .filename(localFilePath)    //本地文件路径
                .object(objectName)         //对象名,minio桶中的路径
                .contentType(mimeType)      //媒体文件类型
                .build());
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        log.error("上传文件出错,bucket{},objectName{},错误信息{}", bucket, objectName, e.getMessage());
    }
    return false;
}

(2) 保存文件到数据库

/**
     *  将文件信息添加到文件表		//------------------------------抽取方法5
     * @param companyId  机构id
     * @param fileMd5  文件md5值
     * @param uploadFileParamsDto  上传文件的信息
     * @param bucket  桶
     * @param objectName 对象名称
     * @return com.xuecheng.media.model.po.MediaFiles
     */
    @Transactional
    public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if(mediaFiles==null){
            mediaFiles = new MediaFiles();
            BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
            //文件id
            mediaFiles.setId(fileMd5);
            //机构id
            mediaFiles.setCompanyId(companyId);
            //桶
            mediaFiles.setBucket(bucket);
            //file_path
            mediaFiles.setFilePath(objectName);
            //file_id
            mediaFiles.setFileId(fileMd5);
            //url
            mediaFiles.setUrl("/"+bucket+"/"+objectName);
            //上传时间
            mediaFiles.setCreateDate(LocalDateTime.now());
            //状态
            mediaFiles.setStatus("1");
            //审核状态
            mediaFiles.setAuditStatus("002003");

            //插入数据库
            int insert = mediaFilesMapper.insert(mediaFiles);

            if(insert<=0){
                log.debug("向数据库保存文件失败,bucket{},objectName{}",bucket,objectName);
                return null;
            }
        }
        return mediaFiles;
    }

7. 完善接口层-Controller

@ApiOperation("上传图片")
@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata")MultipartFile filedata) throws IOException {
    //准备上传文件的信息
    UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
    //原始文件名称
    uploadFileParamsDto.setFilename(filedata.getOriginalFilename());
    //文件大小
    uploadFileParamsDto.setFileSize(filedata.getSize());
    //文件类型
    uploadFileParamsDto.setFileType("001001");

    //创建一个临时文件
    File tempFile = File.createTempFile("minio", ".temp");
    filedata.transferTo(tempFile);
    //文件路径
    String localFilePath = tempFile.getAbsolutePath();

    //机构id
    Long companyId = 1232141425L;

    //调用service上传图片
    mediaFileService.uploadFile(companyId,uploadFileParamsDto,localFilePath);

    return null;
}

8. httpClient测试

### 上传文件
POST {{media_host}}/media/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary

--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="1.png"
Content-Type: application/octet-stream

< D:\c盘移动\1.png

9. Service事务优化

//修改前
@Override
@Transactional
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
    //1.将文件上传到minio
   
    ......

    //2.将文件信息保存到数据库
    MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);

    ......
}
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName) {

    ......

}
public interface MediaFileService {

	......

}
  1. 问题uploadFile方法添加@Transactional注解,但方法中有网络请求的内容(除了数据库之外的网络请求)

    网络请求时间可能很长,数据库链接释放就会变慢,数据库链接可能不够用

  2. 解决:将uploadFile方法上的@Transactional注解去掉,加在其中的addMediaFilesToDb方法

  3. 新问题addMediaFilesToDb方法无法被事务控制

  4. 分析addMediaFilesToDb方法不是通过代理对象去调用的

    image20240602235445730.png

  5. 解决

    将自己注入

    image20240603000225395.png

    将addMediaFilesToDb方法提成接口

    image20240603000352017.png

    通过代理对象调用

image20240603000447988.png

  1. 总结:要想进行事务控制,需同时满足两个条件

    (1) 添加@Transactional注解

    (2) 通过代理对象调用方法

//修改后
@Autowired
private MediaFileService currentProxy;

@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
    //1.将文件上传到minio
   
    ......

    //2.将文件信息保存到数据库
    MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);

    ......
}
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName) {

    ......

}
public interface MediaFileService {
    
    public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);

	......

}