znlgis 博客

GIS开发与技术分享

第三章:项目架构与核心模块解析

目录

  1. 整体架构设计
  2. 后端项目结构详解
  3. 核心层Admin.NET.Core解析
  4. 应用层Admin.NET.Application解析
  5. Web层架构解析
  6. 前端项目结构详解
  7. 数据流转机制
  8. 依赖注入与服务注册

1. 整体架构设计

1.1 架构概览

Admin.NET采用经典的分层架构设计,结合前后端分离的开发模式。整体架构可以分为以下几个层次:

┌─────────────────────────────────────────────────────────┐
│                    前端层 (Vue3 + Element Plus)          │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │  视图层  │ │ 状态管理 │ │ 路由层  │ │ API层   │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────┘
                          │ HTTP/HTTPS
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    Web入口层 (Entry)                     │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │ 控制器  │ │ 中间件  │ │ 过滤器  │ │ 配置    │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    Web核心层 (Web.Core)                  │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │ 启动配置 │ │ 认证授权 │ │ Swagger │ │ 跨域配置 │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    应用层 (Application)                  │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │ 业务服务 │ │ 事件处理 │ │ DTO    │ │ 配置    │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    核心层 (Core)                         │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │ 系统服务 │ │ 实体模型 │ │ 仓储层  │ │ 工具类  │       │
│  │ 缓存服务 │ │ 枚举定义 │ │ 扩展方法 │ │ 常量定义 │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    基础设施层                            │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │ SqlSugar │ │ Redis   │ │ ES      │ │ OSS     │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────┘

1.2 分层职责

前端层(Web)

Web入口层(Admin.NET.Web.Entry)

Web核心层(Admin.NET.Web.Core)

应用层(Admin.NET.Application)

核心层(Admin.NET.Core)

1.3 设计原则

Admin.NET遵循以下设计原则:

单一职责原则(SRP): 每个类只负责一个功能领域,服务类只处理业务逻辑,实体类只定义数据结构。

开闭原则(OCP): 通过接口和抽象类实现扩展,新增功能不需要修改现有代码。

依赖倒置原则(DIP): 高层模块不依赖低层模块,两者都依赖抽象。通过依赖注入实现。

接口隔离原则(ISP): 使用小而专一的接口,而不是大而全的接口。


2. 后端项目结构详解

2.1 解决方案结构

Admin.NET.sln
├── Admin.NET.Core                 # 核心层
├── Admin.NET.Application          # 应用层
├── Admin.NET.Web.Core             # Web核心层
├── Admin.NET.Web.Entry            # Web入口层
├── Admin.NET.Test                 # 单元测试
└── Plugins/                       # 插件项目
    ├── Admin.NET.Plugin.ApprovalFlow
    ├── Admin.NET.Plugin.DingTalk
    ├── Admin.NET.Plugin.GoView
    ├── Admin.NET.Plugin.K3Cloud
    ├── Admin.NET.Plugin.ReZero
    └── Admin.NET.Plugin.WorkWeixin

2.2 项目依赖关系

Admin.NET.Web.Entry
    ├── Admin.NET.Web.Core
    ├── Admin.NET.Application
    └── Plugins (可选)

Admin.NET.Web.Core
    └── Admin.NET.Core

Admin.NET.Application
    └── Admin.NET.Core

Admin.NET.Core
    └── 第三方依赖包

2.3 核心依赖包

查看Admin.NET.Core.csproj中的主要依赖:

<ItemGroup>
    <!-- Furion框架 -->
    <PackageReference Include="Furion" Version="4.x.x" />
    
    <!-- SqlSugar ORM -->
    <PackageReference Include="SqlSugarCore" Version="5.x.x" />
    
    <!-- Redis缓存 -->
    <PackageReference Include="NewLife.Redis" Version="x.x.x" />
    
    <!-- Excel导入导出 -->
    <PackageReference Include="Magicodes.IE.Excel" Version="x.x.x" />
    
    <!-- 微信SDK -->
    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="x.x.x" />
    
    <!-- 验证码 -->
    <PackageReference Include="Lazy.Captcha.Core" Version="x.x.x" />
    
    <!-- 限流组件 -->
    <PackageReference Include="AspNetCoreRateLimit" Version="x.x.x" />
    
    <!-- 任务调度 -->
    <PackageReference Include="Sundial" Version="x.x.x" />
</ItemGroup>

3. 核心层Admin.NET.Core解析

3.1 目录结构

Admin.NET.Core/
├── Attribute/           # 自定义特性
├── Cache/               # 缓存相关
├── Const/               # 常量定义
├── ElasticSearch/       # ES日志
├── Entity/              # 实体定义
├── Enum/                # 枚举定义
├── EventBus/            # 事件总线
├── Extension/           # 扩展方法
├── Hub/                 # SignalR Hub
├── Job/                 # 后台任务
├── Logging/             # 日志配置
├── Option/              # 配置选项
├── SeedData/            # 种子数据
├── Service/             # 系统服务
├── SignalR/             # SignalR配置
├── SignatureAuth/       # 签名认证
├── SqlSugar/            # ORM配置
├── Update/              # 更新相关
├── Utils/               # 工具类
└── GlobalUsings.cs      # 全局using

3.2 实体定义(Entity)

3.2.1 实体基类

// EntityBase.cs
/// <summary>
/// 实体基类
/// </summary>
public abstract class EntityBase : IEntity
{
    /// <summary>
    /// 主键Id
    /// </summary>
    [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
    public virtual long Id { get; set; }
}

/// <summary>
/// 实体操作基类
/// </summary>
public abstract class EntityBaseData : EntityBase
{
    /// <summary>
    /// 创建时间
    /// </summary>
    [SugarColumn(ColumnDescription = "创建时间", IsOnlyIgnoreUpdate = true)]
    public virtual DateTime? CreateTime { get; set; }

    /// <summary>
    /// 更新时间
    /// </summary>
    [SugarColumn(ColumnDescription = "更新时间", IsOnlyIgnoreInsert = true)]
    public virtual DateTime? UpdateTime { get; set; }

    /// <summary>
    /// 创建者Id
    /// </summary>
    [SugarColumn(ColumnDescription = "创建者Id", IsOnlyIgnoreUpdate = true)]
    public virtual long? CreateUserId { get; set; }

    /// <summary>
    /// 创建者姓名
    /// </summary>
    [SugarColumn(ColumnDescription = "创建者姓名", Length = 64, IsOnlyIgnoreUpdate = true)]
    public virtual string? CreateUserName { get; set; }

    /// <summary>
    /// 修改者Id
    /// </summary>
    [SugarColumn(ColumnDescription = "修改者Id", IsOnlyIgnoreInsert = true)]
    public virtual long? UpdateUserId { get; set; }

    /// <summary>
    /// 修改者姓名
    /// </summary>
    [SugarColumn(ColumnDescription = "修改者姓名", Length = 64, IsOnlyIgnoreInsert = true)]
    public virtual string? UpdateUserName { get; set; }

    /// <summary>
    /// 是否删除
    /// </summary>
    [SugarColumn(ColumnDescription = "是否删除")]
    public virtual bool IsDelete { get; set; } = false;
}

/// <summary>
/// 租户实体基类
/// </summary>
public abstract class EntityTenant : EntityBaseData
{
    /// <summary>
    /// 租户Id
    /// </summary>
    [SugarColumn(ColumnDescription = "租户Id")]
    public virtual long? TenantId { get; set; }
}

3.2.2 系统核心实体

用户实体(SysUser)

/// <summary>
/// 系统用户表
/// </summary>
[SugarTable(null, "系统用户表")]
[SystemTable]
public class SysUser : EntityTenant
{
    /// <summary>
    /// 账号
    /// </summary>
    [SugarColumn(ColumnDescription = "账号", Length = 64)]
    [Required, MaxLength(64)]
    public virtual string Account { get; set; }

    /// <summary>
    /// 密码
    /// </summary>
    [SugarColumn(ColumnDescription = "密码", Length = 128)]
    [MaxLength(128)]
    [JsonIgnore]
    public virtual string Password { get; set; }

    /// <summary>
    /// 真实姓名
    /// </summary>
    [SugarColumn(ColumnDescription = "真实姓名", Length = 64)]
    [MaxLength(64)]
    public virtual string? RealName { get; set; }

    /// <summary>
    /// 昵称
    /// </summary>
    [SugarColumn(ColumnDescription = "昵称", Length = 64)]
    [MaxLength(64)]
    public virtual string? NickName { get; set; }

    /// <summary>
    /// 头像
    /// </summary>
    [SugarColumn(ColumnDescription = "头像", Length = 512)]
    public virtual string? Avatar { get; set; }

    /// <summary>
    /// 性别-男_1、女_2
    /// </summary>
    [SugarColumn(ColumnDescription = "性别")]
    public virtual GenderEnum Sex { get; set; } = GenderEnum.Male;

    /// <summary>
    /// 年龄
    /// </summary>
    [SugarColumn(ColumnDescription = "年龄")]
    public virtual int Age { get; set; }

    /// <summary>
    /// 出生日期
    /// </summary>
    [SugarColumn(ColumnDescription = "出生日期")]
    public virtual DateTime? Birthday { get; set; }

    /// <summary>
    /// 手机号码
    /// </summary>
    [SugarColumn(ColumnDescription = "手机号码", Length = 16)]
    public virtual string? Phone { get; set; }

    /// <summary>
    /// 邮箱
    /// </summary>
    [SugarColumn(ColumnDescription = "邮箱", Length = 64)]
    public virtual string? Email { get; set; }

    /// <summary>
    /// 机构Id
    /// </summary>
    [SugarColumn(ColumnDescription = "机构Id")]
    public virtual long OrgId { get; set; }

    /// <summary>
    /// 职位Id
    /// </summary>
    [SugarColumn(ColumnDescription = "职位Id")]
    public virtual long PosId { get; set; }

    /// <summary>
    /// 账号类型
    /// </summary>
    [SugarColumn(ColumnDescription = "账号类型")]
    public virtual AccountTypeEnum AccountType { get; set; } = AccountTypeEnum.NormalUser;

    /// <summary>
    /// 状态
    /// </summary>
    [SugarColumn(ColumnDescription = "状态")]
    public virtual StatusEnum Status { get; set; } = StatusEnum.Enable;

    /// <summary>
    /// 排序
    /// </summary>
    [SugarColumn(ColumnDescription = "排序")]
    public virtual int OrderNo { get; set; } = 100;

    /// <summary>
    /// 备注
    /// </summary>
    [SugarColumn(ColumnDescription = "备注", Length = 256)]
    public virtual string? Remark { get; set; }
}

菜单实体(SysMenu)

/// <summary>
/// 系统菜单表
/// </summary>
[SugarTable(null, "系统菜单表")]
[SystemTable]
public class SysMenu : EntityBase
{
    /// <summary>
    /// 父Id
    /// </summary>
    [SugarColumn(ColumnDescription = "父Id")]
    public long Pid { get; set; }

    /// <summary>
    /// 菜单类型(1目录 2菜单 3按钮)
    /// </summary>
    [SugarColumn(ColumnDescription = "菜单类型")]
    public MenuTypeEnum Type { get; set; }

    /// <summary>
    /// 菜单名称
    /// </summary>
    [SugarColumn(ColumnDescription = "菜单名称", Length = 64)]
    [Required, MaxLength(64)]
    public string Title { get; set; }

    /// <summary>
    /// 路由名称
    /// </summary>
    [SugarColumn(ColumnDescription = "路由名称", Length = 64)]
    public string? Name { get; set; }

    /// <summary>
    /// 路由地址
    /// </summary>
    [SugarColumn(ColumnDescription = "路由地址", Length = 128)]
    public string? Path { get; set; }

    /// <summary>
    /// 组件路径
    /// </summary>
    [SugarColumn(ColumnDescription = "组件路径", Length = 128)]
    public string? Component { get; set; }

    /// <summary>
    /// 重定向
    /// </summary>
    [SugarColumn(ColumnDescription = "重定向", Length = 128)]
    public string? Redirect { get; set; }

    /// <summary>
    /// 权限标识
    /// </summary>
    [SugarColumn(ColumnDescription = "权限标识", Length = 128)]
    public string? Permission { get; set; }

    /// <summary>
    /// 菜单图标
    /// </summary>
    [SugarColumn(ColumnDescription = "菜单图标", Length = 64)]
    public string? Icon { get; set; }

    /// <summary>
    /// 是否内嵌
    /// </summary>
    [SugarColumn(ColumnDescription = "是否内嵌")]
    public bool IsIframe { get; set; }

    /// <summary>
    /// 外链链接
    /// </summary>
    [SugarColumn(ColumnDescription = "外链链接", Length = 256)]
    public string? OutLink { get; set; }

    /// <summary>
    /// 是否隐藏
    /// </summary>
    [SugarColumn(ColumnDescription = "是否隐藏")]
    public bool IsHide { get; set; }

    /// <summary>
    /// 是否缓存
    /// </summary>
    [SugarColumn(ColumnDescription = "是否缓存")]
    public bool IsKeepAlive { get; set; } = true;

    /// <summary>
    /// 排序
    /// </summary>
    [SugarColumn(ColumnDescription = "排序")]
    public int OrderNo { get; set; } = 100;

    /// <summary>
    /// 状态
    /// </summary>
    [SugarColumn(ColumnDescription = "状态")]
    public StatusEnum Status { get; set; } = StatusEnum.Enable;
}

3.3 服务层(Service)

3.3.1 服务基类

// BaseService.cs
/// <summary>
/// 服务基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
public class BaseService<TEntity> where TEntity : class, new()
{
    protected readonly SqlSugarRepository<TEntity> _rep;
    protected readonly IUserManager _userManager;

    public BaseService(SqlSugarRepository<TEntity> rep, IUserManager userManager)
    {
        _rep = rep;
        _userManager = userManager;
    }

    /// <summary>
    /// 获取当前用户Id
    /// </summary>
    protected long UserId => _userManager.UserId;

    /// <summary>
    /// 获取当前租户Id
    /// </summary>
    protected long? TenantId => _userManager.TenantId;

    /// <summary>
    /// 是否为超级管理员
    /// </summary>
    protected bool IsSuperAdmin => _userManager.SuperAdmin;
}

3.3.2 用户服务示例

/// <summary>
/// 系统用户服务
/// </summary>
[ApiDescriptionSettings(Order = 490)]
public class SysUserService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<SysUser> _sysUserRep;
    private readonly SysCacheService _sysCacheService;
    private readonly SysOrgService _sysOrgService;
    private readonly SysRoleService _sysRoleService;
    
    public SysUserService(
        SqlSugarRepository<SysUser> sysUserRep,
        SysCacheService sysCacheService,
        SysOrgService sysOrgService,
        SysRoleService sysRoleService)
    {
        _sysUserRep = sysUserRep;
        _sysCacheService = sysCacheService;
        _sysOrgService = sysOrgService;
        _sysRoleService = sysRoleService;
    }

    /// <summary>
    /// 获取用户分页列表
    /// </summary>
    [DisplayName("获取用户分页列表")]
    public async Task<SqlSugarPagedList<SysUser>> Page(PageUserInput input)
    {
        var orgIdList = await _sysOrgService.GetChildIdListWithSelfById(input.OrgId);
        
        return await _sysUserRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account.Contains(input.Account))
            .WhereIF(!string.IsNullOrWhiteSpace(input.RealName), u => u.RealName.Contains(input.RealName))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Phone), u => u.Phone.Contains(input.Phone))
            .WhereIF(input.OrgId > 0, u => orgIdList.Contains(u.OrgId))
            .WhereIF(input.AccountType.HasValue, u => u.AccountType == input.AccountType)
            .OrderBy(u => u.OrderNo)
            .ToPagedListAsync(input.Page, input.PageSize);
    }

    /// <summary>
    /// 增加用户
    /// </summary>
    [ApiDescriptionSettings(Name = "Add"), HttpPost]
    [DisplayName("增加用户")]
    public async Task<long> Add(AddUserInput input)
    {
        // 验证账号是否存在
        var isExist = await _sysUserRep.IsAnyAsync(u => u.Account == input.Account);
        if (isExist)
            throw Oops.Oh(ErrorCodeEnum.D1003);

        var user = input.Adapt<SysUser>();
        user.Password = CryptogramUtil.Encrypt(input.Password);
        
        await _sysUserRep.InsertAsync(user);
        
        // 保存用户角色关系
        await _sysRoleService.GrantUserRole(new GrantUserRoleInput
        {
            UserId = user.Id,
            RoleIdList = input.RoleIdList
        });
        
        return user.Id;
    }

    /// <summary>
    /// 更新用户
    /// </summary>
    [ApiDescriptionSettings(Name = "Update"), HttpPost]
    [DisplayName("更新用户")]
    public async Task Update(UpdateUserInput input)
    {
        // 验证账号是否存在
        var isExist = await _sysUserRep.IsAnyAsync(u => u.Account == input.Account && u.Id != input.Id);
        if (isExist)
            throw Oops.Oh(ErrorCodeEnum.D1003);

        var user = input.Adapt<SysUser>();
        await _sysUserRep.AsUpdateable(user)
            .IgnoreColumns(u => new { u.Password, u.AccountType })
            .ExecuteCommandAsync();

        // 清除缓存
        _sysCacheService.Remove(CacheConst.KeyUserInfo + user.Id);
    }

    /// <summary>
    /// 删除用户
    /// </summary>
    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
    [DisplayName("删除用户")]
    public async Task Delete(DeleteUserInput input)
    {
        var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.Id);
        if (user == null)
            throw Oops.Oh(ErrorCodeEnum.D1002);

        // 系统管理员不能删除
        if (user.AccountType == AccountTypeEnum.SuperAdmin)
            throw Oops.Oh(ErrorCodeEnum.D1014);

        await _sysUserRep.FakeDeleteAsync(user);

        // 清除缓存
        _sysCacheService.Remove(CacheConst.KeyUserInfo + user.Id);
    }
}

3.4 缓存服务(Cache)

/// <summary>
/// 系统缓存服务
/// </summary>
public class SysCacheService : ITransient
{
    private readonly ICache _cache;

    public SysCacheService(ICache cache)
    {
        _cache = cache;
    }

    /// <summary>
    /// 获取缓存
    /// </summary>
    public T Get<T>(string key)
    {
        return _cache.Get<T>(key);
    }

    /// <summary>
    /// 设置缓存
    /// </summary>
    public void Set(string key, object value, TimeSpan? expiry = null)
    {
        if (expiry.HasValue)
            _cache.Set(key, value, expiry.Value);
        else
            _cache.Set(key, value);
    }

    /// <summary>
    /// 移除缓存
    /// </summary>
    public void Remove(string key)
    {
        _cache.Remove(key);
    }

    /// <summary>
    /// 检查缓存是否存在
    /// </summary>
    public bool Exists(string key)
    {
        return _cache.ContainsKey(key);
    }

    /// <summary>
    /// 按前缀移除缓存
    /// </summary>
    public void RemoveByPrefix(string prefix)
    {
        var keys = _cache.Keys.Where(k => k.StartsWith(prefix));
        foreach (var key in keys)
        {
            _cache.Remove(key);
        }
    }
}

3.5 工具类(Utils)

/// <summary>
/// 加密工具类
/// </summary>
public static class CryptogramUtil
{
    /// <summary>
    /// SM2加密
    /// </summary>
    public static string SM2Encrypt(string plainText)
    {
        // SM2加密实现
    }

    /// <summary>
    /// SM2解密
    /// </summary>
    public static string SM2Decrypt(string cipherText)
    {
        // SM2解密实现
    }

    /// <summary>
    /// SM3摘要
    /// </summary>
    public static string SM3Hash(string data)
    {
        // SM3摘要实现
    }

    /// <summary>
    /// SM4加密
    /// </summary>
    public static string SM4Encrypt(string plainText, string key)
    {
        // SM4加密实现
    }

    /// <summary>
    /// MD5加密
    /// </summary>
    public static string Encrypt(string text)
    {
        return MD5Encryption.Encrypt(text);
    }
}

4. 应用层Admin.NET.Application解析

4.1 目录结构

Admin.NET.Application/
├── Configuration/       # 应用配置
├── Const/               # 业务常量
├── Entity/              # 业务实体
├── EventBus/            # 事件处理
├── OpenApi/             # 开放接口
├── GlobalUsings.cs      # 全局using
└── Startup.cs           # 启动配置

4.2 应用配置

// Startup.cs
[AppStartup(100)]
public class Startup : AppStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // 注册应用层服务
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 配置应用中间件
    }
}

4.3 业务实体示例

// Entity/Business.cs
/// <summary>
/// 业务表示例
/// </summary>
[SugarTable(null, "业务表示例")]
public class Business : EntityTenant
{
    /// <summary>
    /// 业务名称
    /// </summary>
    [SugarColumn(ColumnDescription = "业务名称", Length = 64)]
    public string Name { get; set; }

    /// <summary>
    /// 业务编码
    /// </summary>
    [SugarColumn(ColumnDescription = "业务编码", Length = 64)]
    public string Code { get; set; }

    /// <summary>
    /// 业务状态
    /// </summary>
    [SugarColumn(ColumnDescription = "业务状态")]
    public StatusEnum Status { get; set; }
}

4.4 事件处理

// EventBus/BusinessEventHandler.cs
/// <summary>
/// 业务事件处理
/// </summary>
public class BusinessEventHandler : IEventSubscriber
{
    private readonly ILogger<BusinessEventHandler> _logger;

    public BusinessEventHandler(ILogger<BusinessEventHandler> logger)
    {
        _logger = logger;
    }

    /// <summary>
    /// 处理业务创建事件
    /// </summary>
    [EventSubscribe("Business:Created")]
    public async Task HandleBusinessCreated(EventHandlerExecutingContext context)
    {
        var business = (Business)context.Source.Payload;
        _logger.LogInformation($"业务创建事件:{business.Name}");
        
        // 处理业务逻辑
        await Task.CompletedTask;
    }
}

5. Web层架构解析

5.1 Web.Core配置

// Startup.cs
[AppStartup(100)]
public class Startup : AppStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // 配置跨域
        services.AddCorsAccessor();

        // 配置控制器
        services.AddControllers()
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.Converters.AddDateTimeTypeConverters();
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            })
            .AddInjectWithUnifyResult<AdminResultProvider>();

        // 配置SignalR
        services.AddSignalR()
            .AddNewtonsoftJsonProtocol();

        // 配置Swagger
        services.AddSwaggerGen(options =>
        {
            // Swagger配置
        });

        // 配置认证授权
        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true);

        // 配置限流
        services.AddIpRateLimiting();

        // 配置缓存
        services.AddCache();

        // 配置SqlSugar
        services.AddSqlSugar();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 启用跨域
        app.UseCorsAccessor();

        // 启用Swagger
        app.UseSwagger();
        app.UseSwaggerUI();

        // 启用静态文件
        app.UseStaticFiles();

        // 启用路由
        app.UseRouting();

        // 启用认证授权
        app.UseAuthentication();
        app.UseAuthorization();

        // 启用端点
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapHub<OnlineUserHub>("/hubs/onlineUser");
        });
    }
}

5.2 JWT认证处理

/// <summary>
/// JWT认证处理器
/// </summary>
public class JwtHandler : AppAuthorizeHandler
{
    public override async Task<bool> PipelineAsync(AuthorizationHandlerContext context, 
        DefaultHttpContext httpContext)
    {
        // 验证Token有效性
        var userId = context.User.FindFirstValue(ClaimConst.UserId);
        if (string.IsNullOrEmpty(userId))
            return false;

        // 验证用户状态
        var user = await GetUserById(long.Parse(userId));
        if (user == null || user.Status != StatusEnum.Enable)
            return false;

        // 验证权限
        return await CheckPermission(context, httpContext);
    }

    private async Task<bool> CheckPermission(AuthorizationHandlerContext context, 
        DefaultHttpContext httpContext)
    {
        // 获取当前请求的权限标识
        var permission = httpContext.GetEndpoint()?.Metadata
            .GetMetadata<PermissionAttribute>()?.Permission;

        if (string.IsNullOrEmpty(permission))
            return true;

        // 验证用户是否有该权限
        var userPermissions = await GetUserPermissions(context.User);
        return userPermissions.Contains(permission);
    }
}

5.3 统一结果返回

/// <summary>
/// Admin结果提供器
/// </summary>
public class AdminResultProvider : IUnifyResultProvider
{
    public IActionResult OnSucceeded(ActionExecutedContext context, object data)
    {
        return new JsonResult(new
        {
            code = 200,
            success = true,
            data = data,
            message = "操作成功"
        });
    }

    public IActionResult OnException(ExceptionContext context, Exception exception)
    {
        return new JsonResult(new
        {
            code = GetErrorCode(exception),
            success = false,
            data = (object)null,
            message = exception.Message
        });
    }

    private int GetErrorCode(Exception exception)
    {
        if (exception is AppFriendlyException appException)
            return appException.ErrorCode;
        return 500;
    }
}

6. 前端项目结构详解

6.1 目录结构

Web/src/
├── api/                 # API接口定义
│   ├── system/          # 系统管理接口
│   │   ├── user.ts      # 用户接口
│   │   ├── role.ts      # 角色接口
│   │   ├── menu.ts      # 菜单接口
│   │   └── org.ts       # 机构接口
│   ├── platform/        # 平台管理接口
│   └── model/           # 接口类型定义
├── assets/              # 静态资源
├── components/          # 公共组件
│   ├── form/            # 表单组件
│   ├── table/           # 表格组件
│   └── dialog/          # 对话框组件
├── directives/          # 自定义指令
├── hooks/               # 组合式函数
│   ├── useTable.ts      # 表格Hook
│   ├── useForm.ts       # 表单Hook
│   └── useAuth.ts       # 权限Hook
├── layout/              # 布局组件
│   ├── navBars/         # 导航栏
│   ├── navMenu/         # 侧边菜单
│   └── main/            # 主内容区
├── router/              # 路由配置
│   ├── index.ts         # 路由入口
│   ├── route.ts         # 静态路由
│   └── backEnd.ts       # 动态路由
├── stores/              # Pinia状态管理
│   ├── modules/         # 状态模块
│   │   ├── user.ts      # 用户状态
│   │   ├── menu.ts      # 菜单状态
│   │   └── app.ts       # 应用状态
│   └── index.ts         # 状态入口
├── utils/               # 工具函数
│   ├── request.ts       # axios封装
│   ├── storage.ts       # 存储工具
│   └── validate.ts      # 验证工具
└── views/               # 页面视图
    ├── system/          # 系统管理页面
    │   ├── user/        # 用户管理
    │   ├── role/        # 角色管理
    │   ├── menu/        # 菜单管理
    │   └── org/         # 机构管理
    ├── platform/        # 平台管理页面
    └── home/            # 首页

6.2 API接口封装

// api/system/user.ts
import request from '/@/utils/request';

/**
 * 用户管理接口
 */
export function userApi() {
    return {
        /**
         * 获取用户分页列表
         */
        getPage(params: PageUserInput) {
            return request({
                url: '/api/sysUser/page',
                method: 'get',
                params
            });
        },
        
        /**
         * 获取用户详情
         */
        getDetail(id: number) {
            return request({
                url: '/api/sysUser/detail',
                method: 'get',
                params: { id }
            });
        },
        
        /**
         * 新增用户
         */
        add(data: AddUserInput) {
            return request({
                url: '/api/sysUser/add',
                method: 'post',
                data
            });
        },
        
        /**
         * 更新用户
         */
        update(data: UpdateUserInput) {
            return request({
                url: '/api/sysUser/update',
                method: 'post',
                data
            });
        },
        
        /**
         * 删除用户
         */
        delete(data: DeleteUserInput) {
            return request({
                url: '/api/sysUser/delete',
                method: 'post',
                data
            });
        }
    };
}

6.3 Pinia状态管理

// stores/modules/user.ts
import { defineStore } from 'pinia';
import { Session, Local } from '/@/utils/storage';

export const useUserStore = defineStore('user', {
    state: () => ({
        userInfo: {} as UserInfo,
        token: '',
        roles: [] as string[],
        permissions: [] as string[]
    }),
    
    getters: {
        // 是否已登录
        isLogin: (state) => !!state.token,
        
        // 获取用户名
        userName: (state) => state.userInfo.realName || state.userInfo.account
    },
    
    actions: {
        // 设置Token
        setToken(token: string) {
            this.token = token;
            Session.set('token', token);
        },
        
        // 设置用户信息
        setUserInfo(userInfo: UserInfo) {
            this.userInfo = userInfo;
        },
        
        // 登录
        async login(loginForm: LoginInput) {
            const res = await loginApi().login(loginForm);
            this.setToken(res.data.accessToken);
            return res;
        },
        
        // 获取用户信息
        async getUserInfo() {
            const res = await loginApi().getUserInfo();
            this.setUserInfo(res.data);
            this.roles = res.data.roles;
            this.permissions = res.data.permissions;
            return res;
        },
        
        // 退出登录
        async logout() {
            await loginApi().logout();
            this.token = '';
            this.userInfo = {};
            Session.clear();
            Local.clear();
        }
    }
});

6.4 路由配置

// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { staticRoutes, errorRoutes } from './route';
import { useUserStore } from '/@/stores/modules/user';

const router = createRouter({
    history: createWebHashHistory(),
    routes: [...staticRoutes, ...errorRoutes]
});

// 路由前置守卫
router.beforeEach(async (to, from, next) => {
    const userStore = useUserStore();
    
    // 白名单放行
    if (to.meta.isWhite) {
        next();
        return;
    }
    
    // 未登录跳转登录页
    if (!userStore.isLogin) {
        next({ path: '/login', query: { redirect: to.fullPath } });
        return;
    }
    
    // 已登录获取用户信息
    if (!userStore.userInfo.id) {
        await userStore.getUserInfo();
        // 动态添加路由
        await addDynamicRoutes();
        next({ ...to, replace: true });
        return;
    }
    
    next();
});

export default router;

7. 数据流转机制

7.1 请求流程

┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│  Vue组件 │ -> │ API接口 │ -> │ Axios   │ -> │ 后端API │
└─────────┘    └─────────┘    └─────────┘    └─────────┘
                                                   │
                                                   ▼
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│  Vue组件 │ <- │ 数据处理 │ <- │ Axios   │ <- │ 响应结果 │
└─────────┘    └─────────┘    └─────────┘    └─────────┘

7.2 后端处理流程

┌─────────────────────────────────────────────────────────┐
│                      请求进入                            │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    中间件处理                            │
│  异常处理 -> 日志记录 -> 限流检查 -> 跨域处理            │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    认证授权                              │
│  Token验证 -> 用户解析 -> 权限检查                       │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    路由匹配                              │
│  找到对应的Controller/Action或动态API                    │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    参数绑定                              │
│  模型绑定 -> 数据验证 -> 参数转换                        │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    业务处理                              │
│  Service调用 -> 数据访问 -> 业务逻辑                     │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    结果返回                              │
│  统一包装 -> JSON序列化 -> 响应输出                      │
└─────────────────────────────────────────────────────────┘

7.3 数据库操作流程

// 典型的CRUD操作流程
public async Task<SysUser> GetUserById(long id)
{
    // 1. 先查缓存
    var cacheKey = CacheConst.KeyUserInfo + id;
    var user = _cache.Get<SysUser>(cacheKey);
    
    if (user != null)
        return user;
    
    // 2. 缓存不存在,查数据库
    user = await _sysUserRep.AsQueryable()
        .Includes(u => u.Org)       // 包含机构信息
        .Includes(u => u.Pos)       // 包含职位信息
        .FirstAsync(u => u.Id == id);
    
    // 3. 写入缓存
    if (user != null)
        _cache.Set(cacheKey, user, TimeSpan.FromMinutes(30));
    
    return user;
}

8. 依赖注入与服务注册

8.1 自动注入机制

Furion框架提供了自动依赖注入机制,通过实现特定接口自动注册:

// 瞬时注入(每次请求创建新实例)
public class SysUserService : IDynamicApiController, ITransient
{
    // ...
}

// 作用域注入(每个请求范围内共享实例)
public class SomeService : IScoped
{
    // ...
}

// 单例注入(应用程序生命周期内共享实例)
public class CacheService : ISingleton
{
    // ...
}

8.2 手动注册

// 在Startup.cs中手动注册
public void ConfigureServices(IServiceCollection services)
{
    // 注册单例
    services.AddSingleton<ICacheService, CacheService>();
    
    // 注册作用域
    services.AddScoped<IUserContext, UserContext>();
    
    // 注册瞬时
    services.AddTransient<IEmailService, EmailService>();
    
    // 注册工厂
    services.AddTransient<IDbContext>(sp => 
    {
        var config = sp.GetRequiredService<IConfiguration>();
        return new DbContext(config.GetConnectionString("Default"));
    });
}

8.3 服务定位器

// 使用App.GetService获取服务实例
var userService = App.GetService<SysUserService>();
var cache = App.GetService<ICache>();

// 获取必需服务(不存在会抛异常)
var requiredService = App.GetRequiredService<SysUserService>();

// 在非注入环境使用
public static class ServiceHelper
{
    public static T GetService<T>() where T : class
    {
        return App.GetService<T>();
    }
}

总结

本章详细介绍了Admin.NET的项目架构和核心模块:

  1. 整体架构:采用分层架构,包括前端层、Web入口层、Web核心层、应用层和核心层
  2. 后端项目结构:解决方案包含Core、Application、Web.Core、Web.Entry等项目
  3. 核心层:包含实体定义、服务实现、缓存、工具类等核心功能
  4. 应用层:业务实体、业务服务、事件处理等应用级功能
  5. Web层:认证授权、Swagger、中间件配置等Web相关功能
  6. 前端项目:API接口、状态管理、路由配置、页面视图等
  7. 数据流转:从请求到响应的完整处理流程
  8. 依赖注入:Furion自动注入和手动注册机制

理解项目架构是进行二次开发的基础。在下一章中,我们将深入学习Admin.NET的权限系统和多租户实现。