第三章-项目架构与核心模块解析
第三章:项目架构与核心模块解析
目录
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):
- 用户界面展示
- 用户交互处理
- 状态管理
- API调用
Web入口层(Admin.NET.Web.Entry):
- 应用程序入口点
- 配置加载
- 中间件注册
- 控制器定义
Web核心层(Admin.NET.Web.Core):
- Furion框架配置
- 认证授权配置
- Swagger文档配置
- 跨域策略配置
应用层(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的项目架构和核心模块:
- 整体架构:采用分层架构,包括前端层、Web入口层、Web核心层、应用层和核心层
- 后端项目结构:解决方案包含Core、Application、Web.Core、Web.Entry等项目
- 核心层:包含实体定义、服务实现、缓存、工具类等核心功能
- 应用层:业务实体、业务服务、事件处理等应用级功能
- Web层:认证授权、Swagger、中间件配置等Web相关功能
- 前端项目:API接口、状态管理、路由配置、页面视图等
- 数据流转:从请求到响应的完整处理流程
- 依赖注入:Furion自动注入和手动注册机制
理解项目架构是进行二次开发的基础。在下一章中,我们将深入学习Admin.NET的权限系统和多租户实现。

浙公网安备 33010602011771号