Admin.NET开源版微服务改造记录

将Admin.NET.Core项目拆分成两个项目:Admin.NET.Common,Admin.NET.Core

Admin.NET.Common放基础工具类

Admin.NET.Core放框架核心类库

AspireApp.AppHost中的AppHost.cs配置:

using Aspire.Hosting;
using Aspire.Hosting.Dapr;
using AspireApp.AppHost;

var builder = DistributedApplication.CreateBuilder(args);

var postgresQL = builder.AddPostgres("postgresQL")
                        .WithImage("ankane/pgvector")
                        .WithImageTag("latest")
                        .WithLifetime(ContainerLifetime.Persistent)
                        .WithHealthCheck()
                        .WithPgWeb();
var postgres = postgresQL.AddDatabase("postgres");
var postgres2 = postgresQL.AddDatabase("postgres2");

//var redis = builder.AddRedis("redis").WithLifetime(ContainerLifetime.Persistent)
//                    .WithHealthCheck()
//                    .WithRedisCommander();

// 使用 RabbitMQ 作为 Pub/Sub 组件
//var rabbitmq = builder.AddRabbitMQ("rabbitmq")
//                        .WithLifetime(ContainerLifetime.Persistent)
//                        .WithHealthCheck()
//                        .WithManagementPlugin();

// 1. 定义共享目录的绝对路径(建议指向 admin-net-core 的实际目录或独立的 shared 目录)
var uploadPath = Path.GetFullPath("../Admin.NET/Admin.NET.Core/wwwroot/upload");

// 确保目录存在
if (!Directory.Exists(uploadPath))
{
    Directory.CreateDirectory(uploadPath);
}

// Admin.NET.Core 使用 Furion 的 Knife4j UI,不使用 Aspire 的 Swagger UI
var core = builder.AddProject("admin-net-core")
    .WithReference(postgres)
    .WaitFor(postgres)
    .WithSwaggerUI();

var baseApi = builder.AddProject("base")
    .WithReference(postgres2)
    .WithSwaggerUI()
    .WithReference(core)
    .WaitFor(postgres2)
    .WaitFor(core);

var yarp = builder.AddProject("aspireapp-yarp")
    .WithEndpoint( port: 5008, scheme: "http", name: "yarp-http")    // 指定唯一名称
    .WithEndpoint( port: 7008, scheme: "https", name: "yarp-https") // 指定唯一名称
    .WithReference(core)
    .WithReference(baseApi)
    .WaitFor(core)
    .WaitFor(baseApi);

//var frontend = builder.AddNodeApp(
//       "frontend",
//       scriptPath: "scripts/run-pnpm.cjs",
//       workingDirectory: "../Web",
//       args: new[] { "dev" }
//   )
//   .WithReference(yarp)
//   .WaitFor(yarp);

builder.Build().Run();


AIP网关:AspireApp.Yarp

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.Services.AddReverseProxy()
                .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
                .AddServiceDiscoveryDestinationResolver();

var app = builder.Build();

app.MapDefaultEndpoints();
app.MapReverseProxy();

app.MapGet("/", () => "Hello World!");

app.Run();

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "core": {
        "ClusterId": "core",
        "Match": {
          "Path": "/core/{**remainder}"
        },
        "Transforms": [
          { "PathRemovePrefix": "/core" },
          { "PathPrefix": "/" },
          { "RequestHeaderOriginalHost": "true" }
        ]
      },
      "base": {
        "ClusterId": "base",
        "Match": {
          "Path": "/base/{**remainder}"
        },
        "Transforms": [
          { "PathRemovePrefix": "/base" },
          { "PathPrefix": "/" },
          { "RequestHeaderOriginalHost": "true" }
        ]
      }
    },
    "Clusters": {
      "core": {
        "Destinations": {
          "base_destination": {
            "Address": "http+https://admin-net-core"
          }
        }
      },
      "base": {
        "Destinations": {
          "base_destination": {
            "Address": "http+https://base"
          }
        }
      }
    }
  }
}

业务项目(base项目)调用核心项目(core项目)的方法

我们使用Refit来服务调用

在base项目中的Startup.cs中

        // 注册 JwtTokenHandler - 用于自动添加 JWT Token 到 Refit 请求
        services.AddTransient();
        
        // 配置 Refit 客户端并添加 JWT Token 处理器
        //系统配置
        services.AddRefitClient(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler();
        //系统菜单     
        services.AddRefitClient(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler();
        //组织机构
        services.AddRefitClient(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler();
        //文件
        services.AddRefitClient(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler();

        services.AddRefitClient(refitSettings)
            .ConfigureHttpClient(c => c.BaseAddress = new("https+http://apiservice"))
            .AddHttpMessageHandler();

其中最重要的权限接口调用

using Refit;
using GetAttribute = Refit.GetAttribute;
namespace Base.Rest;

public interface IMenuService
{
    [Get("/api/sysMenu/getOwnBtnPermList")]
    Task> GetOwnBtnPermList();

    [Get("/api/sysMenu/getAllBtnPermList")]
    Task> GetAllBtnPermList();
}

修改base项目的权限验证方法JwtHandler.cs

IConfigService IMenuService 就是我们定义的Refit接口,这样,每次权限验证就会调用core项目的API接口

using Admin.NET.Core;
using Admin.NET.Core.Service;
using Base.Rest;
using Furion;
using Furion.Authorization;
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Threading.Tasks;

namespace Admin.NET.Application;

public class JwtHandler : AppAuthorizeHandler
{
    private readonly SysCacheService _sysCacheService = App.GetRequiredService();
    private readonly IConfigService _sysConfigService = App.GetRequiredService();
    private static readonly IMenuService SysMenuService = App.GetRequiredService();

    /// 
    /// 自动刷新Token
    /// 
    /// 
    /// 
    /// 
    public override async Task HandleAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
    {
        // 若当前账号存在黑名单中则授权失败
        if (_sysCacheService.ExistKey($"{CacheConst.KeyBlacklist}{context.User.FindFirst(ClaimConst.UserId)?.Value}"))
        {
            context.Fail();
            context.GetCurrentHttpContext().SignoutToSwagger();
            return;
        }

        var tokenExpire = await _sysConfigService.GetTokenExpire();
        var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire();
        if (JWTEncryption.AutoRefreshToken(context, context.GetCurrentHttpContext(), tokenExpire.Result, refreshTokenExpire.Result))
        {
            await AuthorizeHandleAsync(context);
        }
        else
        {
            context.Fail(); // 授权失败
            var currentHttpContext = context.GetCurrentHttpContext();
            if (currentHttpContext == null) return;

            // 跳过由于 SignatureAuthentication 引发的失败
            if (currentHttpContext.Items.ContainsKey(SignatureAuthenticationDefaults.AuthenticateFailMsgKey)) return;
            currentHttpContext.SignoutToSwagger();
        }
    }

    public override async Task PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
    {
        // 已自动验证 Jwt Token 有效性
        return await CheckAuthorizeAsync(httpContext);
    }

    /// 
    /// 权限校验核心逻辑
    /// 
    /// 
    /// 
    private static async Task CheckAuthorizeAsync(DefaultHttpContext httpContext)
    {
        // 登录模式判断PC、APP
        if (App.User.FindFirst(ClaimConst.LoginMode)?.Value == ((int)LoginModeEnum.APP).ToString())
            return true;

        // 排除超管
        if (App.User.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString())
            return true;

        // 路由名称
        var routeName = httpContext.Request.Path.StartsWithSegments("/api")
            ? httpContext.Request.Path.Value![5..].Replace("/", ":")
            : httpContext.Request.Path.Value![1..].Replace("/", ":");

        // 获取用户拥有按钮权限集合
        var ownBtnPermList = await SysMenuService.GetOwnBtnPermList();
        if (ownBtnPermList.Exists(u => routeName.Equals(u, StringComparison.CurrentCultureIgnoreCase)))
            return true;

        // 获取系统所有按钮权限集合
        var allBtnPermList = await SysMenuService.GetAllBtnPermList();
        return allBtnPermList.TrueForAll(u => !routeName.Equals(u, StringComparison.CurrentCultureIgnoreCase));
    }
}

项目地址

AspireApp: Admin.NET微服务版

Admin.NET: 🔥🔥🔥 Admin.NET 基于 .NET8/10 (Furion/SqlSugar) 实现的通用权限开发框架,前端采用 Vue3/Element-plus,代码简洁、易扩展。整合最新技术,模块插件式开发,前后端分离,开箱即用。集成多租户、缓存、数据校验、鉴权、事件总线、动态API、通讯、远程请求、任务调度、打印等众多黑科技。让开发更简单、更通用、更流行!


原文地址: https://www.cveoy.top/t/topic/qFXw 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录