3.2-媒资管理之MinIo分布式文件系统+上传图片

媒资管理

3 分布式文件系统

3.1 什么是分布式文件系统

要理解分布式文件系统首先了解什么是文件系统。
查阅百度百科:
在这里插入图片描述

   文件系统是负责管理和存储文件的系统软件,操作系统通过文件系统提供的接口去存取文件,用户通过操作系统访问磁盘上的文件。

下图指示了文件系统所处的位置:
在这里插入图片描述

常见的文件系统:FAT16/FAT32、NTFS、HFS、UFS、APFS、XFS、Ext4等 。
现在有个问题,一此短视频平台拥有大量的视频、图片,这些视频文件、图片文件该如何存储呢?如何存储可以满足互联网上海量用户的浏览。
今天讲的分布式文件系统就是海量用户查阅海量文件的方案。
我们阅读百度百科去理解分布式文件系统的定义:
在这里插入图片描述

通过概念可以简单理解为:一个计算机无法存储海量的文件,通过网络将若干计算机组织起来共同去存储海量的文件,去接收海量用户的请求,这些组织起来的计算机通过网络进行通信,如下图:
在这里插入图片描述

好处:
1、一台计算机的文件系统处理能力扩充到多台计算机同时处理。
2、一台计算机挂了还有另外副本计算机提供数据。
3、每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度。
市面上有哪些分布式文件系统的产品呢?
1、NFS
阅读百度百科:

在这里插入图片描述

特点:
1)在客户端上映射NFS服务器的驱动器。
2)客户端通过网络访问NFS服务器的硬盘完全透明。

2、GFS
在这里插入图片描述

1)GFS采用主从结构,一个GFS集群由一个master和大量的chunkserver组成。
2)master存储了数据文件的元数据,一个文件被分成了若干块存储在多个chunkserver中。
3)用户从master中获取数据元信息,向chunkserver存储数据。

  1. HDFS
    HDFS,是Hadoop Distributed File System的简称,是Hadoop抽象文件系统的一种实现。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。 HDFS的文件分布在集群机器上,同时提供副本进行容错及可靠性保证。例如客户端写入读取文件的直接操作都是分布在集群各个机器上的,没有单点性能压力。
    下图是HDFS的架构图:
    在这里插入图片描述

1)HDFS采用主从结构,一个HDFS集群由一个名称结点和若干数据结点组成。
2) 名称结点存储数据的元信息,一个完整的数据文件分成若干块存储在数据结点。
3)客户端从名称结点获取数据的元信息及数据分块的信息,得到信息客户端即可从数据块来存取数据。

4、云计算厂家
阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。其数据设计持久性不低于 99.9999999999%(12 个 9),服务设计可用性(或业务连续性)不低于 99.995%。
官方网站:https://www.aliyun.com/product/oss
百度对象存储BOS提供稳定、安全、高效、高可扩展的云存储服务。您可以将任意数量和形式的非结构化数据存入BOS,并对数据进行管理和处理。BOS支持标准、低频、冷和归档存储等多种存储类型,满足多场景的存储需求。
官方网站:https://cloud.baidu.com/product/bos.html

3.2 MinIO

3.2.1 介绍

本项目采用MinIO构建分布式文件系统,MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用,它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。
它一大特点就是轻量,使用简单,功能强大,支持各种平台,单个文件最大5TB,兼容 Amazon S3接口,提供了 Java、Python、GO等多版本SDK支持。
官网:https://min.io
中文:https://www.minio.org.cn/,http://docs.minio.org.cn/docs/

MinIO集群采用去中心化共享架构,每个结点是对等关系,通过Nginx可对MinIO进行负载均衡访问。
去中心化有什么好处?
在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。
它将分布在不同服务器上的多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。如下图:
在这里插入图片描述

Minio使用纠删码技术来保护数据,它是一种恢复丢失和损坏数据的数学算法,它将数据分块冗余的分散存储在各各节点的磁盘上,所有的可用磁盘组成一个集合,上图由8块硬盘组成一个集合,当上传一个文件时会通过纠删码算法计算对文件进行分块存储,除了将文件本身分成4个数据块,还会生成4个校验块,数据块和校验块会分散的存储在这8块硬盘上。
使用纠删码的好处是即便丢失一半数量(N/2)的硬盘,仍然可以恢复数据。 比如上边集合中有4个以内的硬盘损害仍可保证数据恢复,不影响上传和下载,如果多于一半的硬盘坏了则无法恢复。

下边输入http://localhost:9000进行登录,账号和密码为:minioadmin/minioadmin

在这里插入图片描述

登录成功:
在这里插入图片描述

下一步创建bucket,桶,它相当于存储文件的目录,可以创建若干的桶。
在这里插入图片描述

输入bucket的名称,点击“CreateBucket”,创建成功
在这里插入图片描述

点击“upload”上传文件。
下边上传几个文件
在这里插入图片描述

3.2.3 测试Docker环境

开发阶段和生产阶段统一使用Docker下的MINIO。
在下发的虚拟机中已安装了MinIO的镜像和容器,执行sh /data/soft /restart.sh启动Docker下的MinIO
启动完成登录MinIO查看是否正常。
访问http://192.168.101.65:9000
在这里插入图片描述

本项目创建两个buckets:
mediafiles: 普通文件
video:视频文件

3.2.4 SDK
3.2.4.1上传文件

MinIO提供多个语言版本SDK的支持,下边找到java版本的文档:
地址:https://docs.min.io/docs/java-client-quickstart-guide.html
最低需求Java 1.8或更高版本:
maven依赖如下:
XML

<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 Key Access key就像用户ID,可以唯一标识你的账户。
Secret Key Secret key是你账户的密码。

官方的示例代码如下:

Java
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());}}
}

参考示例在media-service工程中 测试上传文件功能,
首先创建一个用于测试的bucket
在这里插入图片描述

点击“Manage”修改bucket的访问权限
在这里插入图片描述

选择public权限
在这里插入图片描述

在xuecheng-plus-media-service工程 的test下编写测试代码如下:

Java
package com.xuecheng.media;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;/*** @description 测试MinIO* @author Mr.M* @date 2022/9/11 21:24* @version 1.0*/
public class MinioTest {static MinioClient minioClient =MinioClient.builder().endpoint("http://192.168.101.65:9000").credentials("minioadmin", "minioadmin").build();//上传文件@Testpublic  void upload() {try {UploadObjectArgs testbucket = UploadObjectArgs.builder().bucket("testbucket")
//                    .object("test001.mp4").object("001/test001.mp4")//添加子目录.filename("D:\\develop\\upload\\1mp4.temp").contentType("video/mp4")//默认根据扩展名确定文件内容类型,也可以指定.build();minioClient.uploadObject(testbucket);System.out.println("上传成功");} catch (Exception e) {e.printStackTrace();System.out.println("上传失败");}}}

执行upload方法,分别测试向桶的根目录上传文件以及子目录上传文件。
上传成功,通过web控制台查看文件,并预览文件。
说明:
设置contentType可以通过com.j256.simplemagic.ContentType枚举类查看常用的mimeType(媒体类型)
通过扩展名得到mimeType,代码如下:

Java//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4");String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流

完善上边的代码 如下:

Java
@Testpublic  void upload() {//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4");String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}try {UploadObjectArgs testbucket = UploadObjectArgs.builder().bucket("testbucket")
//                    .object("test001.mp4").object("001/test001.mp4")//添加子目录.filename("D:\\develop\\upload\\1mp4.temp").contentType(mimeType)//默认根据扩展名确定文件内容类型,也可以指定.build();minioClient.uploadObject(testbucket);System.out.println("上传成功");} catch (Exception e) {e.printStackTrace();System.out.println("上传失败");}}
3.2.4.2 删除文件

下边测试删除文件
参考:https://docs.min.io/docs/java-client-api-reference#removeObject

Java
@Test
public void delete(){try {minioClient.removeObject(RemoveObjectArgs.builder().bucket("testbucket").object("001/test001.mp4").build());System.out.println("删除成功");} catch (Exception e) {e.printStackTrace();System.out.println("删除失败");}
}
3.2.4.3 查询文件

通过查询文件查看文件是否存在minio中。
参考:https://docs.min.io/docs/java-client-api-reference#getObject

Java
//查询文件
@Test
public void getFile() {GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("test001.mp4").build();try(FilterInputStream inputStream = minioClient.getObject(getObjectArgs);FileOutputStream outputStream = new FileOutputStream(new File("D:\\develop\\upload\\1_2.mp4"));) {IOUtils.copy(inputStream,outputStream);} catch (Exception e) {e.printStackTrace();}
}

校验文件的完整性,对文件计算出md5值,比较原始文件的md5和目标文件的md5,一致则说明完整

Java
//校验文件的完整性对文件的内容进行md5
FileInputStream fileInputStream1 = new FileInputStream(new File("D:\\develop\\upload\\1.mp4"));
String source_md5 = DigestUtils.md5Hex(fileInputStream1);
FileInputStream fileInputStream = new FileInputStream(new File("D:\\develop\\upload\\1a.mp4"));
String local_md5 = DigestUtils.md5Hex(fileInputStream);
if(source_md5.equals(local_md5)){System.out.println("下载成功");
}

4 上传图片

4.1 需求分析

4.1.1 业务流程

课程图片是宣传课程非常重要的信息,在新增课程界面上传课程图片,也可以修改课程图片。
如下图:
在这里插入图片描述

上传课程图片总体上包括两部分:
1、上传课程图片前端请求媒资管理服务将文件上传至分布式文件系统,并且在媒资管理数据库保存文件信息。
2、上传图片成功保存图片地址到课程基本信息表中。
详细流程如下:
在这里插入图片描述

1、前端进入上传图片界面
2、上传图片,请求媒资管理服务。
3、媒资管理服务将图片文件存储在MinIO。
4、媒资管理记录文件信息到数据库。
5、前端请求内容管理服务保存课程信息,在内容管理数据库保存图片地址。

4.1.2 数据模型

涉及到的数据表有:课程信息表中的图片字段、媒资数据库的文件表,下边主要看媒资数据库的文件表。
在这里插入图片描述

各字段描述如下:
在这里插入图片描述

4.2 准备环境

首先在minio配置bucket,bucket名称为:mediafiles,并设置bucket的权限为公开。
在这里插入图片描述

在nacos配置中minio的相关信息,进入media-service-dev.yaml:
在这里插入图片描述

配置信息如下:
YAML

minio:endpoint: http://192.168.101.65:9000accessKey: minioadminsecretKey: minioadminbucket:files: mediafilesvideofiles: video

在media-service工程编写minio的配置类:

Java
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;@Beanpublic MinioClient minioClient() {MinioClient minioClient =MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();return minioClient;}
}

4.3 接口定义

根据需求分析,下边进行接口定义,此接口定义为一个通用的上传文件接口,可以上传图片或其它文件。
首先分析接口:
请求地址:/media/upload/coursefile
请求内容:Content-Type: multipart/form-data;
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
}

定义上传响应模型类:

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

定义接口如下:

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

接口定义好后可以用httpclient工具测试一下
使用httpclient测试

### 上传文件
POST {{media_host}}/media/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="1.jpg"
Content-Type: application/octet-stream< d:/develop/upload/1.jpg

4.4 接口开发

4.4.1 DAO开发

根据需求分析DAO层实现向media_files表插入一条记录,使用media_files表生成的mapper即可。

4.4.2 Service开发

Service方法需要提供一个更加通用的保存文件的方法。
定义请求参数类:

Java
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;}

定义service方法:

Java
/*** 上传文件* @param companyId 机构id* @param uploadFileParamsDto 上传文件信息* @param localFilePath 文件磁盘路径* @return 文件信息*/
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath);

实现方法如下:

Java@Autowired
MinioClient minioClient;@Autowired
MediaFilesMapper mediaFilesMapper;//普通文件桶
@Value("${minio.bucket.files}")
private String bucket_Files;//获取文件默认存储目录路径 年/月/日
private String getDefaultFolderPath() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");String folder = sdf.format(new Date()).replace("-", "/")+"/";return folder;
}//获取文件的md5
private String getFileMd5(File file) {try (FileInputStream fileInputStream = new FileInputStream(file)) {String fileMd5 = DigestUtils.md5Hex(fileInputStream);return fileMd5;} catch (Exception e) {e.printStackTrace();return null;}
}private String getMimeType(String extension){if(extension==null)extension = "";//根据扩展名取出mimeTypeContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);//通用mimeType,字节流String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;if(extensionMatch!=null){mimeType = extensionMatch.getMimeType();}return mimeType;
}
/*** @description 将文件写入minIO* @param localFilePath  文件地址* @param bucket  桶* @param objectName 对象名称* @return void* @author Mr.M* @date 2022/10/12 21:22*/
public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName) {try {UploadObjectArgs testbucket = UploadObjectArgs.builder().bucket(bucket).object(objectName).filename(localFilePath).contentType(mimeType).build();minioClient.uploadObject(testbucket);log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucket,objectName);System.out.println("上传成功");return true;} catch (Exception e) {e.printStackTrace();log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}",bucket,objectName,e.getMessage(),e);XueChengPlusException.cast("上传文件到文件系统失败");}return false;
}/*** @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;}
@Transactional
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {File file = new File(localFilePath);if (!file.exists()) {XueChengPlusException.cast("文件不存在");}//文件名称String filename = uploadFileParamsDto.getFilename();//文件扩展名String extension = filename.substring(filename.lastIndexOf("."));//文件mimeTypeString mimeType = getMimeType(extension);//文件的md5值String fileMd5 = getFileMd5(file);//文件的默认目录String defaultFolderPath = getDefaultFolderPath();//存储到minio中的对象名(带目录)String  objectName = defaultFolderPath + fileMd5 + exension;//将文件上传到minioboolean b = addMediaFilesToMinIO(localFilePath, mimeType, bucket_files, objectName);//文件大小uploadFileParamsDto.setFileSize(file.length());//将文件信息存储到数据库MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);//准备返回数据UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);return uploadFileResultDto;}
4.4.3 完善接口层

完善接口层代码 :

Java
@ApiOperation("上传文件")
@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile upload,@RequestParam(value = "folder",required=false) String folder,@RequestParam(value = "objectName",required=false) String objectName) 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;
}
4.4.4 接口测试

1、首先使用httpclient测试

Java
### 上传文件
POST {{media_host}}/media/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="1.jpg"
Content-Type: application/octet-stream< d:/develop/upload/1.jpg

2、再进行前后端联调测试
在新增课程、编辑课程界面上传图,保存课程信息后再次进入编辑课程界面,查看是否可以正常保存课程图片信息。
在这里插入图片描述

上图图片完成后,进入媒资管理,查看文件列表中是否有刚刚上传的图片信息。
在这里插入图片描述

4.4.5 Service事务优化

上边的service方法优化后并测试通过,现在思考关于uploadFile方法的是否应该开启事务。
目前是在uploadFile方法上添加@Transactional,当调用uploadFile方法前会开启数据库事务,如果上传文件过程时间较长那么数据库的事务持续时间就会变长,这样数据库链接释放就慢,最终导致数据库链接不够用。
我们只将addMediaFilesToDb方法添加事务控制即可,uploadFile方法上的@Transactional注解去掉。
优化后如下:

Java
@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;}

我们人为在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的代理对象,如下:

Java
@Autowired
MediaFileService currentProxy;

将addMediaFilesToDb方法提成接口。

Java
/*** @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方法的代码处改为如下:

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/673628.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript基础第三天

JavaScript 基础第三天 今天我们学习for循环、while循环、终止循环和无限循环。 1. for 循环 1.1. 语法 // 1. 语法格式 // for(起始值; 结束条件; 累加器) { // // 要重复执行的代码 // }1.2. 示例代码 let sum 0; for (let i 0; i < 100; i) {sum i; } alert(&q…

jsp康养小镇管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP康养小镇管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&a…

[超分辨率重建]ESRGAN算法训练自己的数据集过程

一、下载数据集及项目包 1. 数据集 1.1 文件夹框架的介绍&#xff0c;如下图所示&#xff1a;主要有train和val&#xff0c;分别有高清&#xff08;HR&#xff09;和低清&#xff08;LR&#xff09;的图像。 1.2 原图先通过分割尺寸的脚本先将数据集图片处理成两个相同的图像…

JavaScript中call、apply、bind方法的应用与区别

在JavaScript中&#xff0c;call、apply和bind是函数的三个重要方法&#xff0c;它们虽然功能不同&#xff0c;但都可以用来改变函数的执行上下文或者传递参数。本文将分别介绍call、apply和bind方法的应用和区别&#xff0c;并附带示例代码。 一、call方法 call方法的作用是…

Spring GateWay

概述简介 能干什么 反向代理 鉴权 流量控制 熔断 日志监控 Spring Cloud Gateway 与Zuul的区别 在SpringCloud Finchley正式版之前&#xff0c;Spring Cloud推荐的网关是 Netflix提供的Zuul: 1、Zuul 1.x&#xff0c;是一个基于阻塞Ⅳ/O的APl Gateway 2、Zuul 1.x基于Servl…

如何保持mac苹果电脑系统在最佳状态?不卡顿

苹果电脑一直以其卓越的性能和用户友好的操作系统而备受欢迎。然而电脑上的文件、应用程序和缓存可能会逐渐积累&#xff0c;导致性能下降。为了确保你的苹果电脑保持最佳状态&#xff0c;高效清理是至关重要的一步。在本文中&#xff0c;我们将分享一些如何清理苹果电脑更高效…

npm 上传一个自己的应用(1) 搭建一个项目环境

上文 在npm官网中注册一个账号并登录 带着大家创建了一个npm账号 我们先登录官网 然后 我们在自己电脑中创建一个文件夹 这个文件夹叫什么没有太大所谓 我这里直接叫 grnpmtext 然后 我们在这个文件夹中初始化一个项目 终端输入 npm initpackage name 要我们输入项目的名称 …

Java项目使用jasypt加密和解密配置文件中关键信息

一、使用背景 项目中application.yml 配置文件中&#xff0c;如数据库、redis、加密算法的私钥等各种配置的username&#xff0c;password的值都是明文的&#xff0c;其实存在一定的安全隐患&#xff0c;如果被人拿到这些配置文件&#xff0c;将直接对系统安全构成极大威胁&…

imgaug数据增强神器:增强器一览

官网&#xff1a;imgaug — imgaug 0.4.0 documentationhttps://imgaug.readthedocs.io/en/latest/ github:GitHub - aleju/imgaug: Image augmentation for machine learning experiments. imgaug数据增强神器&#xff1a;增强器一览_iaa 图像增强改变颜色-CSDN博客文章浏览阅…

Python环境下基于最大离散重叠小波变换和支持向量回归的金融时间序列预测

金融时间序列具有非线性、高频性、随机性等特点&#xff0c;其波动情况不仅与当前股票市场、房地产市场、贸易市场等有强联动性&#xff0c;而且大幅度起伏对于其他市场有较大的影响和冲击。由于金融市场受多种因素影响且各影响因素间也存在一定复杂动态交互关系&#xff0c;导…

开源项目的三年,我的项目经历了哪些变化?

0.前言 自己一个项目写了三年&#xff0c;到底写了什么东西了&#xff0c;这个项目经历了哪些变化呢&#xff1f;其中的心路历程如何&#xff1f; 兄弟们&#xff0c;要是感觉我的项目有价值&#xff0c;去b站给俺点点关注呐。我更新的更快。点击下面的了解就可以跳转去b站。…

我的docker随笔43:问答平台answer部署

本文介绍开源问答社区平台Answer的容器化部署。 起因 笔者一直想搭建一个类似stack overflower这样的平台&#xff0c;自使用了Typora&#xff0c;就正式全面用MarkdownTyporagit来积累自己的个人知识库&#xff0c;但没有做到web化&#xff0c;现在也还在探索更好的方法。 无…

Spring + Tomcat项目中nacos配置中文乱码问题解决

实际工作的时候碰到了nacos中文乱码的问题&#xff0c;一顿排查最终还是调源码解决了。下面为具体的源码流程&#xff0c;有碰到的可以参考下。 对于nacos配置来说&#xff0c;初始主要源码就在NacosConfigService类中。里面有初始化获取配置content以及设置对应监听器的操作。…

配备Apple T2 安全芯片的 Mac 机型及T2芯片mac电脑U盘装系统教程

T2 芯片为 Mac 提供了一系列功能&#xff0c;例如加密储存和安全启动功能、增强的图像信号处理功能&#xff0c;以及适用于触控 ID 数据的安全保护功能。哪些电脑配备了 T2 安全芯片呢&#xff0c;T2芯片mac电脑又如何重装系统呢&#xff1f;跟随小编一起来看看吧&#xff01; …

集群及LVS简介、LVSNAT模式原理、LVSNAT模式配置、LVSDR模式原理、LVSDR模式配置、LVS错误排查

集群 将很多机器组织到一起&#xff0c;作为一个整体对外提供服务 集群在扩展性、性能方面都可以做到很灵活 集群分类&#xff1a; 负载均衡集群&#xff1a;Load Balance高可用集群&#xff1a;High Availability高性能计算&#xff1a;High Performance Computing LVS LVS…

6-3、T型加减速单片机程序【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;根据前两节内容&#xff0c;已完成所有计算工作&#xff0c;本节内容介绍具体单片机程序流程及代码 一、程序流程图 根据前两节文章内容可知&#xff0c;T型加减速的关键内容是运动类型的判断以及定时…

CPP项目:Boost搜索引擎

1.项目背景 对于Boost库来说&#xff0c;它是没有搜索功能的&#xff0c;所以我们可以实现一个Boost搜索引擎来实现一个简单的搜索功能&#xff0c;可以更快速的实现Boost库的查找&#xff0c;在这里&#xff0c;我们实现的是站内搜索&#xff0c;而不是全网搜索。 2.对于搜索…

qt/c++实现表情选择框

&#x1f482; 个人主页:pp不会算法^ v ^ &#x1f91f; 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 实现功能 。编解码的设计 。映射关系设计 。匹配机制设计 演示效…

Lustre文件系统fid介绍

fid介绍 fid是lustre文件系统中文件的唯一标识&#xff0c;总共128位&#xff0c;fid序列、fid序列内编号、fid版本号&#xff08;目前未使用默认为0&#xff09; /*** File IDentifier.** FID is a cluster-wide unique identifier of a file or an object (stripe).* FIDs …

HarmonyOS SDK 助力新浪新闻打造精致易用的新闻应用

原生智能是HarmonyOS NEXT的核心亮点之一&#xff0c;依托HarmonyOS SDK丰富全面的开放能力&#xff0c;开发者只需通过几行代码&#xff0c;即可快速实现AI功能。新浪新闻作为鸿蒙原生应用开发的先行者之一&#xff0c;从有声资讯入手&#xff0c;基于Speech Kit朗读控件上线听…