1、媒資管理需求分析
2、為什么要用網(wǎng)關(guān)
當(dāng)前要開發(fā)的是媒資管理服務(wù),目前為止共三個(gè)微服務(wù):內(nèi)容管理、系統(tǒng)管理、媒資管理,如下圖:
后期還會添加更多的微服務(wù),當(dāng)前這種由前端直接請求微服務(wù)的方式存在弊端:
如果在前端對每個(gè)請求地址都配置絕對路徑,非常不利于系統(tǒng)維護(hù),比如下邊代碼中請求系統(tǒng)管理服務(wù)的地址使用的是localhost
當(dāng)系統(tǒng)上線后這里需要改成公網(wǎng)的域名,如果這種地址非常多則非常麻煩。
基于這個(gè)問題可以采用網(wǎng)關(guān)來解決,如下圖:
這樣在前端的代碼中只需要指定每個(gè)接口的相對路徑,如下所示:
在前端代碼的一個(gè)固定的地方在接口地址前統(tǒng)一加網(wǎng)關(guān)的地址,每個(gè)請求統(tǒng)一到網(wǎng)關(guān),由網(wǎng)關(guān)將請求轉(zhuǎn)發(fā)到具體的微服務(wù)。
為什么所有的請求先到網(wǎng)關(guān)呢?
有了網(wǎng)關(guān)就可以對請求進(jìn)行路由,路由到具體的微服務(wù),減少外界對接微服務(wù)的成本,比如:400電話,路由的試可以根據(jù)請求路徑進(jìn)行路由、根據(jù)host地址進(jìn)行路由等, 當(dāng)微服務(wù)有多個(gè)實(shí)例時(shí)可以通過負(fù)載均衡算法進(jìn)行路由,如下:
另外,網(wǎng)關(guān)還可以實(shí)現(xiàn)權(quán)限控制、限流等功能。
項(xiàng)目采用Spring Cloud Gateway作為網(wǎng)關(guān),網(wǎng)關(guān)在請求路由時(shí)需要知道每個(gè)微服務(wù)實(shí)例的地址,項(xiàng)目使用Nacos作用服務(wù)發(fā)現(xiàn)中心和配置中心,整體的架構(gòu)圖如下:
流程如下:
1、微服務(wù)啟動,將自己注冊到Nacos,Nacos記錄了各微服務(wù)實(shí)例的地址。
2、網(wǎng)關(guān)從Nacos讀取服務(wù)列表,包括服務(wù)名稱、服務(wù)地址等。
3、請求到達(dá)網(wǎng)關(guān),網(wǎng)關(guān)將請求路由到具體的微服務(wù)。
要使用網(wǎng)關(guān)首先搭建Nacos,Nacos有兩個(gè)作用:
1、服務(wù)發(fā)現(xiàn)中心。
微服務(wù)將自身注冊至Nacos,網(wǎng)關(guān)從Nacos獲取微服務(wù)列表。
2、配置中心。
微服務(wù)眾多,它們的配置信息也非常復(fù)雜,為了提供系統(tǒng)的可維護(hù)性,微服務(wù)的配置信息統(tǒng)一在Nacos配置。
3、Nacos
Spring Cloud :一套規(guī)范
Spring Cloud alibaba: nacos服務(wù)注冊中心,配置中心
根據(jù)上節(jié)講解的網(wǎng)關(guān)的架構(gòu)圖,要使用網(wǎng)關(guān)首先搭建Nacos。
首先搭建Nacos服務(wù)發(fā)現(xiàn)中心。
在搭建Nacos服務(wù)發(fā)現(xiàn)中心之前需要搞清楚兩個(gè)概念:namespace和group
namespace:用于區(qū)分環(huán)境、比如:開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境。
group:用于區(qū)分項(xiàng)目,比如:xuecheng-plus項(xiàng)目、xuecheng2.0項(xiàng)目
首先在nacos配置namespace:
登錄Centos,啟動Naocs,使用sh /data/soft/restart.sh將自動啟動Nacos。
訪問:http://192.168.101.65:8848/nacos/
賬號密碼:nacos/nacos
相關(guān)配置
- 在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)在內(nèi)容管理模塊的接口工程、系統(tǒng)管理模塊的接口工程中添加如下依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3)配置nacos的地址
在系統(tǒng)管理的接口工程的配置文件中配置如下信息:
YAML
#微服務(wù)配置
spring:
application:
name: system-service
cloud:
nacos:
server-addr: 192.168.101.65:8848
discovery:
namespace: dev
group: xuecheng-plus-project
在內(nèi)容管理的接口工程的配置文件中配置如下信息:
YAML
spring:
application:
name: content-api
cloud:
nacos:
server-addr: 192.168.101.65:8848
discovery:
namespace: dev
group: xuecheng-plus-project
配置優(yōu)先級
到目前為止已將所有微服務(wù)的配置統(tǒng)一在nacos進(jìn)行配置,用到的配置文件有本地的配置文件 bootstrap.yaml和nacos上的配置文件,SpringBoot讀取配置文件 的順序如下:
引入配置文件的形式有:
1、以項(xiàng)目應(yīng)用名方式引入
2、以擴(kuò)展配置文件方式引入
3、以共享配置文件 方式引入
4、本地配置文件
各配置文件 的優(yōu)先級:項(xiàng)目應(yīng)用名配置文件 > 擴(kuò)展配置文件 > 共享配置文件 > 本地配置文件。
有時(shí)候我們在測試程序時(shí)直接在本地加一個(gè)配置進(jìn)行測試,比如下邊的例子:
我們想啟動兩個(gè)內(nèi)容管理微服務(wù),此時(shí)需要在本地指定不同的端口,通過VM Options參數(shù),在IDEA配置啟動參數(shù)
通過-D指定參數(shù)名和參數(shù)值,參數(shù)名即在bootstrap.yml中配置的server.port。
啟動ContentApplication2,發(fā)現(xiàn)端口仍然是63040,這說明本地的配置沒有生效。
這時(shí)我們想讓本地最優(yōu)先,可以在nacos配置文件 中配置如下即可實(shí)現(xiàn):
#配置本地優(yōu)先
spring:
cloud:
config:
override-none: true
再次啟動ContentApplication2,端口為63041。
4、分布式文件系統(tǒng)
可以簡單理解為:一個(gè)計(jì)算機(jī)無法存儲海量的文件,通過網(wǎng)絡(luò)將若干計(jì)算機(jī)組織起來共同去存儲海量的文件,去接收海量用戶的請求,這些組織起來的計(jì)算機(jī)通過網(wǎng)絡(luò)進(jìn)行通信,如下圖:
5、MinIO分布式文件系統(tǒng)
介紹
本項(xiàng)目采用MinIO構(gòu)建分布式文件系統(tǒng),MinIO 是一個(gè)非常輕量的服務(wù),可以很簡單的和其他應(yīng)用的結(jié)合使用,它兼容亞馬遜 S3 云存儲服務(wù)接口,非常適合于存儲大容量非結(jié)構(gòu)化的數(shù)據(jù),例如圖片、視頻、日志文件、備份數(shù)據(jù)和容器/虛擬機(jī)鏡像等。
它一大特點(diǎn)就是輕量,使用簡單,功能強(qiáng)大,支持各種平臺,單個(gè)文件最大5TB,兼容 Amazon S3接口,提供了 Java、Python、GO等多版本SDK支持。
官網(wǎng):https://min.io
中文:https://www.minio.org.cn/,http://docs.minio.org.cn/docs/
MinIO集群采用去中心化共享架構(gòu),每個(gè)結(jié)點(diǎn)是對等關(guān)系,通過Nginx可對MinIO進(jìn)行負(fù)載均衡訪問。
去中心化有什么好處?
在大數(shù)據(jù)領(lǐng)域,通常的設(shè)計(jì)理念都是無中心和分布式。Minio分布式模式可以幫助你搭建一個(gè)高可用的對象存儲服務(wù),你可以使用這些存儲設(shè)備,而不用考慮其真實(shí)物理位置。
它將分布在不同服務(wù)器上的多塊硬盤組成一個(gè)對象存儲服務(wù)。由于硬盤分布在不同的節(jié)點(diǎn)上,分布式Minio避免了單點(diǎn)故障。如下圖:
Minio使用糾刪碼技術(shù)來保護(hù)數(shù)據(jù),它是一種恢復(fù)丟失和損壞數(shù)據(jù)的數(shù)學(xué)算法,它將數(shù)據(jù)分塊冗余的分散存儲在各各節(jié)點(diǎn)的磁盤上,所有的可用磁盤組成一個(gè)集合,上圖由8塊硬盤組成一個(gè)集合,當(dāng)上傳一個(gè)文件時(shí)會通過糾刪碼算法計(jì)算對文件進(jìn)行分塊存儲,除了將文件本身分成4個(gè)數(shù)據(jù)塊,還會生成4個(gè)校驗(yàn)塊,數(shù)據(jù)塊和校驗(yàn)塊會分散的存儲在這8塊硬盤上。
使用糾刪碼的好處是即便丟失一半數(shù)量(N/2)的硬盤,仍然可以恢復(fù)數(shù)據(jù)。 比如上邊集合中有4個(gè)以內(nèi)的硬盤損害仍可保證數(shù)據(jù)恢復(fù),不影響上傳和下載,如果多于一半的硬盤壞了則無法恢復(fù)。
6、Linux下MinIO登錄
地址:http://192.168.101.65:9001/login
用戶名:minioadmin
密碼: minioadmin
7、java在MinIO上傳文件和下載文件
package com.xuecheng.media;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.errors.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* @author Mr.M
* @version 1.0
* @description 測試minio的sdk
* @date 2023/2/17 11:55
*/
public class MinioTest {
MinioClient minioClient =
MinioClient.builder()
.endpoint("http://192.168.101.65:9000")
.credentials("minioadmin", "minioadmin")
.build();
@Test
public void test_upload() throws Exception {
//通過擴(kuò)展名得到媒體資源類型 mimeType
//根據(jù)擴(kuò)展名取出mimeType
ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4");
String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字節(jié)流
if(extensionMatch!=null){
mimeType = extensionMatch.getMimeType();
}
//上傳文件的參數(shù)信息
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket("testbucket")//桶
.filename("F:\\develop\\video\\1.mp4") //指定本地文件路徑
// .object("1.mp4")//對象名 在桶下存儲該文件
.object("test/01/1.mp4")//對象名 放在子目錄下
.contentType(mimeType)//設(shè)置媒體文件類型
.build();
//上傳文件
minioClient.uploadObject(uploadObjectArgs);
}
//刪除文件
@Test
public void test_delete() throws Exception {
//RemoveObjectArgs
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("testbucket").object("1.mp4").build();
//刪除文件
minioClient.removeObject(removeObjectArgs);
}
//查詢文件 從minio中下載
@Test
public void test_getFile() throws Exception {
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("test/01/1.mp4").build();
//查詢遠(yuǎn)程服務(wù)獲取到一個(gè)流對象
FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
//指定輸出流
FileOutputStream outputStream = new FileOutputStream(new File("F:\\develop\\video\\1a.mp4"));
IOUtils.copy(inputStream,outputStream);
//校驗(yàn)文件的完整性對文件的內(nèi)容進(jìn)行md5
FileInputStream fileInputStream1 = new FileInputStream(new File("F:\\develop\\video\\1.mp4"));
String source_md5 = DigestUtils.md5Hex(fileInputStream1);
FileInputStream fileInputStream = new FileInputStream(new File("F:\\develop\\video\\1a.mp4"));
String local_md5 = DigestUtils.md5Hex(fileInputStream);
if(source_md5.equals(local_md5)){
System.out.println("下載成功");
}
}
}
8、上傳圖片
流程:
9、上傳圖片接口
yml
minio:
endpoint: http://192.168.101.65:9000
accessKey: minioadmin
secretKey: minioadmin
bucket:
files: mediafiles
videofiles: video
配置類
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;
@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;
}
}
API
@ApiOperation("上傳圖片")
@RequestMapping(value = "/upload/coursefile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata")MultipartFile filedata) throws IOException {
//準(zhǔn)備上傳文件的信息
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
//原始文件名稱
uploadFileParamsDto.setFilename(filedata.getOriginalFilename());
//文件大小
uploadFileParamsDto.setFileSize(filedata.getSize());
//文件類型
uploadFileParamsDto.setFileType("001001");
//創(chuàng)建一個(gè)臨時(shí)文件
File tempFile = File.createTempFile("minio", ".temp");
filedata.transferTo(tempFile);
Long companyId = 1232141425L;
//文件路徑
String localFilePath = tempFile.getAbsolutePath();
//調(diào)用service上傳圖片
UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, localFilePath);
return uploadFileResultDto;
}
Service
//根據(jù)擴(kuò)展名獲取mimeType
private String getMimeType(String extension){
if(extension == null){
extension = "";
}
//根據(jù)擴(kuò)展名取出mimeType
ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字節(jié)流
if(extensionMatch!=null){
mimeType = extensionMatch.getMimeType();
}
return mimeType;
}
/**
* 將文件上傳到minio
* @param localFilePath 文件本地路徑
* @param mimeType 媒體類型
* @param bucket 桶
* @param objectName 對象名
* @return
*/
public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName){
try {
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket(bucket)//桶
.filename(localFilePath) //指定本地文件路徑
.object(objectName)//對象名 放在子目錄下
.contentType(mimeType)//設(shè)置媒體文件類型
.build();
//上傳文件
minioClient.uploadObject(uploadObjectArgs);
log.debug("上傳文件到minio成功,bucket:{},objectName:{},錯(cuò)誤信息:{}",bucket,objectName);
return true;
} catch (Exception e) {
e.printStackTrace();
log.error("上傳文件出錯(cuò),bucket:{},objectName:{},錯(cuò)誤信息:{}",bucket,objectName,e.getMessage());
}
return false;
}
//獲取文件默認(rèn)存儲目錄路徑 年/月/日
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;
}
}
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
//文件名
String filename = uploadFileParamsDto.getFilename();
//先得到擴(kuò)展名
String extension = filename.substring(filename.lastIndexOf("."));
//得到mimeType
String mimeType = getMimeType(extension);
//子目錄
String defaultFolderPath = getDefaultFolderPath();
//文件的md5值
String fileMd5 = getFileMd5(new File(localFilePath));
String objectName = defaultFolderPath+fileMd5+extension;
//上傳文件到minio
boolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);
if(!result){
XueChengPlusException.cast("上傳文件失敗");
}
//入庫文件信息
MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);
if(mediaFiles==null){
XueChengPlusException.cast("文件上傳后保存信息失敗");
}
//準(zhǔn)備返回的對象
UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
return uploadFileResultDto;
}
/**
* @description 將文件信息添加到文件表
* @param companyId 機(jī)構(gòu)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){
//將文件信息保存到數(shù)據(jù)庫
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
if(mediaFiles == null){
mediaFiles = new MediaFiles();
BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
//文件id
mediaFiles.setId(fileMd5);
//機(jī)構(gòu)id
mediaFiles.setCompanyId(companyId);
//桶
mediaFiles.setBucket(bucket);
//file_path
mediaFiles.setFilePath(objectName);
//file_id
mediaFiles.setFileId(fileMd5);
//url
mediaFiles.setUrl("/"+bucket+"/"+objectName);
//上傳時(shí)間
mediaFiles.setCreateDate(LocalDateTime.now());
//狀態(tài)
mediaFiles.setStatus("1");
//審核狀態(tài)
mediaFiles.setAuditStatus("002003");
//插入數(shù)據(jù)庫
int insert = mediaFilesMapper.insert(mediaFiles);
if(insert<=0){
log.debug("向數(shù)據(jù)庫保存文件失敗,bucket:{},objectName:{}",bucket,objectName);
return null;
}
return mediaFiles;
}
return mediaFiles;
}
10、事務(wù)優(yōu)化(重點(diǎn))
在上傳圖片這一個(gè)業(yè)務(wù)中,如果直接在整個(gè)方法前加@Transactional
事務(wù)的話,因?yàn)樯蟼鲌D片涉及到網(wǎng)絡(luò)上傳,可能需要耗時(shí)較多,這樣請求數(shù)據(jù)庫的時(shí)間就會變長,在高并發(fā)情況下就可能給數(shù)據(jù)庫造成很大的壓力。那么怎么解決這個(gè)問題呢?思路就是減小事務(wù)的粒度,也就是說在原有的方法里,抽取插入數(shù)據(jù)庫的代碼成為獨(dú)立的方法,并且在這個(gè)方法加事務(wù),這樣事務(wù)就不涉及到網(wǎng)絡(luò)的傳輸,可以減少數(shù)據(jù)庫的壓力。但是這可能引發(fā)一個(gè)問題,那就是事務(wù)失效
。
什么是事務(wù)失效?
在spring中,事務(wù)成立的條件是代理對象
+@Transactional
,也就是說如果在一個(gè)非代理對象的方法加事務(wù)的話,那么久可能導(dǎo)致事務(wù)失效。在service里面的方法本質(zhì)是this
方法,顯然不是代理對象。那么怎樣變成一個(gè)代理對象呢?文章來源:http://www.zghlxwxcb.cn/news/detail-480024.html
答案是將自己注入,并且將這個(gè)方法寫在接口里面文章來源地址http://www.zghlxwxcb.cn/news/detail-480024.html
@Autowired
MediaFileService currentProxy;
到了這里,關(guān)于學(xué)成在線----day5的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!