首页 > 其他分享 >Net8微服务实战

Net8微服务实战

时间:2024-04-24 23:46:02浏览次数:12  
标签:实战 set 服务 转码 header proxy Net8 http localhost

前言

学习杨中科老师开源项目在线英语网站微服务

1.需求

服务拆分

image-20240413213531990

image-20240419234736153

2.项目源码

项目 说明
Peng.ASPNETCore DistributedCacheHelper 分布式缓存帮助类
MemoryCacheHelper 内存缓存帮助类
UnitOfWorkFilter 工作单元筛选器
Peng.Commons Validators文件夹 FluentValidation的扩展类
LoggerExtensions 使用FormattableString简化日志的代码
ModuleInitializerExtensions 把服务注册代码放到各自的项目中
Peng.DomainCommons IAggregateRoot 聚合根标识接口
BaseEntity、AggregateRootEntity 领域事件的发布
MultilingualString 多语言值对象
Peng.EventBus 集成事件总线
Peng.Infrastructure BaseDbContext 领域事件的发布
EFCoreInitializerHelper 上下文的自动化注册
ExpressionHelper 简化值对象的相等性比较
MediatorExtensions 领域事件的注册
MultilingualStringEFCoreExtensions 多语言值对象的配置
Peng.JWT 使用JWT实现登录令牌

3.运行

3.1环境搭建

需要什么就装什么

https://www.cnblogs.com/pengboke/p/18156610

3.2升级Net8

所有程序都升级为Net8

创建Net8框架程序,然后把代码复制粘贴,升级对应的NuGet包

image-20240415224859985

	<PropertyGroup>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>

3.3appsettings

这里的配置文件我都换成了本地配置

image-20240415221406651

 "Cors": {
   "Origins": [ "http://localhost:3000", "http://localhost:3001" ]
 },
 "FileService": {
   "SMB": {
     "WorkingDir": "e:/temp/upload"
   },
   "UpYun": {

   },
   "EndPoint": {

   }
 },
 "ConnectionStrings": {
   "Connection": "Server=localhost;Database=Peng-VNext;charset=utf8;uid=root;pwd=root;port=3306;"
 },
 "Redis": {
   "ConnStr": "localhost"
 },
 "JWT": {
   "Issuer": "myIssuer",
   "Audience": "myAudience",
   "Key": "pengpeng@123456789abcdefghijklmnopq",
   "ExpireSeconds": "31536000"
 },
 "RabbitMQ": {
   "HostName": "127.0.0.1",
   "ExchangeName": "peng_event_bus",
   "UserName": "guest",
   "Password": "guest"
 },
 "ElasticSearch": { "Url": "http://elastic:OXanY7JrEElR--N0Fx4z@localhost:9200" }

3.4数据库冲突

注释一下代码:

WebApplicationBuilderExtensions

image-20240415221547291

添加数据库配置

builder.Services.AddDbContext<IdDbContext>(options =>
{
    string connstr = builder.Configuration.GetValue<string>("ConnectionStrings:Connection");
    options.UseMySql(connstr, ServerVersion.AutoDetect(connstr));
});

image-20240415221645001

3.5IMediatR冲突

注释WebApplicationBuilderExtensions的IMediatR注入

image-20240415221838013

IMediatR升级Net8后,没有这个方法了。

image-20240415222032222

3.6Nginx配置

我这里用的是8000端口

主要是配置Server

image-20240415222340101

server {
        listen       8000;
        server_name  localhost;

        location /FileService/ {
			proxy_pass http://localhost:50401/;
			proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto  $scheme;
            client_max_body_size 100m;
		}
        
        location /IdentityService/ {
			proxy_pass  http://localhost:50402/;
			proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto  $scheme;
		}
		
		location /Listening.Admin/ {
			proxy_pass http://localhost:50403/;
			proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto  $scheme;
			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
		}

		location /Listening.Main/ {
			proxy_pass http://localhost:50404/;
			proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;			
            proxy_set_header X-Forwarded-Proto  $scheme;
		}			
		
		location /MediaEncoder/ {
			proxy_pass http://localhost:50405/;
			proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;	
            proxy_set_header X-Forwarded-Proto  $scheme;		
		}

		location /SearchService/ {
			proxy_pass http://localhost:50406/;
			proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto  $scheme;
		}

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }   
    }

3.7数据迁移

3.7.1IdentityService

IdentityService.WebAPI安装

<ItemGroup>
	<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
		<PrivateAssets>all</PrivateAssets>
		<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
	</PackageReference>
	<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
	<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>

image-20240415231829436

IdentityService.Infrastructure安装8.0的Mysql包

  <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />

image-20240415223057854

IdentityService.Domain

image-20240416001519950

DesignTimeDbContextFactory,有些代码没看明白,先固定,保证运行

image-20240416001411817

没有报错运行一下代码,正常跑起来可以用迁移命令

迁移命令

Add-Migration init
Update-Database init

image-20240415222932044

迁移成功,数据库已更新

image-20240415222915328

创建一个管理员

image-20240415223316003

登录成功,认证服务可以正常运行了。

image-20240415224736448

3.7.2FileService

FileService.WebAPI安装包并且注释全球化

<ItemGroup>
	<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
		<PrivateAssets>all</PrivateAssets>
		<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
	</PackageReference>
	<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
	<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>

image-20240415232544073

FileService.Infrastructure

	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
		<ProjectReference Include="..\FileService.Domain\FileService.Domain.csproj" />
		<ProjectReference Include="..\Peng.Infrastructure\Peng.Infrastructure.csproj" />
	</ItemGroup>

	<ItemGroup>	
		<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
		<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
		<PackageReference Include="UpYun.NETCore" Version="1.1.0" />
	</ItemGroup>

image-20240415231102806

注入DB服务

//db
builder.Services.AddDbContext<FSDbContext>(options =>
{
    string connstr = builder.Configuration.GetValue<string>("ConnectionStrings:Connection");
    options.UseMySql(connstr, ServerVersion.AutoDetect(connstr));
});

image-20240415232111642

appsettings

image-20240415232233756

数据迁移

Add-Migration init
Update-Database init

image-20240415232653848

3.7.3ListeningService

Listening.Domain

image-20240415235659905

Listening.Infrastructure

image-20240415235726508

Listening.Main.WebAPI

image-20240415235755730

Listening.Admin.WebAPI

image-20240415235823978

注入DB服务

image-20240415235932066

appsettings

image-20240416000016612

没有报错运行一下代码,正常跑起来使用迁移命令

Add-Migration init
Update-Database init

image-20240416000122892

3.7.4MediaEncoderService

MediaEncoder.Domain

image-20240416002921841

MediaEncoder.Infrastructure

image-20240416002951116

MediaEncoder.Infrastructure

image-20240416003013945

DesignTimeDbContextFactory,有些代码没看明白,先固定,保证运行

image-20240416003038312

注入DB服务,然后注释掉builder.Services.AddHostedService,等数据库迁移完成再放开即可

image-20240416153951872

appsettings

image-20240416003203417

没有报错运行一下代码,正常跑起来使用迁移命令

Add-Migration init
Update-Database init

image-20240416003245150

3.7.5SearchService.WebAPI

SearchService.Domain

image-20240416004615476

SearchService.Infrastructure

image-20240416004632762

SearchService.WebAPI

image-20240416004641628

appsettings

image-20240416004705451

3.7.6前端

淘宝镜像过期

报错:

npm ERR! request to https://registry.npm.taobao.org/XXX failed, reason: certificate has expired

更新淘宝镜像

# 清楚缓存
npm cache clean --force
# 获取镜像地址
npm config get registry
# 设置淘宝镜像地址
npm config set registry https://registry.npmmirror.com

yarn

# 设置yarn的strict-ssl
yarn config set "strict-ssl" false -g
# 安装yarn
npm install -g yarn
# 运行
yarn dev

image-20240416175635070

修改前端端口号

端口号为nginx所配置的

image-20240416203930145

3.7.8运行后端

修改后端启动端口

FileService.WebAPI:http://localhost:50401/
IdentityService.WebAPI:http://localhost:50402/
Listening.Admin.WebAPI:http://localhost:50403/
Listening.Main.WebAPI:http://localhost:50404/
MediaEncoder.WebAPI:http://localhost:50405/
SearchService.WebAPI:http://localhost:50406/

image-20240416204122770

3.7.9完成

到此所有服务基本可以跑起来了,但是基于Net8有很多不兼容,后面慢慢学习慢慢优化。

4.文件服务

清空表数据

truncate table T_Episodes;
truncate table T_FS_UploadedItems;

添加专辑AlbumAddRequestValidator

 RuleFor(x => x.CategoryId).Must((cId, ct) => dbCtx.Query<Category>().Any(c => c.Id == cId.CategoryId)).WithMessage(c => $"CategoryId={c.CategoryId}不存在");

image-20240416215239459

添加EpisodeAddRequestValidator

  RuleFor(x => x.AlbumId).Must((cId, ct) => ctx.Query<Album>().Any(c => c.Id == cId.AlbumId))
      .WithMessage(c => $"AlbumId={c.AlbumId}不存在");

image-20240416221235074

修改端口号,我没搞明白为什么http://localhost可以访问,这里需要加上nginx端口号进行转发,实际的文件服务地址端口号应该也可以。

image-20240416222153405

上传

image-20240416222005546

这样我的文件服务可以正常上传了,下面梳理下逻辑

上传主要是是上传到云服务器和备份服务器

image-20240416223009912

MockCloudStorageClient是模拟云服务器,文件保存在wwwroot文件夹下。

image-20240416223111128

SMBStorageClient是备份服务器,保存在配置的e:/temp/upload

image-20240416223205830

5.认证服务

使用IdentityServer4组件,User和Role

image-20240416225726604

IIdRepository认证服务仓储

image-20240416225838415

实现手机和邮箱发送验证码

image-20240416225918162

创建用户和重置密码的时候发布领域事件

可以同时或者选择性的把新增用户的密码短信/邮件/打印给用户

体现了领域事件对于代码“高内聚、低耦合”的追求

image-20240416230329023

认证服务主要分为

  • LoginController:登录功能

  • UserAdminController:用户的基本CRUD功能

image-20240417191925209

6.听力服务

分类(Category)、专辑(Album)、音频(Episode)3个实体。由于我们可以直接访问某个专辑或者某个音频,因此我们这里把这3个实体放到3个聚合之中。

image-20240417200416576

建造者模式使用链式调用保证创建出来的Episode都是合法的。

把许多构造函数复制变成方法赋值看起来更清晰。

参数的合法性校验也放到Build中。

var builder = new Episode.Builder();
builder.Id(id).SequenceNumber(maxSeq + 1).Name(name).AlbumId(albumId)
     .AudioUrl(audioUrl).DurationInSecond(durationInSecond)
     .SubtitleType(subtitleType).Subtitle(subtitle);

image-20240417200544626

字幕解析防腐层

ISubtitleParser:字幕解析接口。所有的字幕解析实现继承该接口。
JsonParser:Json字幕解析方式。
LrcParser:Lrc字幕解析方式。
SrtParser:Srt字幕解析方式。
SubtitleParserFactory:创建解析对象,通过传入名称生成解析对象。

image-20240417200951849

听力服务的仓储接口IListeningRepository

image-20240417201306616

听力服务的领域服务ListeningDomainService

image-20240417201341414

Listening.Admin.WebAPI(听力后台服务):CategoryController、AlbumController、EpisodeController分别提供了对分类、专辑、音频进行管理的接口。

image-20240417204238972

DDD原则:一个实体不应该有处于不完整状态的时刻,这样就能减少程序中对于不完整状态进行处理的代码。因此:一个没有完成转码的Episode对象就不应该存在于数据库中。

所以不通过IsReady来保存音频状态。

保存音频的时候,先把音频实体放到Redis缓存,然后发布集成事件通知转码服务进行转码。

image-20240417204337209

需要转码的音频保存到Redis,转码完成后,程序再把Redis中暂存的音频数据保存到数据库中。

Episode对象一直处于完整状态,也就是数据库中的音频记录一定是可用的,代码就简单很多了。这就是DDD给系统设计带来的一个很大的好处。

image-20240417204716679

转码服务会在转码开始、转码失败、转码完成等事件出现的时候,发布名字分别为MediaEncoding.Started、MediaEncoding.Failed、MediaEncoding.Completed等集成事件,因此我们只要监听这些集成事件,然后把转码状态的变化推送到前端页面即可,然后在更新Redis。

image-20240417205130700

领域事件转换为集成事件:

EpisodeCreatedEventHandler:音频添加,发布事件。
EpisodeDeletedEventHandler:音频删除,发布事件。
EpisodeUpdatedEventHandler:音频修改,发布事件。

image-20240417205513418

查询分类、专辑、音频的时候使用了MemoryCache缓存。

Redis分布式缓存都缓存在Redis服务器上,而MemoryCache缓存在内存上,这里没有多少服务器而且数据量不大更适合MemoryCache缓存。

还使用了local函数(本地函数),本地函数是一种嵌套在另一成员中的类型的方法。 仅能从其包含成员中调用它们。使代码结构更清晰。

image-20240417212925009

前端显示字幕:根据audio标签的timeupdate事件获取标签进度的时间,然后再去sentences(字幕列表)根据startTime和endTime过滤。

 const querySentence=(position)=>
    {
        const sentences = state.episode.sentences;
        for (var i = 0; i < sentences.length; i++)
        {
            var sentence = sentences[i];
            if (position >= sentence.startTime && position <= sentence.endTime)
            {
                return sentence;
            }
        }              
    };
 const updateCurrentSentence=()=>{
        var position = mainPlayer.value.currentTime;
        var foundSentence = querySentence(position);
        if (foundSentence && !sentenceEqual(foundSentence,state.currentSentence))
        {
            state.currentSentence = foundSentence;
        }    
    };

音频标签

image-20240417220523370

字幕列表

image-20240417220920019

7.转码服务

目前只需要m4a转码,这和听力服务的字幕解析模块类似。

IMediaEncoder:转码接口。所有的转码服务实现继承该接口。
ToM4AEncoder:M4A转码服务。
MediaEncoderFactory:创建转码服务对象,通过传入名称生成解析对象。

image-20240417221713342

IMediaEncoderRepository:转码服务仓储

image-20240417221333284

M4A转码服务实现

使用第三方FFmpeg.NET包(也可以通过命令行(cmd)直接调用ffmpeg.exe)

var ffmpeg = new Engine(ffmpegPath);
string? errorMsg = null;
ffmpeg.Error += (s, e) =>
{
    errorMsg = e.Exception.Message;
};
await ffmpeg.ConvertAsync(inputFile, outputFile, ct);//进行转码
if (errorMsg != null)
{
    throw new Exception(errorMsg);
}

image-20240417221816327

启动后台服务,并创建一个死循环,5s一次获取Redis中需要执行的转码任务。

image-20240417222035432

首先使用RedLockNet.SERedis实现Redis分布式锁,保证任务同一个任务不会被多个转码服务执行。

获取到任务后开始执行,更改并保存任务状态为开始。

然后下载到转码服务器临时文件夹。

image-20240417222403735

声明转码后的文件信息。

计算下载后的文件散列值(相同文件的散列值一样,不一样的概率很低),然后根据文件散列值个文件大小查询数据库是否存在对应的M4A文件,存在就不转码,更新到音频表。

然后使用EncoderFactory创建ToM4AEncoder转码对象进行转码,转码成功发布集成事件和领域事件。

image-20240417223026154

8.搜索服务

搜索服务没什么要说的,本来想通过最新的Elastic.Clients.Elasticsearch实现,但是查询高亮会有一些问题,还是退回NEST。还是留一下Elasticsearch的安装,不过有坑的我都记录下来了。

image-20240419235041500

前端

image-20240419235304900

标签:实战,set,服务,转码,header,proxy,Net8,http,localhost
From: https://www.cnblogs.com/pengboke/p/18156619

相关文章

  • Net8微服务之Consul、Ocelot、IdentityServer4
    前言情绪的尽头是沉默1.微服务概念1.1微服务发展分布式解决性能问题,微服务解决维护性、扩展性、灵活性。1.2微服务概念微服务(或称微服务架构),是一种现代化的软件架构方法,它将一个应用程序分解为多个小型、独立的服务单元,每个服务都负责特定的业务功能,并且可以独立开发、测......
  • ETL工具-nifi干货系列 第十六讲 nifi Process Group实战教程,一文轻松搞定
    1、目前nifi系列已经更新了10多篇教程了,跟着教程走的同学应该已经对nifi有了初步的解,但是我相信同学们应该有一个疑问:nifi设计好的数据流列表在哪里?如何同时运行多个数据流?如启停单个数据流?带着这些疑问,今天的主角nifiProcessGroup正式登场,先给大家看个图。2、ProcessGroup(......
  • 鸿蒙HarmonyOS实战-ArkUI动画(页面转场动画)
    ......
  • 微服务如何解决用户登录验证问题的流程整理
    0-我们通过客户端=》网关=》微服务的顺序访问服务端1-网关:这一步主要是获取token进行验证,成功后把用户信息保存到请求头以供微服务调取因为微服务模块比较多,如果每一个都写拦截器会造成不必要的冗余,所以我们统一把拦截器放在网关模块 网关的信息传递流程为客户端=》断言=》......
  • netsvcs -p 是一个 Windows 系统服务组的标识符,其中包含了多个系统服务。这些服务通常
    netsvcs-p是一个Windows系统服务组的标识符,其中包含了多个系统服务。这些服务通常与网络和其他基础系统功能相关。例如,服务组中的服务可能包括自动更新服务、加密服务、时间同步服务等。这些服务的工作确保了系统的正常运行和与网络的连通性。关于这个命令的使用:netsv......
  • dcomlaunch 是 Windows 操作系统中的一个服务进程,负责启动和管理分布式组件对象模型(DC
    dcomlaunch是Windows操作系统中的一个服务进程,负责启动和管理分布式组件对象模型(DCOM)应用程序。DCOM是一种微软的远程过程调用(RPC)技术,允许运行在不同计算机上的软件组件相互通信和交互。具体来说,dcomlaunch服务进程的作用包括:启动和管理DCOM服务:dcomlaunch负责启动......
  • 进程与服务器初始化
    1.如何查看进程是否已经开启?systemctlstatus进程名psaux/-elf|grep进程名netstat/ss-lntup|grep进程名/:端口lsof-i:端口2.如何通过端口查看进程号?netstat-lntup|grep:端口ss-lntup|grep:端口lsof-i:端口3.对新服务器做过哪些初始化操作?安装操作......
  • vue启动本地服务不显示network访问链接
    在vue.config.js(或者配置config了的,就在config下的index.js)文件下设置devServer或者dev中的public属性值,需要修改为自己电脑的IPV4地址,获取IPV4地址方法,Win+R打开运行窗口,输入cmd,在命令行输入ipconfig回车后会出现一串信息,复制IPV4地址即可;module.exports={......
  • 如何自己写一个开机自启动服务?
    systemd服务介绍systemd是Linux下一个与SysV和LSB初始化脚本兼容的系统和服务管理器。systemd使用socket和D-Bus来开启服务,提供基于守护进程的按需启动策略,保留了Linuxcgroups的进程追踪功能,支持快照和系统状态恢复,维护挂载和自挂载点,实现了各服务间基于从属关系......
  • 一个客户端请求 跟服务网关 服务器 服务后端之间的流程是什么样的?
    一个客户端请求经过服务网关到达服务器和服务后端的流程通常包括以下几个步骤:客户端发起请求:客户端发送请求到服务网关,请求可以是HTTP请求、RPC请求等。服务网关路由:服务网关接收到请求后,根据配置的路由规则将请求路由到相应的服务后端。路由规则可以根据......