学成在线day05

minio的使用

MinIO提供多个语言版本SDK的支持,下边找到java版本的文档:

地址:https://docs.min.io/docs/java-client-quickstart-guide.html

最低需求Java 1.8或更高版本:

maven依赖如下:

minio使用okhttp进行消息传递:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.4.3</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.8.1</version>
</dependency>

在media-service工程添加此依赖。

参数说明:

需要三个参数才能连接到minio服务。

参数说明
Endpoint对象存储服务的URL
Access KeyAccess key就像用户ID,可以唯一标识你的账户。
Secret KeySecret key是你账户的密码。

官方的示例代码如下:

import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import io.minio.errors.MinioException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class FileUploader {
  public static void main(String[] args)throws IOException, NoSuchAlgorithmException, InvalidKeyException {
    try {
      // Create a minioClient with the MinIO server playground, its access key and secret key.
      MinioClient minioClient =
          MinioClient.builder()
              .endpoint("https://play.min.io")
              .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG")
              .build();
      // Make 'asiatrip' bucket if not exist.
      boolean found =
          minioClient.bucketExists(BucketExistsArgs.builder().bucket("asiatrip").build());
      if (!found) {
        // Make a new bucket called 'asiatrip'.
        minioClient.makeBucket(MakeBucketArgs.builder().bucket("asiatrip").build());
      } else {
        System.out.println("Bucket 'asiatrip' already exists.");
      }
      // Upload '/home/user/Photos/asiaphotos.zip' as object name 'asiaphotos-2015.zip' to bucket
      // 'asiatrip'.
      minioClient.uploadObject(
          UploadObjectArgs.builder()
              .bucket("asiatrip")
              .object("asiaphotos-2015.zip")
              .filename("/home/user/Photos/asiaphotos.zip")
              .build());
      System.out.println(
          "'/home/user/Photos/asiaphotos.zip' is successfully uploaded as "
              + "object 'asiaphotos-2015.zip' to bucket 'asiatrip'.");
    } catch (MinioException e) {
      System.out.println("Error occurred: " + e);
      System.out.println("HTTP trace: " + e.httpTrace());
    }
  }
}

测试minio

package com.xuecheng.media;



import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import io.minio.UploadObjectArgs;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.http.MediaType;
import org.springframework.util.DigestUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;

public class Test {
    //准备minio客户端
    MinioClient minioClient =
            MinioClient.builder()
                    .endpoint("http://192.168.101.65:9000")
                    .credentials("minioadmin", "minioadmin")
                    .build();

    //上传文件
    @org.junit.jupiter.api.Test
    public void upload() throws Exception{
        //根据扩展名取出mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4");
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流
        if(extensionMatch!=null){
            mimeType = extensionMatch.getMimeType();
        }
        //准备数据
        UploadObjectArgs testbucket = UploadObjectArgs.builder()
                .bucket("testbucket")
                .object("test/01/1a.mp4")//文件要存放的路径和文件名
                .filename("D:\\BaiduNetdiskDownload\\学成在线\\学成在线项目—资料\\day05 媒资管理 Nacos Gateway MinIO\\资料\\1.mp4")
                .contentType(mimeType)//指定融媒文件的类型
                .build();
        minioClient.uploadObject(testbucket);
    }

    //删除文件
    @org.junit.jupiter.api.Test
    public void delete() throws Exception{
        //准备数据
        RemoveObjectArgs testbucket = RemoveObjectArgs.builder()
                .bucket("testbucket")
                .object("test/01/1a.mp4")//文件要存放的路径和文件名
                .build();
        minioClient.removeObject(testbucket);
    }

    //查询(下载)文件
    @org.junit.jupiter.api.Test
    public void get() throws Exception{
        GetObjectArgs testbucket = GetObjectArgs.builder()
                .bucket("testbucket")
                .object("test/01/1a.mp4")
                .build();
        //读取数据获取到输入流
        FilterInputStream inputStream = minioClient.getObject(testbucket);
        //创建输出流将数据文件写出
        FileOutputStream outputStream = new FileOutputStream("D:\\BaiduNetdiskDownload\\学成在线\\学成在线项目—资料\\day05 媒资管理 Nacos Gateway MinIO\\资料\\12.mp4");
        //IOUtils将输入流复制到输出流,写入到磁盘
        IOUtils.copy(inputStream,outputStream);
        //使用dm5进行校验
        //读取原本文件的输入流
        FileInputStream fileInputStream1 = new FileInputStream("D:\\BaiduNetdiskDownload\\学成在线\\学成在线项目—资料\\day05 媒资管理 Nacos Gateway MinIO\\资料\\1.mp4");
        //读取下载的文件的输入流
        FileInputStream fileInputStream2 = new FileInputStream("D:\\BaiduNetdiskDownload\\学成在线\\学成在线项目—资料\\day05 媒资管理 Nacos Gateway MinIO\\资料\\12.mp4");

        String s1 = DigestUtils.md5DigestAsHex(fileInputStream1);
        String s2 = DigestUtils.md5DigestAsHex(fileInputStream2);
        if (s1.equalsIgnoreCase(s2)){
            System.out.println("下载完成");
        }
    }
}

根基扩展名获取文件的融媒类型的依赖:

<!--        根据扩展名取mimetype-->
        <dependency>
            <groupId>com.j256.simplemagic</groupId>
            <artifactId>simplemagic</artifactId>
            <version>1.17</version>
        </dependency>

上传图片

初始化minio上传

环境准备

  1. 在minio配置bucket,bucket名称为:mediafiles,并设置bucket的权限为公开。
  2. 在nacos配置中minio的相关信息,进入media-service-dev.yaml:
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.101.65:3306/xcplus_media?serverTimezone=UTC&userUnicode=true&useSSL=false&
    username: root
    password: mysql
  cloud:
   config:
    override-none: true

minio:
  endpoint: http://192.168.101.65:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucket:
    files: mediafiles
    videofiles: video
xxl:
  job:
    admin: 
      addresses: http://192.168.101.65:8088/xxl-job-admin
    executor:
      appname: media-process-service
      address: 
      ip: 
      port: 9999
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
    accessToken: default_token

videoprocess:
 ffmpegpath: D:/soft/ffmpeg/ffmpeg.exe

由于minio客户端需要配置相关信息,为了避免死代码将数据配置在配置文件中:

minio:
  endpoint: http://192.168.101.65:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucket:
    files: mediafiles
    videofiles: video

由于写在配置文件中需要使用配置类读取配置文件,同时要建立一个客户端的Bean容器,方便调用:

package com.xuecheng.media.config;

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description minio配置
 * @author Mr.M
 * @date 2022/9/12 19:32
 * @version 1.0
 */
 @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;
 }
}

接口定义

根据需求分析,下边进行接口定义,此接口定义为一个通用的上传文件接口,可以上传图片或其它文件。

首先分析接口:

请求地址:/media/upload/coursefile

请求内容:Content-Type: multipart/form-data;

form-data; name="filedata"; filename="具体的文件名称", 此为和前端协商的文件名称,用于保存

响应参数:文件信息,如下

{
  "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
}

为了方便以后对返回数据的修改,定义一个dto用于返回数据

package com.xuecheng.media.model.dto;

import com.xuecheng.media.model.po.MediaFiles;
import lombok.Data;
import lombok.ToString;

/**
 * @description 上传普通文件成功响应结果
 * @author Mr.M
 * @date 2022/9/12 18:49
 * @version 1.0
 */
 @Data
public class UploadFileResultDto extends MediaFiles {
  

}

定义接口如下:

@ApiOperation("上传文件")
@RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile upload) throws IOException {

    return null;
}

代码实现

注入客户端,读取配置文件中的桶名称:

  //minio客户端
  @Autowired
  MinioClient minioClient;
  //读取配置文件数据
    @Value("${minio.bucket.files}")
    private String bucketFiles;

代码实现:(service代码)

  /**
     * 上传图片
     * @param companyId 机构id
     * @param uploadFileParamsDto 上传文件信息
     * @param localFilePath 文件磁盘路径
     * @return
     */
    @Transactional
    @Override
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
        File file = new File(localFilePath);
        if (!file.exists()) {
            XueChengPlusException.cast("文件不存在");
        }
        //上传图片到minio
        //获取文件后缀名
        String filename = uploadFileParamsDto.getFilename();//获取文件名
        String extension = filename.substring(filename.lastIndexOf("."));
        //获取文件扩展名
        //根据扩展名取出mimeType
        String mimeType = getMimeType(extension);
        //拼接文件路径
        //bucket/日期数据/文件md5.文件后缀名
        String datePath = getDatePath();
        //获取文件MD5值
        String fileMd5 = getFileMd5(localFilePath);
        //拼装文件路径
//        String objectName = bucketFiles + "/" + datePath + fileMd5 + extension;
        String objectName =datePath + fileMd5 + extension;
        //准备数据
        Boolean result = updataFile(mimeType, bucketFiles, objectName, localFilePath);
        if (!result){
            throw new XueChengPlusException("上传文件失败");
        }

        //保存图片数据到数据库
        //文件大小
        uploadFileParamsDto.setFileSize(file.length());
        //将文件信息存储到数据库
        MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucketFiles, objectName);
        //准备返回数据
        UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
        BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
        return uploadFileResultDto;

    }

    /**
     * @description 将文件信息添加到文件表
     * @param companyId  机构id
     * @param fileMd5  文件md5值
     * @param uploadFileParamsDto  上传文件的信息
     * @param bucket  桶
     * @param objectName 对象名称
     * @return com.xuecheng.media.model.po.MediaFiles
     * @author Mr.M
     * @date 2022/10/12 21:22
     */
    @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);
            mediaFiles.setId(fileMd5);
            mediaFiles.setFileId(fileMd5);
            mediaFiles.setCompanyId(companyId);
            mediaFiles.setUrl("/" + bucket + "/" + objectName);
            mediaFiles.setBucket(bucket);
            mediaFiles.setFilePath(objectName);
            mediaFiles.setCreateDate(LocalDateTime.now());
            mediaFiles.setAuditStatus("002003");
            mediaFiles.setStatus("1");
            //保存文件信息到文件表
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert < 0) {
                log.error("保存文件信息到数据库失败,{}",mediaFiles.toString());
                XueChengPlusException.cast("保存文件信息失败");
            }
            log.debug("保存文件信息到数据库成功,{}",mediaFiles.toString());

        }
        return mediaFiles;

    }

    //获取文件的md5值
    private String getFileMd5(String localFilePath) {
        String fileMd5;
        try {
            //文件的路径,获取文件的md5值
            fileMd5 = DigestUtils.md5DigestAsHex(new FileInputStream(localFilePath));
        } catch (Exception e) {
            log.error("获取文件md5值失败:{},具体错误信息:{}",e.getMessage(),e);
            throw new RuntimeException(e);

        }
        return fileMd5;
    }

    //获取日期分级
    private String getDatePath() {
        //获取时间格式化器
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        //将当前日期按照格式化器进行转换
        String dateString = sdf.format(new Date());
        //将日期字符串进行转换 - 转换为 /
        return dateString.replace("-","/")+"/";
    }

    //上传文件
    private boolean updataFile(String mimeType,String bucket,String objectName,String localFilePath) {
        try {
            UploadObjectArgs testbucket = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)//文件要存放的路径和文件名
                    .filename(localFilePath)
                    .contentType(mimeType)//指定融媒文件的类型
                    .build();
            minioClient.uploadObject(testbucket);
            return true;
        }catch (Exception e) {
            log.error("上传文件失败",e);
            return false;
        }
    }

    private static String getMimeType(String fileName_lest) {
        //判断是否为空
        if (fileName_lest==null){
            fileName_lest = "";
        }
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(fileName_lest);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流
        if(extensionMatch!=null){
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }

使用dto存储相关数据:

package com.xuecheng.media.model.dto;

import com.xuecheng.media.model.po.MediaFiles;
import lombok.Data;
import lombok.ToString;

/**
 * @description 上传普通文件请求参数
 * @author Mr.M
 * @date 2022/9/12 18:49
 * @version 1.0
 */
 @Data
public class UploadFileParamsDto {

 /**
  * 文件名称
  */
 private String filename;


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

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

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

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



}

修改接口代码:

@ApiOperation("上传文件")
@RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata) throws IOException {

     //将前端传来的文件暂存到硬盘,用来获取文件路径
    Long companyId = 1232141425L;
    UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
    //文件大小
    uploadFileParamsDto.setFileSize(filedata.getSize());
    //图片
    uploadFileParamsDto.setFileType("001001");
    //文件名称
    uploadFileParamsDto.setFilename(filedata.getOriginalFilename());//文件名称
    //文件大小
    long fileSize = filedata.getSize();
    uploadFileParamsDto.setFileSize(fileSize);
    //创建临时文件
    File tempFile = File.createTempFile("minio", "temp");
    //上传的文件拷贝到临时文件
    filedata.transferTo(tempFile);
    //文件路径
    String absolutePath = tempFile.getAbsolutePath();
    //上传文件
    UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, absolutePath);

    return uploadFileResultDto;
}

Http测试

### 上传文件
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:/www/1.png

改进事物

由于事物注解添加在uploadFile方法上,由于需要进行网络上传,可能会占用大量时间造成数据库线程耗尽,因此修改到提交数据库方法上,但是测试得到事物失败

我们人为在int insert = mediaFilesMapper.insert(mediaFiles);下边添加一个异常代码int a=1/0;

测试是否事务控制。很遗憾,事务控制失败。

方法上已经添加了@Transactional注解为什么该方法不能被事务控制呢?

如果是在uploadFile方法上添加@Transactional注解就可以控制事务,去掉则不行。

现在的问题其实是一个非事务方法调同类一个事务方法,事务无法控制,这是为什么?

下边分析原因:

如果在uploadFile方法上添加@Transactional注解,代理对象执行此方法前会开启事务,如下图:

如果在uploadFile方法上没有@Transactional注解,代理对象执行此方法前不进行事务控制,如下图:

所以判断该方法是否可以事务控制必须保证是通过代理对象调用此方法,且此方法上添加了@Transactional注解。

现在在addMediaFilesToDb方法上添加@Transactional注解,也不会进行事务控制是因为并不是通过代理对象执行的addMediaFilesToDb方法。为了判断在uploadFile方法中去调用addMediaFilesToDb方法是否是通过代理对象去调用,我们可以打断点跟踪。

我们发现在uploadFile方法中去调用addMediaFilesToDb方法不是通过代理对象去调用。

事物生效的条件是,注解和代理对象同时成立

如何解决呢?通过代理对象去调用addMediaFilesToDb方法即可解决。

在MediaFileService的实现类中注入MediaFileService的代理对象,如下:

@Autowired
MediaFileService currentProxy;

将addMediaFilesToDb方法提成接口。

/**
 * @description 将文件信息添加到文件表
 * @param companyId  机构id
 * @param fileMd5  文件md5值
 * @param uploadFileParamsDto  上传文件的信息
 * @param bucket  桶
 * @param objectName 对象名称
 * @return com.xuecheng.media.model.po.MediaFiles
 * @author Mr.M
 * @date 2022/10/12 21:22
 */

public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);

调用addMediaFilesToDb方法的代码处改为如下:

.....
//写入文件表
MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);
 ....

再次测试事务是否可以正常控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值