文章目录

  • 一 、媒资模块环境搭建
    • 1、 网关gateway
    • 2、Nacos
    • 3、搭建gateway
  • 二、分布式文件系统
    • 2.1 文件系统
    • 2.2 分布式文件系统
    • 2.3 MinIO
  • 四、上传图片
    • 4.1 需求分析
    • 4.2 数据模型与环境配置
    • 4.3 接口定义
    • 4.4 开发mapper层
    • 4.5 开发service层
    • 4.6 完善controller层
    • 4.7 Service层事务优化

一 、媒资模块环境搭建

1、 网关gateway

接下来是媒资管理服务,目前为止共三个微服务:内容管理、系统管理、媒资管理:


如此,前端得知道每个微服务实例的地址和端口, 且这样写死IP, 维护也不方便:


考虑引入网关, (复习下网关的几个作用:

  • 身份认证和权限校验
  • 服务路由和负载均衡
  • 请求限流


如此 , 请求统一到网关,有由网关路由到不同的微服务上, 网关在这儿有点像400电话, 根据不同的需求转接电话到不同的业务员 . 这样, 前端代码中只需要写接口的相对路径:

2、Nacos

2.1 认识Nacos

网关想请求路由, 就必须知道每个微服务实例的地址,项目使用Nacos作用服务发现中心和配置中心:

流程如下:

  • 微服务启动, 将自己的信息注册到Nacos, Nacos记录各个微服务的地址
  • 网关从Nacos读取服务列表, 包括服务名称和服务地址
  • 请求到达网关, 网关将请求路由到具体的微服务

由此也可以看到Nacos的两个作用:

  • 服务发现中心 : 微服务将自身信息注册登记至Nacos,网关从Nacos获取微服务列表
  • 服务配置中心 : 微服务众多, 配置信息复杂 , 微服务的配置信息统一在Nacos配置

Nacos中的两个概念:

  • namespace:用于区分环境、比如:开发环境、测试环境、生产环境
  • group:用于区分项目,比如:xuecheng-plus项目、xuecheng2.0项目

2.2 实现服务的发现

  • 先在一级父工程的pom文件添加Spring Cloud Alibaba的依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope>
</dependency>
  • 在需要注册的模块的pom文件中添加nacos的服务发现和配置文件依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  • 在需要注册的模块的bookstrap.yml中添加信息:
spring:application:name: de-system  # 应用名称cloud:nacos:server-addr: localhost:8848discovery:namespace: developgroup: DEFAULT_GROUP
  • 重启服务,查看nacos, 可以看到注册成功,以及该服务实例的IP和端口等信息

2.3 配置中心

通过nacos来管理微服务的相关配置, 配置中有每个微服务独有的, 如:spring.application.name, 也有公共的信息, 如mysql、redis , nacos定位一个具体的配置文件通过:namespace、group、dataid.

  • 通过namespace、group找到具体的环境和具体的项目
  • 通过dataid找到具体的配置文件

dataid有三部分组成, 如content-service-dev.yaml配置文件:

  • 第一部分是配置的应用名,即spring.application.name的值
  • 第二部分是环境名, 由spring.profiles.active指定
  • 第三部分即后缀名, nacos支持properties、yaml

启动项目中传入spring.profiles.active的参数决定引用哪个环境的配置文件,例如:传入spring.profiles.active=dev表示使用dev环境的配置文件即content-service-dev.yaml


这里以content-service工程为例进行配置:

  • 点击新建
  • 输入dataid、group、以及配置内容:
  • 点击发布
/spring.application.name等不在nacos中配置,而是要在工程的本地进行配置
/因为nacos客户端要根据此值确定配置文件名称spring:application:name: content-servicecloud:nacos:server-addr: 192.168.101.65:8848discovery:  # 服务注册namespace: devgroup: xuecheng-plus-projectconfig: # 配置文件相关namespace: devgroup: xuecheng-plus-projectfile-extension: yamlrefresh-enabled: true#profiles默认为devprofiles:active: dev

nacos提供了shared-configs可以引入公用配置 :

  • 定义公用配置:
  • 在工程本地配置中引入公用配置:
...shared-configs:- data-id: swagger-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true
...

2.4 配置优先级

到此 , 微服务的配置统一在nacos进行配置,用到的配置文件有本地的配置文件 bootstrap.yaml和nacos上的配置文件 , 服务启动的时候, SpringBoot读取配置文件的顺序如下:

微服务引入配置文件的形式有:

  • 以项目应用名方式引入
  • 以扩展配置文件方式引入
  • 以共享配置文件 方式引入
  • 本地配置文件

当配置有冲突的时候, 优先级:项目应用名配置文件 > 扩展配置文件 > 共享配置文件 > 本地配置文件。想要让本地配置文件优先级最高,可在Nacos中添加:

#配置本地优先
spring:cloud:config:override-none: true

3、搭建gateway

  • 创建一个工程:
  • 在pom文件中引入相关依赖
<dependencies><!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--服务发现中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- 排除 Spring Boot 依赖的日志包冲突 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Spring Boot 集成 log4j2 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency></dependencies>
  • 配置网关的bootstrap.yaml配置文件
#微服务配置
spring:application:name: gatewayprofiles:active: devcloud:nacos:# 服务注册地址server-addr: localhost:8848discovery:namespace: devgroup: DEFAULT_GROUPconfig:# 配置中心地址server-addr: localhost:8848namespace: devgroup: DEFAULT_GROUP# 配置文件格式file-extension: yamlrefresh-enabled: true# 共享配置shared-configs:- data-id: application-${spring.profiles.active}.yamlgroup: DEFAULT_GROUPrefresh: true
  • 在nacos上配置网关路由策略
server:port: 63010 # 网关端口
spring:cloud:gateway:
#      filter:
#        strip-prefix:
#          enabled: trueroutes: # 网关路由配置- id: content-api # 路由id,自定义,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址uri: lb://content-api # 路由的目标地址 lb就是负载均衡,后面跟服务名称predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/content/** # 这个是按照路径匹配,只要以/content/开头就符合要求
#          filters:
#            - StripPrefix=1- id: system-api# uri: http://127.0.0.1:8081uri: lb://system-apipredicates:- Path=/system/**
#          filters:
#            - StripPrefix=1- id: media-api# uri: http://127.0.0.1:8081uri: lb://media-apipredicates:- Path=/media/**
#          filters:
#            - StripPrefix=1# 不校验白名单
ignore:whites:- /auth/logout
  • 启动网关工程, 在http-client-env.json中配置网关的地址

二、分布式文件系统

2.1 文件系统

操作系统通过文件系统提供的接口取存取文件,用户则通过操作系统来访问磁盘上的文件:


常见的文件系统:FAT16/FAT32、NTFS、HFS、UFS、APFS、XFS、Ext4等. 看一下我Windos使用的文件系统:

2.2 分布式文件系统

官话:

直白的说: 一台计算机无法去进行海量文件的存储和响应海量用户的请求, 因此通过网络将若干计算机组织起来共同去完成这个任务


这样的好处有:

  • 一台计算机的文件系统处理能力扩充到多台计算机同时处理
  • 一台计算机挂了还有另外副本计算机提供数据
  • 每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度

市面上分布式文件系统的相关产品有: NFS、GFS、HDFS:

NFS:


网络文件系统, 客户端通过网络访问NFS服务器的硬盘

GFS:

  • GFS采用主从结构,一个GFS集群由一个master和大量的chunkserver组成
  • master存储了数据文件的元数据,如大小名称类型, 一个文件被分成了若干块存储在多个chunkserver中
  • 用户从master中获取数据元信息,向chunkserver存储数据

HDFS:

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

云计算厂家:
  • 阿里云对象存储服务OOS
  • 百度对象存储BOS

在实际项目中, 根据场景来进行技术选型

2.3 MinIO

认识MinIO

  • MinIO适合于存储大容量非结构化的数据, 提供了 Java、Python、GO等多版本SDK支持

  • MinIO集群采用去中心化共享架构,每个结点是对等关系,通过Nginx可对MinIO进行负载均衡访问

  • 将数据分块冗余的分散存储在各各节点的磁盘上,所有的可用磁盘组成一个集合

  • 当上传一个文件时通过纠删码算法对文件进行分块,文件本身分成4个数据块,还会生成4个校验块,数据块和校验块会分散的存储在这8块硬盘上

  • 使用纠删码的好处是不超过一半数量(N/2)的硬盘损坏时,仍然可以恢复数据

使用

  • 下载
链接: https://pan.baidu.com/s/16x9K1j1XPxCHKzqWRmh-mw?pwd=9527
提取码: 9527 复制这段内容后打开百度网盘手机App,操作更方便哦
  • 启动
在exe文件的目录下打开DOS窗口:
minio.exe server xx xx xx

  • 登录
    http://localhost:9000进行登录,账号和密码为:minioadmin/minioadmin
  • 创建桶bucket, 相当于存储文件的目录,可以创建若干的桶
  • 点击upload上传文件, 观察分块存储的效果

MinIO与Java

MinIO提供多个语言版本SDK的支持, Java相关的文档https://docs.min.io/docs/java-client-quickstart-guide.html

  • 添加Maven依赖
<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>
  • 设置bucket的权限为public

  • 官方示例程序

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上传文件
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("上传失败");}}}

其中contentType可以通过com.j256.simplemagic.ContentType枚举类查看常用的mimeType(媒体类型),下面通过扩展名得到mimeType:

@Test
public  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("上传失败");}}
  • 测试删除文件
@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("删除失败");}
}
  • 测试下载
@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 ,注意这里, 别用上面代码中的inputStream, 它在这里受网络影响, 可能有偏差

//校验文件的完整性对文件的内容进行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.1 需求分析

整个上传过程有两点:

  • 点击上传课程图片, 前端请求媒资管理服务将文件上传至分布式文件系统,并且在媒资管理数据库保存文件信息
  • 上传图片成功保存图片地址到课程基本信息表中

4.2 数据模型与环境配置

数据模型


其中有字段md5值的字段, 用来判断文件是否已经上传过, 有相同文件,可不用重复上传, 以提高效率

Nacos配置

  • 在nacos配置中minio的相关信息,进入media-service-dev.yaml:
minio:endpoint: http://localhost:9000accessKey: minioadminsecretKey: minioadminbucket:files: mediafilesvideofiles: video
  • 在media-service工程编写minio的配置类:
@Configuration
public class MinioConfig {//加@Configuration后从nacos中拿信息@Value("${minio.endpoint}")private String endpoint;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;//创建minioClient的bean,方便以后注入@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=“具体的文件名称”
  • 响应:
{"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
}
  • 定义vo类
@Data
public class UploadFileResultVo extends MediaFiles {}
//注意这里虽然前端要求返回的字段和表对应的po一样
//但别直接用po,万一以后前端要求少返回一个字段, 你又不能动po
  • 定义接口
@ApiOperation("上传文件")
@RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile upload) throws IOException {return null;
}

4.4 开发mapper层

最后是向media_files表插入一条记录,使用media_files表生成的mapper即可

4.5 开发service层

定义上传文件的Dto,即从前端能拿到的数据:

/*** @description 上传普通文件请求参数Dto*/@Data
public class UploadFileParamsDto {/** 文件名称*/private String filename;/** 文件类型(文档,音频,视频)*/private String fileType;/**文件大小*/private Long fileSize;/**标签*/private String tags;/**上传人*/private String username;/**备注*/private String remark;}

定义接口中的方法:

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

写service方法的实现类:

@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*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){//从数据库查询文件MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);//无重复md5, 开始插入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 UploadFileResultVo 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);//准备返回数据UploadFileResultVo uploadFileResultVo = new UploadFileResultVo();BeanUtils.copyProperties(mediaFiles, uploadFileResultVo);return uploadFileResultVo;}

注意这里的几个点:

  • 对于公共代码抽取成单独的方法的习惯
  • @Slf4j注解和 log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}",bucket,objectName,e.getMessage(),e);的使用
  • 使用日期当作各级目录, 使用md5值当作文件名
  • 使用@Value注解从Nacos配置文件中读值@Value("${minio.bucket.files}")

4.6 完善controller层

@ApiOperation("上传文件")
@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public UploadFileResultVo upload(@RequestPart("filedata") MultipartFile upload,@RequestParam(value = "folder",required=false) String folder,@RequestParam(value = "objectName",required=false) String objectName) throws IOException {//机构id暂时写死,还没加登录模块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;
}
  • 注意这里获取临时文件后拷贝上传文件到临时文件, 来获得文件路径
  • File tempFile = File.createTempFile("minio", "temp");

4.7 Service层事务优化

上面的代码中,给整个uploadFile文件开启事务(包括文件上传和文件信息入库),即调用uploadFile方法前会开启数据库事务,如果上传文件时间很长,此时数据库事务的持续时间就会很长,数据库链接释放慢,最后导致数据库链接不够用。优化为:只在addMediaFilesToDb方法添加事务控制即可。

@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){...int insert = mediaFilesMapper.insert(mediaFiles);int a = 1/0; //模拟发生异常....
}

测试发现,事务控制失败。失败的原因是一个非事务方法调同类一个事务方法,事务无法控制。


之前在uploadFile方法上添加@Transactional注解时:代理对象MediaFileServiceProxy会在方法执行前开启事务:

而@Transactional注解改到addMediaFilesToDb上后,controller再调uploadFile方法,则代理对象不再进行事务控制。


判断该方法是否可以事务控制必须保证是通过代理对象调用此方法,且此方法上添加了@Transactional注解。现在事务注解在addMediaFilesToDb方法,在调用这个方法的地方打断点,debug看到调用这个同类中方法的对象(this)并不是代理对象:

根据“事务控制必须保证是通过代理对象调用此方法,且此方法上添加了@Transactional注解”,在MediaFileService的实现类中注入MediaFileService的代理对象(自己注入自己):

@Autowired
MediaFileService currentProxy;

原uploadFile方法中对事务方法addMediaFilesToDb的调用改为:

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

关于非事务方法调用事务方法的另一种解决思路:【方法2】

【网课平台】Day3.网关与分布式文件系统相关推荐

  1. 玩玩短视频平台和网课平台开发1——腾讯云对象储存COS的初步配置

    近一两年来,抖音.快手等以短视频为手段的社交工具红遍了大江南北,腾讯也推出了"微视"平台,希望分一杯羹:传统的直播平台如:斗鱼直播.虎牙直播就更不用说了,甚至涌现了许多主打&quo ...

  2. 最好的十个英语网课平台,这年头在网上学英语靠谱吗?

    最好的十个英语网课平台,对比下来你会选择哪家?关于少儿英语的学习有些家长都比较迷茫,受过高等教育的父母甚至英语的重要性,知道英语学习要趁早,恨不得胎教时就灌输英语,但仍然存在很多父母对少儿是否需要学习 ...

  3. 前一段时间比较火的刷网课平台源码,带数据库和教程

    前一段时间比较火的刷网课平台源码,带数据库和教程. 好在疫情已经结束了,希望今后世上再无网课. 这个代码免费提供给大家学习开发用吧,作为一个php的入门学习案例用用还可以. 使用办法 网站根目录解压 ...

  4. 最详细新版网课联盟27刷网课平台源码+安装教程+最新模板+下载地址

    新版网课联盟27刷网课平台源码+安装教程+最新模板 第一步:准备一个服务器+域名 没有域名暂时使用服务器网址也可以 第二步:上传文件 上传文件到wwwroot上面,进行解压 第三步:进行防伪静态设置: ...

  5. 阿里云POLARDB如何帮助猿辅导打造“孩子喜欢老师好”的网课平台?

    2019独角兽企业重金招聘Python工程师标准>>> 海量的题库.音视频答题资料.用户数据以及日志,对猿辅导后台数据存储和处理能力都提出了严峻的要求.而由于教育辅导行业的业务特点, ...

  6. 阿里云POLARDB如何帮助猿辅导打造“孩子喜欢老师好”的网课平台?...

    海量的题库.音视频答题资料.用户数据以及日志,对猿辅导后台数据存储和处理能力都提出了严峻的要求.而由于教育辅导行业的业务特点,猿辅导也面临着业务峰值对于数据库能力的巨大挑战.本文就为大家介绍阿里云PO ...

  7. 插画网课平台哪个靠谱(最新插画网课排行)

    靠谱的插画培训机构有哪些,5个靠谱的插画网课推荐!给大家梳理了国内5家专业的插画师培训班,最新5大插画班排行榜,各有优势和特色! 一:插画线上培训机构排名 1.轻微课(五颗星) 主打课程有日系插画.游 ...

  8. 【网课平台】Day16.项目优化:压测、加缓存优化与分布式锁

    文章目录 一.压力测试 1.优化需求 2.性能指标 3.安装Jmeter 4.压力测试 5.优化日志 二.缓存优化 1.给接口加Redis缓存 2.缓存穿透 3.解决缓存穿透 4.缓存雪崩 5.缓存击 ...

  9. 【网课平台】Day10.对接第三方:实现微信扫码登录

    文章目录 一.需求:微信扫码登录 1.接口文档 2.开发环境准备 3.接入分析 4.接口定义 5.申请令牌 6.查询用户信息 7.保存用户信息 一.需求:微信扫码登录 (和第三方对接的流程) 1.接口 ...

最新文章

  1. 递归删除单链表中所有值为x的元素_如何纯递归反转链表的一部分
  2. bzoj3339 Rmq Problem
  3. JSF基于事件的沟通:过时的方法
  4. maven向本地仓库导入jar包
  5. 屏幕共享技术及相关软件使用测评
  6. Mac 安装仿宋GB2312 For Word/WPS
  7. 今天再发一下热门关键字,看看能否推广网站
  8. php助理面试技巧,助理类面试问题
  9. mysql 参照完整性规则_mysql数据的完整性约束(完整)
  10. Acwing 1227. 分巧克力
  11. 大学计算机基础超详细知识点(高手总结),大学计算机基础超详细知识点(高手总结)免费-...
  12. 计算机网络原理(交换机,路由器详解)
  13. 语音生成视频论文:Audio-driven Talking Face Video Generation with Learning-based Personalized Head Pose
  14. 彻底清除SMSS.EXE病毒
  15. 航运“大数据”热潮袭来,亿海蓝船讯网助力产业升级
  16. activiti会签功能
  17. 数据结构与算法python语言实现-第四章答案
  18. 【JavaScript】关于基本数据类型和引用数据类型
  19. 前端SEO优化的一些解决方案
  20. java:求鸡兔同笼类型题目的小程序

热门文章

  1. WordPress开发中常用代码(必备)
  2. 一些portrait教程链接
  3. web前端学习笔记(初识HTML)
  4. python中的方法什么意思_什么是Python中的“方法”?
  5. Git合并冲突的根本原因和解决方法
  6. 微信小程序直播 ---微信官方组件简单使用
  7. Failed to configure a DataSource: ‘url‘ attribute is not specified and no embedd.
  8. HTML期末大作业~ 学校招聘的HTML网页设计(7个页面) 学生HTML个人网页作业作品下载 ~ web课程设计网页规划与设计 ~大学生个人网站作业模板
  9. 服务器高并发负载解决方案
  10. 内卷 - 内卷化 - 过密化 - involution