学成day4(文件上传,@Transactional,分块(视频)文件上传,下载,合并)

一、文件上传

1、接口定义

首先分析接口:

请求地址:/media/upload/coursefile

请求参数:

Content-Type: multipart/form-data;boundary=.....

FormData:   filedata=??

定义接口如下:

 @RequestMapping(value = "/upload/coursefile",consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
 public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata,//文件数据
                                   @RequestParam("folder") String folder,//文件路径
                                   @RequestParam("objectName") String objectName){
        return mediaFileService.uploadFile();
    }

文件数据可能是各种格式的,所以用MultipartFile接收,并加上注解@RequestPart

由于接口定义时未指定是Post请求还是Get请求,所以该接口用@RequestMapping注解

2、service层实现业务逻辑

代码比较复杂,我们分成两部分说明

(1)将文件上传到Minio的桶中

要上传到Minio,首先需要获取桶的名称和桶内的文件路径

桶的名称我们通过读取配置信息获取

    @Value("${minio.bucket.files}")
    private String bucket_Files;

桶内的文件路径又分为两部分,folder和objectName

folder是目录名,如果参数中folder不为空,直接用就行,如果为空,就使用默认的方法(如下)构建目录名

private String getFileFolder(Date date, boolean year, boolean month, boolean day) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        //获取当前日期字符串
        String dateString = sdf.format(new Date());
        //取出年、月、日
        String[] dateStringArray = dateString.split("-");
        StringBuffer folderString = new StringBuffer();
        if (year) {
            folderString.append(dateStringArray[0]);
            folderString.append("/");
        }
        if (month) {
            folderString.append(dateStringArray[1]);
            folderString.append("/");
        }
        if (day) {
            folderString.append(dateStringArray[2]);
            folderString.append("/");
        }
        return folderString.toString();
    }

最后还要对folder做检查,因为可能不带"/"导致路径格式不正确

if (folder.indexOf("/") < 0) {
            folder = folder + "/";
        }

与此类似,objectName不为空就直接使用,为空则用MD5值+文件扩展名的形式构建

        //生成文件id,文件的md5值
        String fileId = DigestUtils.md5Hex(bytes);
        //构造objectname
        if (StringUtils.isEmpty(objectName)) {
            objectName = fileId + filename.substring(filename.lastIndexOf("."));
        }

最后将folder和objectName合并即为最后的路径名

objectName = folder + objectName;

上传

try {
            //转为流
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucket_Files).object(objectName)
                    //-1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000,
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                    .contentType(uploadFileParamsDto.getContentType())
                    .build();


            minioClient.putObject(putObjectArgs);

(2)将数据插入mediaFile表中

            //从数据库查询文件
            mediaFiles = mediaFilesMapper.selectById(fileId);
            if (mediaFiles == null) {
                mediaFiles = new MediaFiles();
                //拷贝基本信息
                BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
                mediaFiles.setId(fileId);
                mediaFiles.setFileId(fileId);
                mediaFiles.setCompanyId(companyId);
                mediaFiles.setUrl("/" + bucket_Files + "/" + objectName);
                mediaFiles.setBucket(bucket_Files);
                mediaFiles.setCreateDate(LocalDateTime.now());
                mediaFiles.setStatus("1");
                mediaFiles.setAuditStatus("002003");
                //保存文件信息到文件表
                int insert = mediaFilesMapper.insert(mediaFiles);
                if (insert < 0) {
                    XueChengPlusException.cast("保存文件信息失败");
                }
                UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
                BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
                return uploadFileResultDto;

(3)代码优化

显然上面的业务逻辑代码过于繁杂,我们进行拆分优化,本身比较简单,直接上代码

先将上传文件到Minio的代码抽离

/**
     * @param bytes       文件字节数组
     * @param bucket      桶
     * @param objectName  对象名称
     * @param contentType 内容类型
     * @return void
     * @description 将文件写入minIO
     * @author Mr.M
     * @date 2022/10/12 21:22
     */
    public void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName, String contentType) {
        try {
            //转为流
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucket_Files).object(objectName)
                    //-1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000,
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                    .contentType(contentType)
                    .build();
            minioClient.putObject(putObjectArgs);
            //从数据库查询文件
        } catch (Exception e) {
            log.error("上传文件失败:{}", e.getMessage());
            XueChengPlusException.cast("上传过程中出错");
        }
    }

再将写入文件信息到数据库的代码抽离

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

    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.setCreateDate(LocalDateTime.now());
            mediaFiles.setStatus("1");
            mediaFiles.setAuditStatus("002003");
            //保存文件信息到文件表
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert < 0) {
                XueChengPlusException.cast("保存文件信息失败");
            }
        }
        return mediaFiles;
    }

 最后修改一下upload方法

    @Transactional
    @Override
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {

        //生成文件id,文件的md5值
        String fileId = DigestUtils.md5Hex(bytes);
        //文件名称
        String filename = uploadFileParamsDto.getFilename();
        //构造objectname
        if (StringUtils.isEmpty(objectName)) {
            objectName = fileId + filename.substring(filename.lastIndexOf("."));
        }
        if (StringUtils.isEmpty(folder)) {
            //通过日期构造文件存储路径
            folder = getFileFolder(new Date(), true, true, true);
        } else if (folder.indexOf("/") < 0) {
            folder = folder + "/";
        }
        //对象名称
        objectName = folder + objectName;
        MediaFiles mediaFiles = null;
        try {
            addMediaFilesToMinIO(bytes,bucket_Files,objectName,uploadFileParamsDto.getContentType());
            mediaFiles = addMediaFilesToDb(companyId,fileId,uploadFileParamsDto,bucket_Files,objectName);
            UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
            BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
            return uploadFileResultDto;
        } catch (Exception e){
            XueChengPlusException.cast("上传过程中出错");
        }
        return null;
    }

二、@Transactional

可以简单认为,在数据库进行两次或多次写操作时,需要加上@Transactional注解表示这是一个事务,这跟事务的基本要素有关

事务基本要素

  • 原子性(Atomicity): 事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
  • 一致性(Consistency): 事务开始前和结束后,数据库的完整性约束没有被破坏。比如A向B转账,不可能A扣了钱,B却没收到。
  • 隔离性(Isolation): 同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
  • 持久性(Durability): 事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

注意:该注解一般加载接口的实现类上的public方法上

 如果想要被事务控制,有两个条件

1、方法被代理对象调用

2、方法上加入@Transactional

当无事务方法调用有事务方法时是否是用代理对象调用?

分析源码可知,当无事务方法调用有事务方法时,spring会向其传入被代理对象(即impl对象),而不是代理对象,这样调用时不满足方法被代理对象调用这个点,所以有事务方法此时不能被事务控制

解决方案:将代理对象注入serviceImpl中,通过代理对象去调用有事务方法

    @Autowired
    MediaFileService currentProxy;

什么时候spring事务会失效

  1. 在方法中捕获异常而没有抛出去
  2. 非事务方法调用事务方法
  3. 事务方法内部调用事务方法
  4. @Transactional标记的不是public方法
  5. 抛出的异常与rollbackfor指定的异常(默认为RuntimeException)不匹配
  6. 数据库表不支持事务
  7. spring的传播行为导致事务失效,有些传播行为不支持事务

三、分块(视频)文件上传,下载,合并

视频上传流程图 

 需要注意的点

一、合并视频上传成功后,要把分块文件和合并文件删除

二、上传分块前要检查分块是否存在,检查方式是通过字符串拼接找到分块的位置,查看是

        否存在

三、合并文件前要下载所有分块文件到临时文件存放点,然后将分块文件内容写入合并文件

        中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值