wip: Upms

main
NoahLan 8 months ago
parent 9cac129b15
commit becd8df532

@ -1,5 +1,6 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Text;
using System.Web;
using Microsoft.AspNetCore.Http;
namespace NPin.Framework.Core.Extensions;
@ -7,93 +8,125 @@ namespace NPin.Framework.Core.Extensions;
public static class HttpContextExtensions
{
/// <summary>
/// 设置文件下载名称
/// </summary>
/// <param name="httpContext"></param>
/// <param name="fileName"></param>
public static void FileInlineHandle(this HttpContext httpContext, string fileName)
{
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename);
/// 设置文件下载名称
/// </summary>
/// <param name="httpContext"></param>
/// <param name="fileName"></param>
public static void FileInlineHandle(this HttpContext httpContext, string fileName)
{
string encodeFilename = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename);
}
}
/// <summary>
/// 设置文件附件名称
/// </summary>
/// <param name="httpContext"></param>
/// <param name="fileName"></param>
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
{
string encodeFilename = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename);
}
/// <summary>
/// 设置文件附件名称
/// </summary>
/// <param name="httpContext"></param>
/// <param name="fileName"></param>
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
/// <summary>
/// 获取语言种类
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public static string GetLanguage(this HttpContext httpContext)
{
string res = "zh-CN";
var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
if (str is not null)
{
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename);
res = str.Split(",")[0];
}
/// <summary>
/// 获取语言种类
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public static string GetLanguage(this HttpContext httpContext)
{
string res = "zh-CN";
var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
if (str is not null)
{
res = str.Split(",")[0];
}
return res;
return res;
}
}
/// <summary>
/// 判断是否为异步请求
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsAjaxRequest(this HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
/// <summary>
/// 获取客户端Ip
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetClientIp(this HttpContext? context)
{
if (context == null) return "";
/// <summary>
/// 判断是否为异步请求
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsAjaxRequest(this HttpRequest request)
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(result))
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
result = context.Connection.RemoteIpAddress?.ToString();
}
/// <summary>
/// 获取客户端IP
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetClientIp(this HttpContext context)
{
if (context == null) return "";
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(result))
{
result = context.Connection.RemoteIpAddress?.ToString();
}
if (string.IsNullOrEmpty(result) || result.Contains("::1"))
result = "127.0.0.1";
result = result.Replace("::ffff:", "127.0.0.1");
// 解析 Ip String 到 Ipv4 或 MapToIPv4
// 若无法解析 IP String强制转为本地 Ipv4
result = IPAddress.TryParse(result, out var ipAddr)
? ipAddr.MapToIPv4().ToString()
: IPAddress.Loopback.ToString();
//Ip规则效验
var regResult = Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
// // 本地回环 转换为 ipv4 127.0.0.1
// if (string.IsNullOrEmpty(result) || result.Contains("::1"))
// {
// result = "127.0.0.1";
// }
//
// result = result.Replace("::ffff:", "127.0.0.1");
//
// // Ip规则效验
// var regResult = Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
//
// result = regResult ? result : "127.0.0.1";
return result;
}
result = regResult ? result : "127.0.0.1";
return result;
}
/// <summary>
/// 获取客户端Ip
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static IPAddress? GetClientIpAddress(this HttpContext? context)
{
if (context == null) return null;
/// <summary>
/// 获取浏览器标识
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetUserAgent(this HttpContext context)
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(result))
{
return context.Request.Headers["User-Agent"];
result = context.Connection.RemoteIpAddress?.ToString();
}
public static string[]? GetUserPermissions(this HttpContext context, string permissionsName)
{
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
}
// 解析 Ip String 到 IpAddress
// 若无法解析 IP String强制转为本地 Ipv4
return IPAddress.TryParse(result, out var ipAddr)
? ipAddr
: IPAddress.Loopback;
}
/// <summary>
/// 获取浏览器标识
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetUserAgent(this HttpContext context)
{
return context.Request.Headers["User-Agent"];
}
public static string[] GetUserPermissions(this HttpContext context, string permissionsName)
{
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
}
}

@ -0,0 +1,16 @@
namespace NPin.Framework.Upms.Domain.Shared.Consts;
public class TokenTypeConst
{
public const string Id = nameof(Id);
public const string Username = nameof(Username);
public const string TenantId = nameof(TenantId);
public const string OrgId = nameof(OrgId);
public const string Email = nameof(Email);
public const string PhoneNumber = nameof(PhoneNumber);
public const string Roles = nameof(Roles);
public const string Organizations = nameof(Organizations);
public const string Permission = nameof(Permission);
public const string RoleInfo = nameof(RoleInfo);
public const string Refresh = nameof(Refresh);
}

@ -0,0 +1,33 @@
namespace NPin.Framework.Upms.Domain.Shared.Dtos;
public class OrganizationDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public string? Remark { get; set; }
/// <summary>
/// 负责人ID
/// </summary>
public Guid LeaderId { get; set; }
/// <summary>
/// 负责人名称,仅用户展示,不存储
/// </summary>
public string Leader { get; set; }
/// <summary>
/// 父节点 ID
/// </summary>
public Guid ParentId { get; set; }
public bool IsDeleted { get; }
public DateTime CreationTime { get; } = DateTime.Now;
public Guid? CreatorId { get; }
public DateTime? LastModificationTime { get; }
public Guid? LastModifierId { get; }
public int OrderNum { get; set; }
public bool IsEnabled { get; set; }
}

@ -0,0 +1,18 @@
namespace NPin.Framework.Upms.Domain.Shared.Dtos;
public class PostDto
{
public Guid Id { get; set; }
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? Remark { get; set; }
public bool IsDeleted { get; }
public DateTime CreationTime { get; } = DateTime.Now;
public Guid? CreatorId { get; }
public DateTime? LastModificationTime { get; }
public Guid? LastModifierId { get; }
public int OrderNum { get; set; }
public bool IsEnabled { get; set; }
}

@ -0,0 +1,29 @@
using NPin.Framework.Upms.Domain.Shared.Enums;
namespace NPin.Framework.Upms.Domain.Shared.Dtos;
public class RoleFullDto
{
public RoleDto Role { get; set; } = new();
// Relations
public HashSet<OrganizationDto> OrganizationList { get; set; } = [];
}
public class RoleDto
{
public Guid Id { get; set; }
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public DataScopeEnum DataScope { get; set; } = DataScopeEnum.All;
public string? Remark { get; set; }
public bool IsDeleted { get; }
public DateTime CreationTime { get; } = DateTime.Now;
public Guid? CreatorId { get; }
public DateTime? LastModificationTime { get; }
public Guid? LastModifierId { get; }
public int OrderNum { get; set; }
public bool IsEnabled { get; set; }
}

@ -0,0 +1,51 @@
using NPin.Framework.Upms.Domain.Shared.Enums;
namespace NPin.Framework.Upms.Domain.Shared.Dtos;
/// <summary>
/// 完整用户信息Dto
/// 包括所有关联关系
/// </summary>
public class UserFullDto
{
public UserDto User { get; set; } = new();
// Relations
public HashSet<RoleDto> Roles { get; set; } = [];
public HashSet<PostDto> Posts { get; set; } = [];
public HashSet<OrganizationDto> Organizations { get; set; } = [];
public HashSet<string> PostCodes { get; set; } = [];
public HashSet<string> RoleCodes { get; set; } = [];
public HashSet<string> PermissionCodes { get; set; } = [];
}
public class UserDto
{
/// <summary>
/// 主键ID
/// </summary>
public Guid Id { get; set; }
public bool IsDeleted { get; }
public DateTime CreationTime { get; } = DateTime.Now;
public Guid? CreatorId { get; }
public DateTime? LastModificationTime { get; }
public Guid? LastModifierId { get; }
public bool IsEnabled { get; set; }
public int OrderNum { get; set; }
public string Username { get; set; } = string.Empty;
public string PhoneNumber { get; set; }
public string? Email { get; set; }
public string? Nickname { get; set; }
public string Password { get; set; } = string.Empty;
public string Salt { get; set; } = string.Empty;
public string? Introduction { get; set; }
public GenderEnum Gender { get; set; } = GenderEnum.Secrecy;
public string? IpAddr { get; set; }
public string? Avatar { get; set; }
// For user (metadata)
public Dictionary<string, string> Metadata { get; set; }
}

@ -18,12 +18,12 @@ public enum DataScopeEnum
/// <summary>
/// 本部门
/// </summary>
Dept = 2,
Org = 2,
/// <summary>
/// 部门以及子部门
/// </summary>
DeptFollow = 3,
OrgFollow = 3,
/// <summary>
/// 只有自己

@ -0,0 +1,45 @@
namespace NPin.Framework.Upms.Domain.Shared.Etos;
/// <summary>
/// 用户登录事件参数
/// </summary>
public class LoginEventArgs
{
public Guid UserId { get; set; }
public string Username { get; set; }
public string Nickname { get; set; }
public DateTime CreationTime { get; set; }
/// <summary>
/// 登录地点
/// </summary>
public string? LoginLocation { get; set; }
/// <summary>
/// 登录Ipv4地址
/// </summary>
public string? LoginIPv4 { get; set; }
/// <summary>
/// 登录Ipv6地址仅当支持Ipv6时有值
/// </summary>
public string? LoginIPv6 { get; set; }
/// <summary>
/// 登录浏览器
/// </summary>
public string? Browser { get; set; }
/// <summary>
/// 操作系统
/// </summary>
public string? Os { get; set; }
/// <summary>
/// 登录信息
/// </summary>
public string? LoginMsg { get; set; }
}

@ -0,0 +1,9 @@
namespace NPin.Framework.Upms.Domain.Shared.Etos;
/// <summary>
/// 用户创建事件参数
/// </summary>
public class UserCreatedEventArgs
{
public Guid UserId { get; set; }
}

@ -8,8 +8,6 @@
<PackageReference Include="Volo.Abp.Users.Domain.Shared" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Dtos\" />
<Folder Include="Etos\" />
<Folder Include="Model\" />
</ItemGroup>

@ -0,0 +1,16 @@
using Volo.Abp.Data;
namespace NPin.Framework.Upms.Domain.Authorization;
public static class DataPermissionExtensions
{
/// <summary>
/// 关闭数据权限
/// </summary>
/// <param name="dataFilter"></param>
/// <returns></returns>
public static IDisposable DisablePermissionHandler(this IDataFilter dataFilter)
{
return dataFilter.Disable<IDataPermission>();
}
}

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Http;
using NPin.Framework.Core.Extensions;
using NPin.Framework.Upms.Domain.Shared.Consts;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace NPin.Framework.Upms.Domain.Authorization;
/// <summary>
/// 默认权限处理器
/// </summary>
public class DefaultPermissionHandler : IPermissionHandler, ITransientDependency
{
private readonly ICurrentUser _currentUser;
private readonly IHttpContextAccessor _httpContextAccessor;
public DefaultPermissionHandler(ICurrentUser currentUser, IHttpContextAccessor httpContextAccessor)
{
_currentUser = currentUser;
_httpContextAccessor = httpContextAccessor;
}
public bool IsPass(string permission)
{
var permissions = _httpContextAccessor.HttpContext.GetUserPermissions(TokenTypeConst.Permission);
// 超级管理员
if (permissions.Contains("*:*:*"))
{
return true;
}
return permissions.Contains(permission);
}
}

@ -0,0 +1,6 @@
namespace NPin.Framework.Upms.Domain.Authorization;
public interface IPermissionHandler
{
bool IsPass(string permission);
}

@ -0,0 +1,12 @@
namespace NPin.Framework.Upms.Domain.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class PermissionAttribute: Attribute
{
internal string Code { get; set; }
public PermissionAttribute(string code)
{
Code = code;
}
}

@ -0,0 +1,45 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
namespace NPin.Framework.Upms.Domain.Authorization;
/// <summary>
/// 权限处理器
/// </summary>
internal class PermissionGlobalAttribute : ActionFilterAttribute, ITransientDependency
{
private readonly IPermissionHandler _permissionHandler;
public PermissionGlobalAttribute(IPermissionHandler permissionHandler)
{
_permissionHandler = permissionHandler;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionDescriptor is not ControllerActionDescriptor controllerActionDescriptor) return;
var perAttribute = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.FirstOrDefault(a => a.GetType() == typeof(PermissionAttribute)) as PermissionAttribute;
// 无权限标识,通过
if (perAttribute is null) return;
var passed = _permissionHandler.IsPass(perAttribute.Code);
if (passed) return;
var model = new RemoteServiceErrorInfo
{
Code = "403",
Message = "您无权访问,请联系管理员",
Details = $"您无权访问该接口-{context.HttpContext.Request.Path.Value}"
};
var content = new ObjectResult(new { error = model })
{
StatusCode = (int)HttpStatusCode.Forbidden
};
context.Result = content;
}
}

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using NPin.Framework.Upms.Domain.Shared.Consts;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace NPin.Framework.Upms.Domain.Authorization;
/// <summary>
/// RefreshToken 处理中间件
/// </summary>
public class RefreshTokenMiddleware : IMiddleware, ITransientDependency
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var refreshToken = context.Request.Headers["refresh_token"].ToString();
if (!refreshToken.IsNullOrEmpty())
{
var authResult = await context.AuthenticateAsync(TokenTypeConst.Refresh);
// Token刷新成功
if (authResult.Succeeded)
{
var userId = Guid.Parse(authResult.Principal.FindFirst(AbpClaimTypes.UserId).Value);
// TODO
// var accessToken =
// var refreshToken =
context.Response.Headers["access_token"] = "";
context.Response.Headers["refresh_token"] = "";
// 请求头替换
context.Request.Headers["Authorization"] = $"Bearer {""}";
}
}
await next(context);
}
}
/// <summary>
/// 扩展
/// </summary>
public static class RefreshTokenExtensions
{
public static IApplicationBuilder UseRefreshToken(this IApplicationBuilder builder)
{
builder.UseMiddleware<RefreshTokenMiddleware>();
return builder;
}
}

@ -0,0 +1,46 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
namespace NPin.Framework.Upms.Domain.Authorization;
[SugarTable("Social", "第三方授权表")]
public class SocialAggregateRoot: AggregateRoot<Guid>, ISoftDelete, IHasCreationTime
{
[SugarColumn(IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public Guid UserId { get; set; }
public string OpenId { get; set; }
public string? UnionId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public bool IsDeleted { get; }
public DateTime CreationTime { get; }
[SugarColumn(IsIgnore = true)]
public override ExtraPropertyDictionary ExtraProperties { get; protected set; }
public SocialAggregateRoot()
{
}
public SocialAggregateRoot(string type, Guid userId, string openId, string? unionId)
{
UserId = userId;
OpenId = openId;
UnionId = unionId;
Type = type;
}
public SocialAggregateRoot(string type, Guid userId, string openId, string? unionId, string name): this(type, userId, openId, unionId)
{
Name = name;
}
}

@ -0,0 +1,81 @@
using System.Net;
using System.Net.Sockets;
using IPTools.Core;
using Microsoft.AspNetCore.Http;
using NPin.Framework.Core.Extensions;
using SqlSugar;
using UAParser;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
namespace NPin.Framework.Upms.Domain.Entities;
[SugarTable("LoginLog", "登录日志表")]
public class LoginLogEntity : Entity<Guid>, ICreationAuditedObject
{
[SugarColumn(IsPrimaryKey = true)] public override Guid Id { get; protected set; }
public DateTime CreationTime { get; }
public Guid? CreatorId { get; }
[SugarColumn(ColumnDescription = "登录用户")]
public string? LoginUser { get; set; }
[SugarColumn(ColumnDescription = "登录用户ID")]
public Guid LoginUserId { get; set; }
[SugarColumn(ColumnDescription = "登录地点")]
public string? LoginLocation { get; set; }
[SugarColumn(ColumnDescription = "Ipv4")]
public string? LoginIPv4 { get; set; }
[SugarColumn(ColumnDescription = "Ipv6")]
public string? LoginIPv6 { get; set; }
[SugarColumn(ColumnDescription = "浏览器")]
public string? Browser { get; set; }
[SugarColumn(ColumnDescription = "操作系统")]
public string? Os { get; set; }
[SugarColumn(ColumnDescription = "登录信息")]
public string? LoginMsg { get; set; }
public static LoginLogEntity GetInfoByHttpContext(HttpContext httpContext)
{
// var ipInfo = httpContext.GetRemoteIpInfo();
string ipv4AddrStr = null;
string ipv6AddrStr = null;
var ipAddr = httpContext.GetClientIpAddress();
if (ipAddr != null)
{
switch (ipAddr.AddressFamily)
{
case AddressFamily.InterNetwork:
ipv4AddrStr = ipAddr.ToString();
break;
case AddressFamily.InterNetworkV6:
ipv6AddrStr = ipAddr.ToString();
break;
}
}
var location = IPAddress.IsLoopback(ipAddr)
? new IpInfo { Province = "本地", City = "本机" }
: IpTool.Search(ipAddr.ToString());
var clientInfo = GetClientInfo(httpContext);
return new LoginLogEntity
{
Browser = clientInfo.Device.Family,
Os = clientInfo.OS.ToString(),
LoginIPv4 = ipv4AddrStr,
LoginIPv6 = ipv6AddrStr,
LoginLocation = $"{location.Country}-{location.Province}-{location.City}"
};
ClientInfo GetClientInfo(HttpContext ctx) => Parser.GetDefault().Parse(ctx.GetUserAgent());
}
}

@ -20,7 +20,7 @@ public class RoleEntity : Entity<Guid>, ISoftDelete, IAuditedObject, IOrderNum,
[SugarColumn(ColumnDescription = "角色数据权限范围")]
public DataScopeEnum DataScope { get; set; } = DataScopeEnum.All;
[SugarColumn(ColumnDescription = "描述")]
public string? Remark { get; set; }
@ -31,4 +31,12 @@ public class RoleEntity : Entity<Guid>, ISoftDelete, IAuditedObject, IOrderNum,
public Guid? LastModifierId { get; }
public int OrderNum { get; set; }
public bool IsEnabled { get; set; }
#region 导航
[Navigate(typeof(RoleOrganizationEntity), nameof(RoleOrganizationEntity.RoleId),
nameof(RoleOrganizationEntity.OrgId))]
public List<OrganizationEntity> OrganizationList { get; set; }
#endregion
}

@ -0,0 +1,16 @@
using SqlSugar;
using Volo.Abp.Domain.Entities;
namespace NPin.Framework.Upms.Domain.Entities;
[SugarTable("RelRoleOrganization", "角色-机构 关系表")]
public class RoleOrganizationEntity : Entity<Guid>
{
[SugarColumn(IsPrimaryKey = true)] public override Guid Id { get; protected set; }
[SugarColumn(ColumnDescription = "角色ID")]
public Guid RoleId { get; set; }
[SugarColumn(ColumnDescription = "机构ID")]
public Guid OrgId { get; set; }
}

@ -88,6 +88,9 @@ public class UserEntity : Entity<Guid>, ISoftDelete, IAuditedObject, IEnabled, I
[Navigate(typeof(UserPostEntity), nameof(UserPostEntity.UserId), nameof(UserPostEntity.PostId))]
public List<PostEntity> Posts { get; set; }
/// <summary>
/// 所在的组织机构列表,多对多
/// </summary>
[Navigate(typeof(UserOrganizationEntity), nameof(UserOrganizationEntity.UserId),
nameof(UserOrganizationEntity.OrganizationId))]
public List<OrganizationEntity> Organizations { get; set; }

@ -0,0 +1,32 @@
using Mapster;
using Microsoft.Extensions.Logging;
using NPin.Framework.Upms.Domain.Entities;
using NPin.Framework.Upms.Domain.Shared.Etos;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus;
namespace NPin.Framework.Upms.Domain.EventHandlers;
public class LoginEventHandler : ILocalEventHandler<LoginEventArgs>, ITransientDependency
{
private readonly ILogger<LoginEventHandler> _logger;
private readonly IRepository<LoginLogEntity> _repository;
public LoginEventHandler(ILogger<LoginEventHandler> logger, IRepository<LoginLogEntity> repository)
{
_logger = logger;
_repository = repository;
}
public async Task HandleEventAsync(LoginEventArgs eventData)
{
_logger.LogInformation($"用户[{eventData.UserId}:{eventData.Username}]登录");
var loginLogEntity = eventData.Adapt<LoginLogEntity>();
loginLogEntity.LoginMsg = $"{eventData.Username}登录系统";
loginLogEntity.LoginUser = eventData.Username;
loginLogEntity.LoginUserId = eventData.UserId;
// 插入
await _repository.InsertAsync(loginLogEntity);
}
}

@ -0,0 +1,42 @@
using NPin.Framework.Upms.Domain.Shared.Consts;
using Volo.Abp.Users;
namespace NPin.Framework.Upms.Domain.Extensions;
public static class CurrentUserExtensions
{
/// <summary>
/// 获取用户权限代码列表
/// </summary>
/// <param name="currentUser"></param>
/// <returns></returns>
public static List<string> GetPermissions(this ICurrentUser currentUser)
{
return currentUser.FindClaims(TokenTypeConst.Permission)
.Select(x => x.Value)
.ToList();
}
/// <summary>
/// 获取用户组织机构ID列表
/// </summary>
/// <param name="currentUser"></param>
/// <returns></returns>
public static List<Guid> GetOrganizationIds(this ICurrentUser currentUser)
{
return currentUser.FindClaims(TokenTypeConst.Organizations)
.Select(x => Guid.Parse(x.Value))
.ToList();
}
/// <summary>
/// 用户是否是通过Refresh进来的
/// </summary>
/// <param name="currentUser"></param>
/// <returns></returns>
public static bool IsRefreshToken(this ICurrentUser currentUser)
{
var valOrNull = currentUser.FindClaim(TokenTypeConst.Refresh)?.Value;
return valOrNull is not null && bool.Parse(valOrNull);
}
}

@ -0,0 +1,12 @@
using Volo.Abp.Domain.Services;
namespace NPin.Framework.Upms.Domain.Managers;
public interface IAccountManager
{
}
public class AccountManager : DomainService, IAccountManager
{
// private readonly
}

@ -1,6 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using NPin.Framework.Caching.FreeRedis;
using NPin.Framework.Upms.Domain.Authorization;
using NPin.Framework.Upms.Domain.OperLog;
using NPin.Framework.Upms.Domain.Shared;
using NPin.Framework.Upms.Domain.Shared.Options;
using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.Caching;
using Volo.Abp.Domain;
@ -23,10 +26,11 @@ public class NPinFrameworkUpmsDomainModule : AbpModule
var configuration = services.GetConfiguration();
services.AddControllers(opts =>
{
// opts.Filters.Add()
opts.Filters.Add<PermissionGlobalAttribute>();
opts.Filters.Add<OperLogGlobalAttribute>();
});
// 配置短信
// Configure<AliyunOptions>();
Configure<AliyunOptions>(configuration.GetSection(nameof(AliyunOptions)));
}
}

@ -0,0 +1,79 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using NPin.Framework.Core.Extensions;
using NPin.Framework.Upms.Domain.Shared.OperLog;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
namespace NPin.Framework.Upms.Domain.OperLog;
public class OperLogGlobalAttribute : ActionFilterAttribute, ITransientDependency
{
private readonly ILogger<OperLogGlobalAttribute> _logger;
private IRepository<OperationLogEntity> _repository;
private ICurrentUser _currentUser;
public OperLogGlobalAttribute(ILogger<OperLogGlobalAttribute> logger, IRepository<OperationLogEntity> repository,
ICurrentUser currentUser)
{
_logger = logger;
_repository = repository;
_currentUser = currentUser;
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// 获取执行结果
var resultContext = await next.Invoke();
// 判断是否在 控制器方法 上
if (resultContext.ActionDescriptor is not ControllerActionDescriptor contextActionDescriptor) return;
// 查找特性
var operLogAttribute = contextActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.FirstOrDefault(a => a.GetType() == typeof(OperLogAttribute)) as OperLogAttribute;
// 无特性表达式 直接返回 不处理
if (operLogAttribute is null) return;
// 获取Ip
var logEntity = OperationLogEntity.GetInfoByHttpContext(resultContext.HttpContext);
logEntity.OperType = operLogAttribute.OperType;
logEntity.Title = operLogAttribute.Title;
logEntity.RequestMethod = resultContext.HttpContext.Request.Method;
logEntity.Method = resultContext.HttpContext.Request.Path.Value;
logEntity.OperUser = _currentUser.UserName;
// 请求结果保存
if (operLogAttribute.IsSaveResponseData)
{
if (resultContext.Result is ContentResult { ContentType: "application/json" } result)
{
logEntity.RequestResult = result.Content?.Replace("\r\n", "").Trim();
}
if (resultContext.Result is JsonResult result2)
{
logEntity.RequestResult = result2.Value?.ToString();
}
if (resultContext.Result is ObjectResult result3)
{
logEntity.RequestResult = JsonConvert.SerializeObject(result3);
}
}
// 请求参数保存
if (operLogAttribute.IsSaveRequestData)
{
// 不建议保存 比较吃性能
// logEntity.RequestParam = context.HttpContext.GetRequestValue();
}
await _repository.InsertAsync(logEntity);
}
}

@ -0,0 +1,86 @@
using System.Net;
using System.Net.Sockets;
using IPTools.Core;
using Microsoft.AspNetCore.Http;
using NPin.Framework.Core.Extensions;
using NPin.Framework.Upms.Domain.Entities;
using NPin.Framework.Upms.Domain.Shared.OperLog;
using SqlSugar;
using UAParser;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
namespace NPin.Framework.Upms.Domain.OperLog;
[SugarTable("OperationLog", "操作日志记录表")]
public class OperationLogEntity: Entity<Guid>, ICreationAuditedObject
{
[SugarColumn(IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
[SugarColumn(ColumnDescription = "日志标题")]
public string? Title { get; set; }
[SugarColumn(ColumnDescription = "操作类型")]
public OperTypeEnum OperType { get; set; }
[SugarColumn(ColumnDescription = "请求方式")]
public string? RequestMethod { get; set; }
[SugarColumn(ColumnDescription = "操作者")]
public string? OperUser { get; set; }
[SugarColumn(ColumnDescription = "操作者Ipv4")]
public string? OperIPv4 { get; set; }
[SugarColumn(ColumnDescription = "操作者Ipv6")]
public string? OperIPv6 { get; set; }
[SugarColumn(ColumnDescription = "操作地点")]
public string? OperLocation { get; set; }
[SugarColumn(ColumnDescription = "操作方法")]
public string? Method { get; set; }
[SugarColumn(ColumnDescription = "请求参数")]
public string? RequestParam { get; set; }
[SugarColumn(ColumnDescription = "请求结果", Length = 9999)]
public string? RequestResult { get; set; }
public DateTime CreationTime { get; }
public Guid? CreatorId { get; }
public static OperationLogEntity GetInfoByHttpContext(HttpContext httpContext)
{
// var ipInfo = httpContext.GetRemoteIpInfo();
string ipv4AddrStr = null;
string ipv6AddrStr = null;
IpInfo info = new IpInfo { Province = "本地", City = "本机" };
var ipAddr = httpContext.GetClientIpAddress();
if (ipAddr != null)
{
switch (ipAddr.AddressFamily)
{
case AddressFamily.InterNetwork:
ipv4AddrStr = ipAddr.ToString();
break;
case AddressFamily.InterNetworkV6:
ipv6AddrStr = ipAddr.ToString();
break;
}
if (!IPAddress.IsLoopback(ipAddr))
{
info = IpTool.Search(ipAddr.ToString());
}
}
return new OperationLogEntity
{
OperIPv4 = ipv4AddrStr,
OperIPv6 = ipv6AddrStr,
OperLocation = $"{info.Country}-{info.Province}-{info.City}"
};
}
}

@ -0,0 +1,9 @@
using NPin.Framework.SqlSugarCore.Abstractions;
using NPin.Framework.Upms.Domain.Entities;
namespace NPin.Framework.Upms.Domain.Repositories;
public interface IUserRepository: ISqlSugarRepository<UserEntity>
{
// Task<>
}

@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\NPin.Framework.Ddd.Application.Contracts\NPin.Framework.Ddd.Application.Contracts.csproj" />
<ProjectReference Include="..\..\module\tenant-management\NPin.Framework.TenantManagement.Application.Contracts\NPin.Framework.TenantManagement.Application.Contracts.csproj" />
<ProjectReference Include="..\..\module\upms\NPin.Framework.Upms.Application.Contracts\NPin.Framework.Upms.Application.Contracts.csproj" />
<ProjectReference Include="..\NPin.Domain.Shared\NPin.Domain.Shared.csproj" />
</ItemGroup>
</Project>

@ -1,12 +1,14 @@
using NPin.Domain.Shared;
using NPin.Framework.Ddd.Application.Contracts;
using NPin.Framework.TenantManagement.Application.Contracts;
using NPin.Framework.Upms.Application.Contracts;
namespace NPin.Application.Contracts;
[DependsOn(
typeof(NPinDomainSharedModule),
// TODO RBAC
//
typeof(NPinFrameworkUpmsApplicationContractsModule),
// TODO Bbs
typeof(NPinFrameworkTenantManagementApplicationContractsModule),
typeof(NPinFrameworkDddApplicationContractsModule)

@ -3,6 +3,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Framework\NPin.Framework.Ddd.Application\NPin.Framework.Ddd.Application.csproj" />
<ProjectReference Include="..\..\module\tenant-management\NPin.Framework.TenantManagement.Application\NPin.Framework.TenantManagement.Application.csproj" />
<ProjectReference Include="..\..\module\upms\NPin.Framework.Upms.Application\NPin.Framework.Upms.Application.csproj" />
<ProjectReference Include="..\NPin.Application.Contracts\NPin.Application.Contracts.csproj" />
<ProjectReference Include="..\NPin.Domain\NPin.Domain.csproj" />
</ItemGroup>

@ -2,13 +2,16 @@
using NPin.Domain;
using NPin.Framework.Ddd.Application;
using NPin.Framework.TenantManagement.Application;
using NPin.Framework.Upms.Application;
namespace NPin.Application;
[DependsOn(
typeof(NPinApplicationContractsModule),
typeof(NPinDomainModule),
// TODO rbac bbs
//
typeof(NPinFrameworkUpmsApplicationModule),
// TODO bbs
typeof(NPinFrameworkTenantManagementApplicationModule),
// TODO code-gen
typeof(NPinFrameworkDddApplicationModule)

@ -14,5 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\..\module\audit-logging\NPin.Framework.AuditLogging.Domain.Shared\NPin.Framework.AuditLogging.Domain.Shared.csproj" />
<ProjectReference Include="..\..\module\upms\NPin.Framework.Upms.Domain.Shared\NPin.Framework.Upms.Domain.Shared.csproj" />
</ItemGroup>
</Project>

@ -1,10 +1,11 @@
using NPin.Framework.AuditLogging.Domain.Shared;
using NPin.Framework.Upms.Domain.Shared;
using Volo.Abp.Domain;
namespace NPin.Domain.Shared;
[DependsOn(
// TODO RBAC
typeof(NPinFrameworkUpmsDomainSharedModule),
// TODO BBS
typeof(NPinFrameworkAuditLoggingDomainSharedModule),
typeof(AbpDddDomainSharedModule)

@ -13,6 +13,7 @@
<ProjectReference Include="..\..\framework\NPin.Framework.SqlSugarCore.Abstractions\NPin.Framework.SqlSugarCore.Abstractions.csproj" />
<ProjectReference Include="..\..\module\audit-logging\NPin.Framework.AuditLogging.Domain\NPin.Framework.AuditLogging.Domain.csproj" />
<ProjectReference Include="..\..\module\tenant-management\NPin.Framework.TenantManagement.Domain\NPin.Framework.TenantManagement.Domain.csproj" />
<ProjectReference Include="..\..\module\upms\NPin.Framework.Upms.Domain\NPin.Framework.Upms.Domain.csproj" />
<ProjectReference Include="..\NPin.Domain.Shared\NPin.Domain.Shared.csproj" />
</ItemGroup>

@ -2,6 +2,7 @@
using NPin.Framework.AuditLogging.Domain;
using NPin.Framework.Mapster;
using NPin.Framework.TenantManagement.Domain;
using NPin.Framework.Upms.Domain;
using Volo.Abp.Caching;
using Volo.Abp.Domain;
@ -11,7 +12,7 @@ namespace NPin.Domain;
typeof(NPinDomainSharedModule),
//
typeof(NPinFrameworkTenantManagementDomainModule),
// TODO Rbac
typeof(NPinFrameworkUpmsDomainModule),
// TODO Bbs
typeof(NPinFrameworkAuditLoggingDomainModule),
//

@ -11,6 +11,7 @@
<ProjectReference Include="..\..\framework\NPin.Framework.SqlSugarCore\NPin.Framework.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\audit-logging\NPin.Framework.AuditLogging.SqlSugarCore\NPin.Framework.AuditLogging.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\tenant-management\NPin.Framework.TenantManagement.SqlSugarCore\NPin.Framework.TenantManagement.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\upms\NPin.Framework.Upms.SqlSugarCore\NPin.Framework.Upms.SqlSugarCore.csproj" />
<ProjectReference Include="..\NPin.Domain\NPin.Domain.csproj" />
</ItemGroup>
</Project>

@ -1,6 +1,11 @@
namespace NPin.SqlSugarCore;
using NPin.Framework.Upms.SqlSugarCore;
using Volo.Abp.DependencyInjection;
public class NPinDbContext
namespace NPin.SqlSugarCore;
public class NPinDbContext: NPinUpmsDbContext
{
public NPinDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
{
}
}

@ -3,12 +3,15 @@ using NPin.Framework.AuditLogging.SqlSugarCore;
using NPin.Framework.Mapster;
using NPin.Framework.SqlSugarCore;
using NPin.Framework.TenantManagement.SqlSugarCore;
using NPin.Framework.Upms.SqlSugarCore;
namespace NPin.SqlSugarCore;
[DependsOn(
typeof(NPinDomainModule),
// TODO rbac bbs codegen
typeof(NPinFrameworkUpmsSqlSugarCoreModule),
// TODO bbs
// TODO codegen
typeof(NPinFrameworkAuditLoggingSqlSugarCoreModule),
typeof(NPinFrameworkTenantManagementSqlSugarCoreModule),
//
@ -19,7 +22,7 @@ public class NPinSqlSugarCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// context.Services.AddNPinDbContext<NPinDbContext>();
context.Services.AddNPinDbContext<NPinDbContext>();
// 默认不开放可根据项目需要是否直接对外开放db
// context.Services.AddTransient(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 MiB

Loading…
Cancel
Save