当前位置: 首页 > news >正文

在线学堂-第二章媒资管理模块上

第二章媒资管理模块

媒体资源管理(MAM)系统是对各种媒体及内容(如视/音频资料、文本文件、图表等)进行数字化存储、管理以及应用的总体解决方案

每个教学机构都可以在媒资系统管理自己的教学资源,包括:视频、教案等文件

目前媒资管理的主要管理对象是视频、图片、文档等,包括:媒资文件的查询、文件上传、视频处理等

媒资查询:教学机构查询自己所拥有的媒资信息

文件上传:包括上传图片、上传文档、上传视频

视频处理:视频上传成功,系统自动对视频进行编码处理

文件删除:教学机构删除自己上传的媒资文件

1.业务流程

上传图片

教学机构人员在课程信息编辑页面上传课程图片,课程图片统一记录在媒资管理系统

下图是上传图片的界面:

在这里插入图片描述

上传视频

1.教学机构人员进入媒资管理列表查询自己上传的媒资文件

点击“媒资管理”

在这里插入图片描述

进入媒资管理列表页面查询本机构上传的媒资文件。

在这里插入图片描述

2.教育机构用户在"媒资管理"页面中点击 “上传视频” 按钮

在这里插入图片描述

点击“上传视频”打开上传页面

在这里插入图片描述

3.选择要上传的文件,自动执行文件上传,视频上传成功会自动处理

在这里插入图片描述

处理视频

对需要转码处理的视频系统会自动对其处理,处理后生成视频的URL

处理视频没有用户界面,完全是后台自动执行

审核媒资

审核媒资包括程序自动审核和人工审核,程序可以通过鉴黄接口(https://www.aliyun.com/product/lvwang?spm=5176.19720258.J_3207526240.51.e93976f4rSq796)审核视频,对有异议的视频由人工进行审核

1.运营用户登入运营平台并进入媒资管理页面,查找待审核媒资

在这里插入图片描述

2.点击列表中媒资名称链接,可预览该媒资,若是视频,则播放视频

在这里插入图片描述

3.点击列表中某媒资后的"审核" 按钮,既完成媒资的审批过程

在这里插入图片描述

点击“审核”,选择审核结果,输入审核意见

在这里插入图片描述

绑定媒资

课程计划创建好后需要绑定媒资文件,比如:如果课程计划绑定了视频文件,进入课程在线学习界面后点课程计划名称则在线播放视频。如下图:

在这里插入图片描述

如何将课程计划绑定媒资呢?

1.教育机构用户进入课程管理页面并编辑某一个课程,在"课程大纲"标签页的某一小节后可点击”添加视频“。

在这里插入图片描述

2.弹出添加视频对话框,可通过视频关键字搜索已审核通过的视频媒资。

在这里插入图片描述

3.选择视频媒资,点击提交按钮,完成课程计划绑定媒资流程。

在这里插入图片描述

课程计划关联视频后如下图:

在这里插入图片描述

1.1 数据模型

在这里插入图片描述

媒资文件表:存储文件信息,包括图片、视频、文档等

media_process: 待处理视频表

media_process_history: 视频处理历史表,记录已经处理成功的视频信息

媒资文件与课程计划绑定关系表如下:

在这里插入图片描述

1.2 搭建模块环境

2.1 架构的问题分析

当前要开发的是媒资管理服务,目前为止共三个微服务:内容管理、系统管理、媒资管理,后期还会添加更多的微服务,基于这个情况可以采用网关来解决

在这里插入图片描述

这样在前端的代码中只需要指定每个接口的相对路径,如下所示:

在这里插入图片描述

在前端代码的一个固定的地方在接口地址前统一加网关的地址,每个请求统一到网关,由网关将请求转发到具体的微服务

为什么所有的请求先到网关呢?

有了网关就可以对请求进行路由,路由到具体的微服务,减少外界对接微服务的成本,比如可以根据请求路径进行路由、根据host地址进行路由等, 当微服务有多个实例时可以通过负载均衡算法进行路由,另外,网关还可以实现权限控制、限流等功能

项目采用Spring Cloud Gateway作为网关,网关在请求路由时需要知道每个微服务实例的地址,项目使用Nacos作用服务发现中心和配置中心,整体的架构图如下:

在这里插入图片描述

流程如下:

1.微服务启动,将自己注册到Nacos,Nacos记录了各微服务实例的地址

2.网关从Nacos读取服务列表,包括服务名称、服务地址等

3.请求到达网关,网关将请求路由到具体的微服务

要使用网关首先搭建Nacos,Nacos有两个作用:

1.服务发现中心:微服务将自身注册至Nacos,网关从Nacos获取微服务列表

2.配置中心:微服务众多,它们的配置信息也非常复杂,为了提供系统的可维护性,微服务的配置信息统一在Nacos配置

2.搭建Nacos

2.1 服务发现中心

要使用网关首先搭建Nacos,在搭建Nacos服务发现中心之前需要搞清楚两个概念:namespace、group

namespace:用于区分环境、比如:开发环境、测试环境、生产环境

group:用于区分项目

首先在nacos配置namespace:

登录成功,点击左侧菜单“命名空间”进入命名空间管理界面,点击“新建命名空间”,填写命名空间的相关信息。如下图:

在这里插入图片描述

首先完成各服务注册到Naocs,下边将内容管理服务注册到nacos中

1.在xuecheng-plus-parent中添加依赖管理

<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>

2.在内容管理模块的接口工程、系统管理模块的接口工程中添加如下依赖

<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>

3.配置nacos的地址,在系统管理的接口工程的配置文件中配置如下信息:

#微服务配置
spring:application:name: content-service #服务名   content-service-dev.yamlcloud:nacos:server-addr: 192.168.1.106:8848config: #配置文件相关信息(nacos的配置列表)namespace: devgroup: xuecheng-plus-projectfile-extension: yamlrefresh-enabled: trueprofiles:active: dev #环境名

在内容管理的接口工程的配置文件中配置如下信息:

server:servlet:context-path: /contentport: 63040
# 微服务配置
spring:application:name: content-api #服务名   content-api-dev.yamlcloud:nacos:server-addr: 192.168.1.106:8848discovery: #服务注册相关配置(nacos的服务列表)namespace: devgroup: xuecheng-plus-project  #xuecheng-plus-projectprofiles:active: dev #环境名

2.2 配置中心

2.2.2.1 配置三要素

搭建Nacos为配置中心,其目的就是通过Nacos去管理项目的所有配置

nacos如何去定位一个具体的配置文件,即:namespace、group、dataid

1.通过namespace、group找到具体的环境和具体的项目

2.通过dataid找到具体的配置文件,dataid有三部分组成(服务名-环境.配置文件后缀)

比如:content-service-dev.yaml配置文件是由content-service服务名、dev、yaml三部分组成

content-service:第一部分,它是服务名,即spring.application.name的值

dev:第二部分,它是环境名,通过spring.profiles.active指定

yaml: 第三部分,它是配置文件的后缀

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

2.2.2.2 配置content-service

点击加号,添加一个配置,输入data id、group以及配置文件内容

在这里插入图片描述

配置content-api

以相同的方法配置content-api工程的配置文件,在nacos中的开发环境中配置content-api-dev.yaml

server:servlet:context-path: /contentport: 63040# 日志文件配置路径
logging:config: classpath:log4j2-dev.xml# swagger 文档配置
swagger:title: "学成在线内容管理系统"description: "内容系统管理系统对课程相关信息进行业务管理数据"base-package: com.xuecheng.contentenabled: trueversion: 1.0.0

在content-api工程 的本地配置bootstrap.yaml,内容如下:

server:servlet:context-path: /contentport: 63040
# 微服务配置
spring:application:name: content-api #服务注册到Nacos中的服务名   content-api-dev.yamlcloud:nacos:server-addr: 192.168.1.106:8848discovery: #把服务服务注册到nacos的相关配置(nacos的服务列表)namespace: devgroup: xuecheng-plus-project config: #配置文件相关信息(nacos的配置列表)namespace: devgroup: xuecheng-plus-projectfile-extension: yamlrefresh-enabled: trueextension-configs: #扩展配置- data-id: content-service-${spring.profiles.active}.yamlgroup: xuecheng-plus-projectrefresh: trueshared-configs: #共享配置- data-id: swagger-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true- data-id: logging-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: trueprofiles:active: dev #环境名#spring:
#  datasource:
#    driver-class-name: com.mysql.cj.jdbc.Driver
#    url: jdbc:mysql://127.0.0.1:3306/xue_content?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
#    username: root
#    password: 123456

3.搭建Gateway

本项目使用Spring Cloud Gateway作为网关,下边创建网关工程

在这里插入图片描述

添加依赖:1.添加网关的依赖;2.服务发现中心(discover、config)

<parent><groupId>com.xuecheng</groupId><artifactId>xuecheng-plus-parent</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../xuecheng-plus-parent</relativePath>
</parent>
<artifactId>xuecheng-plus-gateway</artifactId><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: gatewaycloud:nacos:server-addr: 192.168.101.65:8848discovery:namespace: devgroup: xuecheng-plus-projectconfig:namespace: devgroup: xuecheng-plus-projectfile-extension: yamlrefresh-enabled: trueshared-configs:- data-id: logging-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: trueprofiles:active: dev

在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

4.搭建媒资工程

至此网关、Nacos已经搭建完成,建媒资工程模块xuecheng-plus-media,右键pom.xml转为maven工程

在这里插入图片描述

下边做如下配置:

1.创建媒资数据库xc_media,并导入资料目录中的xcplus_media.sql

2.修改nacos上的media-service-dev.yaml配置文件中的数据库链接信息

3.重启media-api工程只要能正常启动成功即可,稍后根据需求写接口

http://localhost:63050/media/swagger-ui.html

5.分布式文件系统

今天讲的分布式文件系统就是海量用户查阅海量文件的方案

分布式文件系统可以简单理解为:一个计算机无法存储海量的文件,通过网络将若干计算机组织起来共同去存储海量的文件,去接收海量用户的请求,这些组织起来的计算机通过网络进行通信,如下图:

在这里插入图片描述

好处:

1.一台计算机的文件系统处理能力扩充到多台计算机同时处理

2.一台计算机挂了还有另外副本计算机提供数据

3.每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度

市面上有哪些分布式文件系统的产品呢?云计算厂家

阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务

百度对象存储BOS提供稳定、安全、高效、高可扩展的云存储服务。您可以将任意数量和形式的非结构化数据存入BOS,并对数据进行管理和处理。BOS支持标准、低频、冷和归档存储等多种存储类型,满足多场景的存储需求

5.1 MinIO

本项目采用MinIO构建分布式文件系统,MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用,它兼容亚马逊云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等

它一大特点就是轻量,使用简单,功能强大,支持各种平台,单个文件最大5TB,兼容 Amazon 接口,提供了 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个以内的硬盘损害仍可保证数据恢复,不影响上传和下载,如果多于一半的硬盘坏了则无法恢复

5.1.1 启动minio

启动并登录MinIO

在这里插入图片描述

本项目创建两个buckets:

mediafiles: 普通文件

video:视频文件

5.1.2 SDK

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

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

在media-service工程添加此依赖

<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>

参数说明:需要三个参数才能连接到minio服务

参数说明
Endpoint对象存储服务的URL
Access Key账户
Secret Key密码

在xuecheng-plus-media-service工程 的test下编写测试代码如下:测试上传、删除、查询文件功能

package media.mediaTest;/*** 测试minio的sdk*/
public class FileUploader {// 连接minioClientstatic MinioClient minioClient =MinioClient.builder().endpoint("http://192.168.1.103:9000/")//api.credentials("minioadmin", "minioadmin")//账号、密码.build();// 判断桶存不存在
//    boolean found =
//            minioClient.bucketExists(BucketExistsArgs.builder().bucket("testbucket").build());
//      if (!found) {
//        minioClient.makeBucket(MakeBucketArgs.builder().bucket("testbucket").build());
//    } else {
//        System.out.println("桶'testbucket' 已经存在.");
//    }//上传文件@Testpublic  void upload() {try {UploadObjectArgs testbucket = UploadObjectArgs.builder().bucket("testbucket")//桶.object("test/2.jpg")//对象名(上传到minio中的文件名).filename("C:\\Users\\Administrator\\Desktop\\1\\个人资料\\文件\\图标\\20210930123720614.jpg")//指定本地文件路径(上传哪一个文件).build();minioClient.uploadObject(testbucket);System.out.println("上传成功");} catch (Exception e) {e.printStackTrace();System.out.println("上传失败");}}@Testpublic void delete(){try {minioClient.removeObject(RemoveObjectArgs.builder().bucket("testbucket").object("test/2.jpg").build());System.out.println("删除成功");} catch (Exception e) {e.printStackTrace();System.out.println("删除失败");}}//查询文件@Testpublic void getFile() {GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("1.jpg").build();try(FilterInputStream inputStream = minioClient.getObject(getObjectArgs);FileOutputStream outputStream = new FileOutputStream(new File("E:\\Project"));//下载到哪里) {IOUtils.copy(inputStream,outputStream);//校验文件的完整性对文件的内容进行md5FileInputStream 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("下载成功");}} catch (Exception e) {e.printStackTrace();}}}

首先创建一个用于测试的bucket

在这里插入图片描述

点击“Manage”修改bucket的访问权限

在这里插入图片描述

选择public权限

在这里插入图片描述

5.2 上传图片

5.2.1 业务流程

课程图片是宣传课程非常重要的信息,在新增课程界面上传课程图片,也可以修改课程图片

在这里插入图片描述

上传课程图片总体上包括两部分:

1、上传课程图片前端请求媒资管理服务将文件上传至分布式文件系统,并且在媒资管理数据库保存文件信息

2、上传图片成功保存图片地址到课程基本信息表中

在这里插入图片描述

1.前端进入上传图片界面

2.上传图片,请求媒资管理服务

3.媒资管理服务将图片文件存储在MinIO

4.媒资管理记录文件信息到数据库

5.前端请求内容管理服务保存课程信息,在内容管理数据库保存图片地址

5.2.2 数据模型

涉及到的数据表有:课程信息表中的图片字段、媒资数据库的文件表,下边主要看媒资数据库的文件表

在这里插入图片描述

各字段描述如下:

在这里插入图片描述

5.2.3 准备环境

首先在minio配置bucket,bucket名称为:mediafiles,并设置bucket的权限为公开

在这里插入图片描述

在nacos配置中minio的相关信息,进入media-service-dev.yaml:

在这里插入图片描述

配置信息如下:

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

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

package com.xuecheng.media.config;/*** @description minio配置*/
@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=“具体的文件名称”

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

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

package com.xuecheng.media.model.dto;/*** 上传普通文件成功响应结果(后可能会增加额外的字段)*/@Data
public class UploadFileResultDto extends MediaFiles {}

定义接口如下:

创建了一个临时文件然后把文件写入临时文件中进行上传的,模拟远程目录

package com.xuecheng.media.api;/*** @description 媒资文件管理接口*/
@Api(value = "媒资文件管理接口", tags = "媒资文件管理接口")
@RestController
public class MediaFilesController {@AutowiredMediaFileService mediaFileService;@ApiOperation("媒资列表查询接口")@PostMapping("/files")public PageResult<MediaFiles> list(PageParams pageParams, @RequestBody QueryMediaParamsDto queryMediaParamsDto) {Long companyId = 1232141425L;return mediaFileService.queryMediaFiels(companyId, pageParams, queryMediaParamsDto);}@ApiOperation("上传文件接口")@RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)//文件上传要指定接收的请求格式public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata) throws IOException {//@RequestPart注解接收文件,filedata前端传递过来的文件名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.4.1 DAO开发

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

4.4.2 Service开发

Service方法需要提供一个更加通用的保存文件的方法

定义请求参数类:存储文件信息的,dto前端传递过来的

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

定义service方法:

/*** 媒资文件管理业务类*/
public interface MediaFileService {/*** 媒资文件查询方法* pageParams 分页参数* queryMediaParamsDto 查询条件*/public PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto);/*** 上传文件* companyId 机构id* uploadFileParamsDto 上传文件信息* localFilePath 文件磁盘路径* @return 文件信息*/public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath);}

实现方法如下:

存到minio,存保存文件信息;存minio和存数据库的都单独抽出来成方法

package com.xuecheng.media.service.impl;@Service
@Slf4j
public class MediaFileServiceImpl implements MediaFileService {@AutowiredMinioClient minioClient;@AutowiredMediaFilesMapper mediaFilesMapper;//存储普通文件桶@Value("${minio.bucket.files}")private String bucket_Files;//从nacos配置文件中读取桶名// //存储视频桶
// @Value("${minio.bucket.videofiles}")
// private String bucket_Video;/*** 媒资文件查询方法**/@Overridepublic PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {//构建查询条件对象LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();//分页对象Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());// 查询数据内容获得结果Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);// 获取数据列表List<MediaFiles> list = pageResult.getRecords();// 获取数据总数long total = pageResult.getTotal();// 构建结果集PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());return mediaListResult;}/*** 上传文件* @companyId 机构id* uploadFileParamsDto 上传文件信息* localFilePath 文件磁盘路径*/@Transactional@Overridepublic UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {//1.将文件上传到minioFile file = new File(localFilePath);if (!file.exists()) {XueChengPlusException.cast("文件不存在");}//文件名称String filename = uploadFileParamsDto.getFilename();//文件扩展名String extension = filename.substring(filename.lastIndexOf("."));//文件mimeTypeString mimeType = getMimeType(extension);//文件的md5值(nacos中文件名字设置为md5值,不重复)String fileMd5 = getFileMd5(file);//文件的默认目录(nacos中文件目录设置为年月日)String defaultFolderPath = getDefaultFolderPath();//存储到minio中的对象名(年月日+md5+扩展名)String objectName = defaultFolderPath + fileMd5 + extension;//将文件上传到minioboolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_Files, objectName);if (!result) {XueChengPlusException.cast("文件上传失败");}//2.将文件信息保存到数据库//文件大小uploadFileParamsDto.setFileSize(file.length());//将文件信息存储到数据库MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_Files, objectName);//准备返回数据UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);return uploadFileResultDto;}/*** 将文件写入minIO,上传成功返回布尔值* localFilePath 文件地址(文件本地路径)* bucket  桶* objectName 对象名称*/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;}/*** 将文件信息添加到文件表* companyId  机构id* fileMd5  文件md5值* uploadFileParamsDto 上传文件的信息* bucket  桶* objectName  对象名称*/@Transactionalpublic 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最终要添加的数据库中//设置文件主键id为md5值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;}//文件默认存储目录路径设置为年/月/日private String getDefaultFolderPath() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");String folder = sdf.format(new Date()).replace("-", "/") + "/";return folder;}//获取文件的md5private String getFileMd5(File file) {try (FileInputStream fileInputStream = new FileInputStream(file)) {String fileMd5 = DigestUtils.md5Hex(fileInputStream);return fileMd5;} catch (Exception e) {e.printStackTrace();return null;}}//根据扩展名,获取文件mimeTypeprivate 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;}
}

4.4.3 接口测试

### 上传文件:D:\\upload\\2.jpg本地的文件路径
POST {{media_host}}/media/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="2.jpg"
Content-Type: application/octet-stream< D:\\upload\\2.jpg

进行前后端联调测试

在新增课程、编辑课程界面上传图,保存课程信息后再次进入编辑课程界面,查看是否可以正常保存课程图片信息

在这里插入图片描述

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

在这里插入图片描述

打开minio

在这里插入图片描述

注意:如果要走网关,就要关闭63040,打开63010

上传图片成功,但是显示空白的,前端那儿除了把网关的地址放出来,还有下面有个回显图片的minio服务器地址要改成自己的minio的ip,这样回显拼接才会对

一.检查env里的minio地址跟你自己的minio是否相同; 二.minio是否开启了bucket的访问权限

在这里插入图片描述

4.4.4 Service事务优化

上边的service方法优化后并测试通过,现在思考关于uploadFile方法的是否应该开启事务

目前是在uploadFile方法上添加@Transactional,当调用uploadFile方法前会开启数据库事务,如果上传文件过程时间较长那么数据库的事务持续时间就会变长,这样数据库链接释放就慢,最终导致数据库链接不够用

我们只将addMediaFilesToDb方法添加事务控制即可,uploadFile方法上的@Transactional注解去掉

优化后如下:

	/*** 上传文件*/@Overridepublic UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {//2.将文件信息保存到数据库//将文件信息存储到数据库(重点this,this原始对象)MediaFiles mediaFiles =this.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_Files, objectName);}@Transactionalpublic MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName){xxx}

现在的问题是事务控制失败(事务失效:非public,同类方法调用,没纳入spring管理,捕获异常)

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

如果是在uploadFile方法上添加@Transactional注解就可以控制事务,去掉则不行,现在的问题其实是一个非事务方法调同类一个事务方法,事务无法控制,这是为什么?

下边分析原因:

事务注解基于aop的,方法互调,切面无效 (代理对象+注解,事务不会失效)

this是原始对象,代理对象代理对象是把本来对象放进去了,然后invoke,实际上调用的是this的方法

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

如何解决呢?

通过代理对象去调用addMediaFilesToDb方法即可解决(通过@Autowired把本类的service接口注入进来,(自己注入自己),然后再来调用,把添加的那个方法写成接口,然后在这个接口上去加事务注解)

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

package com.xuecheng.media.service.impl;@Service
@Slf4j
public class MediaFileServiceImpl implements MediaFileService {//注入进来@AutowiredMediaFileService currentProxy;......
}

将addMediaFilesToDb方法提取出来设置成一个接口

通过aop进行反射调用方法,在反射过程中查看调用的方法是否存在注解,有就开启事务;没有就直接进入方法

/*** 媒资文件管理业务类*/
public interface MediaFileService {/*** 将文件信息添加到文件表* companyId  机构id* fileMd5  文件md5值* uploadFileParamsDto  上传文件的信息* @bucket  桶* objectName 对象名称*/public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);}

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

package com.xuecheng.media.service.impl;@Service
@Slf4j
public class MediaFileServiceImpl implements MediaFileService {@AutowiredMediaFileService currentProxy;/*** 上传文件*/@Overridepublic UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {//2.将文件信息保存到数据库//将文件信息存储到数据库
//        MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_Files, objectName);//写入文件表(换成代理对象调用)MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_Files, objectName);......}/*** 将文件信息添加到文件表*/@Transactionalpublic MediaFiles addMediaFilesToDb(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName) {      ......}
}

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

http://www.xdnf.cn/news/960751.html

相关文章:

  • 高效清理C盘
  • Quick BI 自定义组件开发 -- 第一篇 Lifecycle 接口的定义
  • esp_image: invalid segment length 0xffffffff
  • MySQL自定义函数零基础学习教程
  • FastAPI 与 JWT 身份验证:保护你的 API
  • SpringBoot配置最新的AI版本加入Maven的配置方式
  • CDBench论文精读
  • 树莓派4B, ubuntu20.04, 安装Ros Noetic[踩坑记录]
  • 当拼音文字遇上回文:英语中的诗意镜像与文化密码
  • Profinet转CAN网关如何实现profinet与can协议互转
  • 如何通过API接口获取淘宝商品列表?操作详解
  • Quick BI 自定义组件开发 -- 第二篇 添加 echart 组件,开发图表
  • Spring AMQP
  • 打造高效能技术组织的逆向法则
  • 解读新交规中关于“电动自行车能否在快车道骑行”的核心问题
  • Shellshock漏洞与永恒之蓝(WannaCry)勒索病毒深度分析
  • [大A量化专栏] 看盘界面设置(未完待续)
  • Linux进程信号(一)
  • AI Bot到底是真助手,还是又一个流量收割伎俩?
  • 软件功能测试有哪些类型?软件测评机构
  • CppCon 2015 学习:The Importance of Being const
  • 鸠摩搜书官网入口,免费电子书小说在线搜索下载网站
  • 火山 RTC 引擎10 ----远端视频 转网易视频格式
  • 镜头景深的影响因素有哪些
  • 【西门子杯工业嵌入式-7-OLED】
  • 高防CDN是什么?和传统CDN有什么区别?
  • 深入浅出 红黑树:如何手写红黑树(基于TreeMap,算法导论的实现)
  • 振动力学:复模态法和状态空间描述(一般阻尼系统的自由振动)
  • 网站维护页面Plus + HTML源码(源码下载)
  • 门静脉高压——检查