阅读此篇文章之前,你需要对Abpvnext 微服务样例项目
文章并没有详细说明远程服务的配置相关,各位可自行查阅官网文档,或者相关博客,下面会给出相关url地址
https://docs.abp.io/zh-Hans/abp/latest/API/Dynamic-CSharp-API-Clients
场景:
Abpvnext 微服务样例项目的基础上变更出的
https://www.cnblogs.com/william-xu/p/12537155.html
InternalGateWay.Host (内部网关)
BaseService(基础服务 用户_角色_组织_权限等)
BusinessService (业务服务)
感谢各位先驱大佬的探索与分享 @一曲肝肠断
那么回到我们本篇博客的主题:微服务间 服务通讯时如何使用abpvnext的动态api客户端? 例如 ,子服务(BusinessService 如何调用 BaseService中我们定义的接口方法)
第一步,检查BaseService项目中是否构建了BaseService.HttpApi.Client项目,若没有,补充下
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace BaseService.HttpApi.Client;
[DependsOn(
typeof(BaseServiceApplicationContractsModule)
)]
public class BaseServiceHttpApiClientModule : AbpModule
{
public const string RemoteServiceName = "Default";
// 添加动态api客户端
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHttpClientProxies(
typeof(BaseServiceApplicationContractsModule).Assembly,
RemoteServiceName
);
}
}
BaseService.Application项目中我实现了自己的UserAppService
[Authorize(IdentityPermissions.Users.Default)]
public class UserAppService : ApplicationService, IUserAppService
{
protected IdentityUserManager UserManager { get; }
protected IIdentityUserRepository UserRepository { get; }
public IIdentityRoleRepository RoleRepository { get; }
private readonly IRepository<Organization, Guid> _orgRepository;
private readonly IRepository<UserJob> _userJobsRepository;
private readonly IRepository<UserOrganization> _userOrgsRepository;
public UserAppService(
IdentityUserManager userManager,
IIdentityUserRepository userRepository,
IIdentityRoleRepository roleRepository,
IRepository<Organization, Guid> orgRepository,
IRepository<UserJob> userJobsRepository,
IRepository<UserOrganization> userOrgsRepository
)
{
UserManager = userManager;
UserRepository = userRepository;
RoleRepository = roleRepository;
_orgRepository = orgRepository;
_userJobsRepository = userJobsRepository;
_userOrgsRepository = userOrgsRepository;
}
public async Task<PagedResultDto<BaseIdentityUserDto>> GetAll(GetBaseIdentityUsersInput input)
{
var orgQueryable = await _orgRepository.GetQueryableAsync();
var userOrgQueryable = await _userOrgsRepository.GetQueryableAsync();
if (input.OrganizationId.HasValue)
{
var userDbSet = await UserRepository.GetDbSetAsync();
var org = await _orgRepository.GetAsync(input.OrganizationId.Value);
var orgs = await (await _orgRepository.GetQueryableAsync()).Where(_ => _.CascadeId.Contains(org.CascadeId)).ToListAsync();
var totalCount = await userOrgQueryable.Where(_ => orgs.Select(o => o.Id).Contains(_.OrganizationId))
.GroupBy(_ => _.UserId)
.LongCountAsync();
//TODO: Redis Query
var userIds = await userOrgQueryable.Where(_ => orgs.Select(o => o.Id).Contains(_.OrganizationId))
.Select(_ => _.UserId)
.Distinct()
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
.ToListAsync();
var items = await userDbSet.WhereIf(!string.IsNullOrWhiteSpace(input.Filter), _ => _.UserName.Contains(input.Filter))
.Where(_ => userIds.Contains(_.Id)).ToListAsync();
var userOrgs = await userOrgQueryable.Where(_ => items.Select(i => i.Id).Contains(_.UserId))
.ToListAsync();
var allOrg = await orgQueryable.Where(_ => userOrgs.Select(uo => uo.OrganizationId).Contains(_.Id))
.OrderBy(_ => _.CascadeId)
.ToListAsync();
var dtos = ObjectMapper.Map<List<IdentityUser>, List<BaseIdentityUserDto>>(items);
foreach (var dto in dtos)
{
var oids = userOrgs.Where(_ => _.UserId == dto.Id).Select(_ => _.OrganizationId);
dto.OrganizationNames = string.Join(", ", allOrg.Where(_ => oids.Contains(_.Id)).Select(_ => _.Name).ToArray());
}
return new PagedResultDto<BaseIdentityUserDto>(totalCount, dtos);
}
else
{
var totalCount = await UserRepository.GetCountAsync(input.Filter);
var items = await UserRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, input.Filter);
//TODO: Redis Query
var userOrgs = await userOrgQueryable.Where(_ => items.Select(i => i.Id).Contains(_.UserId))
.ToListAsync();
var orgs = await orgQueryable.Where(_ => userOrgs.Select(uo => uo.OrganizationId).Contains(_.Id))
.OrderBy(_ => _.CascadeId)
.ToListAsync();
var dtos = ObjectMapper.Map<List<IdentityUser>, List<BaseIdentityUserDto>>(items);
foreach (var dto in dtos)
{
var oids = userOrgs.Where(_ => _.UserId == dto.Id).Select(_ => _.OrganizationId);
dto.OrganizationNames = string.Join(", ", orgs.Where(_ => oids.Contains(_.Id)).Select(_ => _.Name).ToArray());
}
return new PagedResultDto<BaseIdentityUserDto>(totalCount, dtos);
}
}
}
然后在我们的需要用到BaseService方法的项目中引入HttpApi.Client项目
(大佬是推荐构建私有nuget包管理,然后引入,个人猜测是避免项目间循环引用,同时加强了版本管理,我呢,偷懒了,直接项目引用的)
例如Business.Applicattion中想要使用BaseService 的方法GetAll获取用户分页数据(我随便写的方法,不要在意方法名,他就是拿来取分页数据的,我懒得改动而已,你可以将他看为GetPage) ---- 0.0
首先我们直接在Business.Application项目中引用BaseService.HttpApi.Client项目
然后像我们注入自己项目的接口一样,直接注入BaseService相关的接口
public class BusinessTestService: ApplicationServiceBase
{
//内部网关通讯测试
private readonly IUserAppService _userBaseAppService;
public BusinessTestService(IUserAppService userBaseAppService)
{
_userBaseAppService = userBaseAppService;
}
///
/// 内部网关通讯测试
///
///
public async Task<PageResultDto<BaseIdentityUserDto>> TestInternalGateWay()
{
var pageParams = new GetBaseIdentityUsersInput
{
Filter = null,
};
var users = await _userBaseAppService.GetAll(pageParams);
return users;
}
按照官方文档的动态API客户端的说明,编码工作已经完成了
但是你会得到
Could not found remote action
的奇怪错误
我们应该猜测,按照官方文档的,是直接访问相关项目部署地址的接口,而微服务项目是通过内部网关 InternalGateway 转发的相关请求,
故此时, 我们应该在InternalGateWay 内部网关项目中,引用 BaseService.HttpApi 项目(此处单纯的就是为网关提供接口定义,根据转发规则,相关接口请求会实际转发至你配置的子服务端去)
此时,再次启动调试,你会发现,已经能够正常获取到数据了
其实,动态Api客户端的设计,真的是非常贴合.netcore开发,大佬们是真的花了心思实现的这一块,极大的提高了开发效率.再次感谢前辈们的付出与分享.希望更多的人一起学习netcore… 也希望有朝一日,我们国人也能出一套自己的强大的开发框架