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

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<Projects.Admin_NET_Core>("admin-net-core")
    .WithReference(postgres)
    .WaitFor(postgres)
    .WithSwaggerUI();

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

var yarp = builder.AddProject<Projects.AspireApp_Yarp>("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<JwtTokenHandler>();
        
        // 配置 Refit 客户端并添加 JWT Token 处理器
        //系统配置
        services.AddRefitClient<IConfigService>(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler<JwtTokenHandler>();
        //系统菜单     
        services.AddRefitClient<IMenuService>(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler<JwtTokenHandler>();
        //组织机构
        services.AddRefitClient<IOrgService>(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler<JwtTokenHandler>();
        //文件
        services.AddRefitClient<IFileService>(refitSettings)
               .ConfigureHttpClient(c => c.BaseAddress = new("https+http://admin-net-core"))
               .AddHttpMessageHandler<JwtTokenHandler>();

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

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

复制代码
using Refit;
using GetAttribute = Refit.GetAttribute;
namespace Base.Rest;

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

    [Get("/api/sysMenu/getAllBtnPermList")]
    Task<List<string>> 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<SysCacheService>();
    private readonly IConfigService _sysConfigService = App.GetRequiredService<IConfigService>();
    private static readonly IMenuService SysMenuService = App.GetRequiredService<IMenuService>();

    /// <summary>
    /// 自动刷新Token
    /// </summary>
    /// <param name="context"></param>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    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<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
    {
        // 已自动验证 Jwt Token 有效性
        return await CheckAuthorizeAsync(httpContext);
    }

    /// <summary>
    /// 权限校验核心逻辑
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    private static async Task<bool> 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));
    }
}
相关推荐
小满Autumn3 小时前
log4net 日志框架 — 从配置到实战速查手册
笔记·c#·.net·wpf·上位机·log4net
code bean8 小时前
【LangChain】检索器完全指南:从向量检索到生产级 RAG 架构
java·开发语言·微服务
神奇的小猴程序员9 小时前
提升 AI 与开发效率!两款实用 Skill 开源工具 FunctionCool-Skill & StyleCool-Skill 深度体验
人工智能·开源·s
Cosolar9 小时前
Docsify零构建文档站完全指南:从快速搭建到企业级部署
前端·开源·github
ceclar12313 小时前
C# 的任务并行库(TPL)
开发语言·c#·.net
分布式存储与RustFS14 小时前
基于Rust的国产开源对象存储RustFS:S3 Table对Iceberg数据湖的适配详解
rust·开源·iceberg·对象存储·rustfs·minio平替·s3 table
JGDT_15 小时前
ERP重塑与未来趋势:SAP的实践及大一统格局(上)
大数据·人工智能·安全·架构·开源
ceclar12315 小时前
C#异步编程async与await
c#·.net
恼书:-(空寄16 小时前
接口乱改直接炸线上!微服务接口版本控制全方案:URL_请求头版本+接口兼容原则,老旧系统无痛迭代
微服务·架构
步步为营DotNet17 小时前
借助 C# 14 特性强化 .NET 后端数据验证的深度实践
java·c#·.net