Bladeren bron

平迁登录,编写Simple.JWT,未拆分服务

dev/1.0.0
stevelee 2 jaren geleden
bovenliggende
commit
62a18d0b53
22 gewijzigde bestanden met toevoegingen van 1537 en 64 verwijderingen
  1. +5
    -0
      BPA.SaaS.Stock.Api.sln
  2. +4
    -0
      src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginInput.cs
  3. +1
    -2
      src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginOutput.cs
  4. +394
    -0
      src/BPA.SaaS.Stock.Api.DTO/EnumErrorCode.cs
  5. +6
    -7
      src/BPA.SaaS.Stock.Api.Entity/BaseGroupIdEntity.cs
  6. +13
    -33
      src/BPA.SaaS.Stock.Api.Entity/BaseOPEntity.cs
  7. +2
    -4
      src/BPA.SaaS.Stock.Api.Entity/Entity.cs
  8. +6
    -0
      src/BPA.SaaS.Stock.Api.IRepository/Auth/IAuthRepository.cs
  9. +3
    -0
      src/BPA.SaaS.Stock.Api.IRepository/BPA.SaaS.Stock.Api.IRepository.csproj
  10. +28
    -0
      src/BPA.SaaS.Stock.Api.IService/Auth/IAuthService.cs
  11. +39
    -0
      src/BPA.SaaS.Stock.Api.IService/Cache/ISysCacheService.cs
  12. +12
    -0
      src/BPA.SaaS.Stock.Api.Repository/Auth/AuthRepository.cs
  13. +1
    -1
      src/BPA.SaaS.Stock.Api.Repository/Product/ProductCodeRepository.cs
  14. +17
    -17
      src/BPA.SaaS.Stock.Api.Repository/Product/ProductRepository.cs
  15. +227
    -0
      src/BPA.SaaS.Stock.Api.Service/Auth/AuthService.cs
  16. +1
    -0
      src/BPA.SaaS.Stock.Api.Service/BPA.SaaS.Stock.Api.Service.csproj
  17. +229
    -0
      src/BPA.SaaS.Stock.Api.Service/Cache/SysCacheService.cs
  18. +455
    -0
      src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTEncryption.cs
  19. +34
    -0
      src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTSettingsOptions.cs
  20. +15
    -0
      src/BPA.SaaS.Stock.Api.Service/SimpleJWT/MultiClaimsDictionaryComparer.cs
  21. +38
    -0
      src/BPA.SaaS.Stock.Api.WebApi/Controllers/Auth/AuthController.cs
  22. +7
    -0
      src/SQLScript/20220418.sql

+ 5
- 0
BPA.SaaS.Stock.Api.sln Bestand weergeven

@@ -49,6 +49,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".pack", ".pack", "{5CE18647
src\.pack\BPA.SaaS.Stock.Api.SDK.nuspec = src\.pack\BPA.SaaS.Stock.Api.SDK.nuspec
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Script", ".Script", "{FBC5FEFB-0107-45CD-B2A4-8EA9ABDA9082}"
ProjectSection(SolutionItems) = preProject
src\SQLScript\20220418.sql = src\SQLScript\20220418.sql
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU


+ 4
- 0
src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginInput.cs Bestand weergeven

@@ -20,13 +20,16 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
/// <example>123456</example>
[Required(ErrorMessage = "密码不能为空")]
public string Password { get; set; }

public string RoleId { get; set; }
public string Name { get; set; }

/// <summary>
/// 0正常 1停用 2删除
/// </summary>
public int Stutas { get; set; }
}

//public class PersonValidator : AbstractValidator<LoginInput>
//{
// public PersonValidator()
@@ -43,6 +46,7 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
public int PageIndex { get; set; }
public int PageSize { get; set; }
}

public class UserInputDTO
{
public string OldPwd { get; set; }


+ 1
- 2
src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginOutput.cs Bestand weergeven

@@ -72,7 +72,6 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
/// </summary>
public String Tel { get; set; }


/// <summary>
/// 最后登陆IP
/// </summary>
@@ -101,7 +100,7 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
/// <summary>
/// 登录菜单信息---AntDesign版本菜单
/// </summary>
public List<AntDesignTreeNode> Menus { get; set; } = new List<AntDesignTreeNode>();
public List<AntDesignTreeNode> Menus { get; set; } = new();

/// <summary>
/// 数据范围(机构)信息


+ 394
- 0
src/BPA.SaaS.Stock.Api.DTO/EnumErrorCode.cs Bestand weergeven

@@ -0,0 +1,394 @@
using System.ComponentModel;

namespace BPA.SaaS.Stock.Api.DTO
{
/// <summary>
/// 系统错误码
/// </summary>
public enum ErrorCode
{
/// <summary>
/// 用户名或密码不正确
/// </summary>
[Description("用户名或密码不正确")]
D1000,

/// <summary>
/// 非法操作!禁止删除自己
/// </summary>
[Description("非法操作,禁止删除自己")]
D1001,

/// <summary>
/// 记录不存在
/// </summary>
[Description("记录不存在")]
D1002,

/// <summary>
/// 账号已存在
/// </summary>
[Description("账号已存在")]
D1003,

/// <summary>
/// 旧密码不匹配
/// </summary>
[Description("旧密码输入错误")]
D1004,

/// <summary>
/// 测试数据禁止更改admin密码
/// </summary>
[Description("测试数据禁止更改用户【admin】密码")]
D1005,

/// <summary>
/// 数据已存在
/// </summary>
[Description("数据已存在")]
D1006,

/// <summary>
/// 数据不存在或含有关联引用,禁止删除
/// </summary>
[Description("数据不存在或含有关联引用,禁止删除")]
D1007,

/// <summary>
/// 禁止为管理员分配角色
/// </summary>
[Description("禁止为管理员分配角色")]
D1008,

/// <summary>
/// 重复数据或记录含有不存在数据
/// </summary>
[Description("重复数据或记录含有不存在数据")]
D1009,

/// <summary>
/// 禁止为超级管理员角色分配权限
/// </summary>
[Description("禁止为超级管理员角色分配权限")]
D1010,

/// <summary>
/// 非法数据
/// </summary>
[Description("非法数据")]
D1011,

/// <summary>
/// Id不能为空
/// </summary>
[Description("Id不能为空")]
D1012,

/// <summary>
/// 所属机构不在自己的数据范围内
/// </summary>
[Description("没有权限操作该数据")]
D1013,

/// <summary>
/// 禁止删除超级管理员
/// </summary>
[Description("禁止删除超级管理员")]
D1014,

/// <summary>
/// 禁止修改超级管理员状态
/// </summary>
[Description("禁止修改超级管理员状态")]
D1015,

/// <summary>
/// 没有权限
/// </summary>
[Description("没有权限")]
D1016,

/// <summary>
/// 账号已冻结
/// </summary>
[Description("账号已冻结")]
D1017,

/// <summary>
/// 父机构不存在
/// </summary>
[Description("父机构不存在")]
D2000,

/// <summary>
/// 当前机构Id不能与父机构Id相同
/// </summary>
[Description("当前机构Id不能与父机构Id相同")]
D2001,

/// <summary>
/// 已有相同组织机构,编码或名称相同
/// </summary>
[Description("已有相同组织机构,编码或名称相同")]
D2002,

/// <summary>
/// 没有权限操作机构
/// </summary>
[Description("没有权限操作机构")]
D2003,

/// <summary>
/// 该机构下有员工禁止删除
/// </summary>
[Description("该机构下有员工禁止删除")]
D2004,

/// <summary>
/// 附属机构下有员工禁止删除
/// </summary>
[Description("附属机构下有员工禁止删除")]
D2005,

/// <summary>
/// 只能增加下级机构
/// </summary>
[Description("只能增加下级机构")]
D2006,

/// <summary>
/// 字典类型不存在
/// </summary>
[Description("字典类型不存在")]
D3000,

/// <summary>
/// 字典类型已存在
/// </summary>
[Description("字典类型已存在,名称或编码重复")]
D3001,

/// <summary>
/// 字典类型下面有字典值禁止删除
/// </summary>
[Description("字典类型下面有字典值禁止删除")]
D3002,

/// <summary>
/// 字典值已存在
/// </summary>
[Description("字典值已存在,名称或编码重复")]
D3003,

/// <summary>
/// 字典值不存在
/// </summary>
[Description("字典值不存在")]
D3004,

/// <summary>
/// 字典状态错误
/// </summary>
[Description("字典状态错误")]
D3005,

/// <summary>
/// 菜单已存在
/// </summary>
[Description("菜单已存在")]
D4000,

/// <summary>
/// 路由地址为空
/// </summary>
[Description("路由地址为空")]
D4001,

/// <summary>
/// 打开方式为空
/// </summary>
[Description("打开方式为空")]
D4002,

/// <summary>
/// 权限标识格式为空
/// </summary>
[Description("权限标识格式为空")]
D4003,

/// <summary>
/// 权限标识格式错误
/// </summary>
[Description("权限标识格式错误")]
D4004,

/// <summary>
/// 权限不存在
/// </summary>
[Description("权限不存在")]
D4005,

/// <summary>
/// 父级菜单不能为当前节点,请重新选择父级菜单
/// </summary>
[Description("父级菜单不能为当前节点,请重新选择父级菜单")]
D4006,

/// <summary>
/// 不能移动根节点
/// </summary>
[Description("不能移动根节点")]
D4007,

/// <summary>
/// 已存在同名或同编码应用
/// </summary>
[Description("已存在同名或同编码应用")]
D5000,

/// <summary>
/// 默认激活系统只能有一个
/// </summary>
[Description("默认激活系统只能有一个")]
D5001,

/// <summary>
/// 该应用下有菜单禁止删除
/// </summary>
[Description("该应用下有菜单禁止删除")]
D5002,

/// <summary>
/// 已存在同名或同编码应用
/// </summary>
[Description("已存在同名或同编码应用")]
D5003,

/// <summary>
/// 已存在同名或同编码职位
/// </summary>
[Description("已存在同名或同编码职位")]
D6000,

/// <summary>
/// 该职位下有员工禁止删除
/// </summary>
[Description("该职位下有员工禁止删除")]
D6001,

/// <summary>
/// 通知公告状态错误
/// </summary>
[Description("通知公告状态错误")]
D7000,

/// <summary>
/// 通知公告删除失败
/// </summary>
[Description("通知公告删除失败")]
D7001,

/// <summary>
/// 通知公告编辑失败
/// </summary>
[Description("通知公告编辑失败,类型必须为草稿")]
D7002,

/// <summary>
/// 文件不存在
/// </summary>
[Description("文件不存在")]
D8000,

/// <summary>
/// 已存在同名或同编码参数配置
/// </summary>
[Description("已存在同名或同编码参数配置")]
D9000,

/// <summary>
/// 禁止删除系统参数
/// </summary>
[Description("禁止删除系统参数")]
D9001,

/// <summary>
/// 已存在同名任务调度
/// </summary>
[Description("已存在同名任务调度")]
D1100,

/// <summary>
/// 任务调度不存在
/// </summary>
[Description("任务调度不存在")]
D1101,

/// <summary>
/// 演示环境禁止修改数据
/// </summary>
[Description("演示环境禁止修改数据")]
D1200,

/// <summary>
/// 已存在同名或同管理员或同主机租户
/// </summary>
[Description("已存在同名或同主机租户")]
D1300,

/// <summary>
/// 该表代码模板已经生成过
/// </summary>
[Description("该表代码模板已经生成过")]
D1400,

/// <summary>
/// 该类型不存在
/// </summary>
[Description("该类型不存在")]
D1501,

/// <summary>
/// 该字段不存在
/// </summary>
[Description("该字段不存在")]
D1502,

/// <summary>
/// 该类型不是枚举类型
/// </summary>
[Description("该类型不是枚举类型")]
D1503,

/// <summary>
/// 该实体不存在
/// </summary>
[Description("该实体不存在")]
D1504,

/// <summary>
/// 父菜单不存在
/// </summary>
[Description("父菜单不存在")]
D1505,

/// <summary>
/// 已存在同名或同编码项目
/// </summary>
[Description("已存在同名或同编码项目")]
xg1000,

/// <summary>
/// 已存在相同证件号码人员
/// </summary>
[Description("已存在相同证件号码人员")]
xg1001,

/// <summary>
/// 检测数据不存在
/// </summary>
[Description("检测数据不存在")]
xg1002,
}
}

+ 6
- 7
src/BPA.SaaS.Stock.Api.Entity/BaseGroupIdEntity.cs Bestand weergeven

@@ -1,11 +1,10 @@
using SqlSugar;

namespace BPA.SaaS.Stock.Api.Entity
namespace BPA.SaaS.Stock.Api.Entity;

public class BaseGroupIdEntity : Entity
{
public class BaseGroupIdEntity : Entity
{
// TODO:MOD
[SugarColumn(ColumnDataType = "nvarchar(64)", ColumnDescription = "GroupId", IsNullable = true)]
public string GroupId { get; set; } /*= App.User?.FindFirst(ClaimConst.GroupId)?.Value;*/
}
// TODO:MOD
[SugarColumn(ColumnDataType = "nvarchar(64)", ColumnDescription = "GroupId", IsNullable = true)]
public string GroupId { get; set; } /*= App.User?.FindFirst(ClaimConst.GroupId)?.Value;*/
}

+ 13
- 33
src/BPA.SaaS.Stock.Api.Entity/BaseOPEntity.cs Bestand weergeven

@@ -1,4 +1,5 @@
using SqlSugar;
using BPA.SaaS.Stock.Api.DTO.Basic.Claim;
using SqlSugar;

namespace BPA.SaaS.Stock.Api.Entity
{
@@ -12,8 +13,8 @@ namespace BPA.SaaS.Stock.Api.Entity
/// 0=> 正常使用
/// 1=> 已经被标记删除
/// </summary>
[SugarColumn(ColumnDataType = "int", ColumnDescription = "是否删除", IsNullable = false)]
public int IsDeleted { get; set; } = 0;
[SugarColumn(ColumnDataType = "bit", ColumnDescription = "是否删除", IsNullable = false)]
public bool IsDeleted { get; set; }

/// <summary>
/// 删除时间
@@ -58,52 +59,31 @@ namespace BPA.SaaS.Stock.Api.Entity
// {
// var userId = App.User?.FindFirst(ClaimConst.CLAINM_USERID)?.Value;
// var userName = App.User?.FindFirst(ClaimConst.CLAINM_ACCOUNT)?.Value;
// this.Id = Guid.NewGuid().ToString();
// this.CreateAt = DateTime.Now;
// if (!string.IsNullOrEmpty(userId))
// {
// this.CreateBy = userId;
// }
// else
// {
// this.CreateBy = "admin";
// }
// Id = Guid.NewGuid().ToString();
// CreateAt = DateTime.Now;
// CreateBy = !string.IsNullOrEmpty(userId) ? userId : "admin";
// }
//
// /// <summary>
// /// 修改
// /// </summary>
// public void Modify()
// public virtual void Modify()
// {
// var userId = App.User?.FindFirst(ClaimConst.CLAINM_USERID)?.Value;
// var userName = App.User?.FindFirst(ClaimConst.CLAINM_ACCOUNT)?.Value;
// this.UpdateAt = DateTime.Now;
// if (!string.IsNullOrEmpty(userId))
// {
// this.UpdateBy = userId;
// }
// else
// {
// this.UpdateBy = "admin";
// }
// UpdateAt = DateTime.Now;
// UpdateBy = !string.IsNullOrEmpty(userId) ? userId : "admin";
// }
//
// /// <summary>
// /// 删除
// /// </summary>
// public void Delete()
// public virtual void Delete()
// {
// var userId = App.User?.FindFirst(ClaimConst.CLAINM_USERID)?.Value;
// var userName = App.User?.FindFirst(ClaimConst.CLAINM_ACCOUNT)?.Value;
// this.DeleteAt = DateTime.Now;
// if (!string.IsNullOrEmpty(userId))
// {
// this.DeleteBy = userId;
// }
// else
// {
// this.DeleteBy = "admin";
// }
// DeleteAt = DateTime.Now;
// DeleteBy = !string.IsNullOrEmpty(userId) ? userId : "admin";
// }
}
}

+ 2
- 4
src/BPA.SaaS.Stock.Api.Entity/Entity.cs Bestand weergeven

@@ -6,14 +6,12 @@ namespace BPA.SaaS.Stock.Api.Entity
/// <summary>
/// 自定义实体基类
/// </summary>
public abstract class Entity: BaseOPEntity
public abstract class Entity : BaseOPEntity
{
/// <summary>
/// 状态
/// </summary>
[SugarColumn(ColumnDataType = "int", ColumnDescription = "状态", IsNullable = false)]
public EnumCommonStatus Status { get; set; } = EnumCommonStatus.ENABLE;

}
}
}

+ 6
- 0
src/BPA.SaaS.Stock.Api.IRepository/Auth/IAuthRepository.cs Bestand weergeven

@@ -0,0 +1,6 @@
namespace BPA.SaaS.Stock.Api.IRepository.Auth;

public interface IAuthRepository
{
}

+ 3
- 0
src/BPA.SaaS.Stock.Api.IRepository/BPA.SaaS.Stock.Api.IRepository.csproj Bestand weergeven

@@ -12,4 +12,7 @@
<ProjectReference Include="..\BPA.SaaS.Stock.Api.DTO\BPA.SaaS.Stock.Api.DTO.csproj" />
<ProjectReference Include="..\BPA.SaaS.Stock.Api.Entity\BPA.SaaS.Stock.Api.Entity.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Cache" />
</ItemGroup>
</Project>

+ 28
- 0
src/BPA.SaaS.Stock.Api.IService/Auth/IAuthService.cs Bestand weergeven

@@ -0,0 +1,28 @@
using BPA.Component.DTOCommon.BaseDTOs;
using BPA.SaaS.Stock.Api.DTO.Basic.Auth;

namespace BPA.SaaS.Stock.Api.IService.Auth
{
public interface IAuthService
{
/// <summary>
/// 获取当前登录用户信息
/// </summary>
/// <returns></returns>
Task<BaseResult<LoginOutput>> GetLoginUserAsync(string logintype);

/// <summary>
/// 用户名密码登录
/// </summary>
/// <param name="LoginType">1平台用户登录,0加盟商登录</param>
/// <param name="input"></param>
/// <returns></returns>
Task<BaseResult<LoginOutInfo>> LoginAsync(string LoginType, LoginInput input);

/// <summary>
/// 退出
/// </summary>
/// <returns></returns>
BaseResult<string> LogoutAsync();
}
}

+ 39
- 0
src/BPA.SaaS.Stock.Api.IService/Cache/ISysCacheService.cs Bestand weergeven

@@ -0,0 +1,39 @@
using BPA.SaaS.Stock.Api.DTO.Basic.Cache;

namespace BPA.SaaS.Stock.Api.IService.Cache
{
public interface ISysCacheService
{
Task AddCacheKey(string cacheKey);

Task DelByPatternAsync(string key);

Task DelCacheKey(string cacheKey);

bool Exists(string cacheKey);

Task<List<string>> GetAllCacheKeys();

Task<T> GetAsync<T>(string cacheKey);

Task<List<long>> GetDataScope(long userId);

Task<List<AntDesignTreeNode>> GetMenu(long userId, string appCode);

Task<List<string>> GetPermission(long userId);

Task<string> GetStringAsync(string cacheKey);

Task RemoveAsync(string key);

Task SetAsync(string cacheKey, object value);

Task SetDataScope(long userId, List<long> dataScopes);

Task SetMenu(long userId, string appCode, List<AntDesignTreeNode> menus);

Task SetPermission(long userId, List<string> permissions);

Task SetStringAsync(string cacheKey, string value);
}
}

+ 12
- 0
src/BPA.SaaS.Stock.Api.Repository/Auth/AuthRepository.cs Bestand weergeven

@@ -0,0 +1,12 @@
using BPA.Component.DbClient.RepositoryModel;
using BPA.SaaS.Stock.Api.Entity.SYS;
using BPA.SaaS.Stock.Api.IRepository.Auth;

namespace BPA.SaaS.Stock.Api.Repository.Auth;

public class AuthRepository : BaseDbRepository<StockDbSugarClient, BPA_Users>, IAuthRepository
{
public AuthRepository(StockDbSugarClient dbClient) : base(dbClient)
{
}
}

+ 1
- 1
src/BPA.SaaS.Stock.Api.Repository/Product/ProductCodeRepository.cs Bestand weergeven

@@ -20,7 +20,7 @@ public class ProductCodeRepository : BaseDbRepository<StockDbSugarClient, BPA_Pr
var total = 0;

var rows = await SimpleClient.AsQueryable()
.Where(a => a.IsDeleted == 0)
.Where(a => a.IsDeleted == false)
.WhereIF(dto.Code.IsNotEmpty(), code => code.Code == dto.Code)
.WhereIF(dto.Id.IsNotEmpty(), code => code.ProductID == dto.Code)
// where etc..


+ 17
- 17
src/BPA.SaaS.Stock.Api.Repository/Product/ProductRepository.cs Bestand weergeven

@@ -20,7 +20,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
var total = 0;
var result = await DbClient
.Queryable<BPA_Batching, BPA_ProductCode>((a, b) => new JoinQueryInfos(JoinType.Inner, a.Id == b.ProductID))
.Where((a, b) => a.IsDeleted == 0 && b.IsDeleted == 0)
.Where((a, b) => a.IsDeleted == false && b.IsDeleted == false)
.Select((a, b) => new ProductListByCodeQuery
{
Id = a.Id,
@@ -43,14 +43,14 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
var total = 0;
var result = await DbClient
.Queryable<BPA_Batching, BPA_ProductType, BPA_Uint>((a, b, c) => new JoinQueryInfos(JoinType.Inner, a.TypeID == b.Id, JoinType.Inner, a.StockUint == c.Id))
.Where((a, b, c) => a.IsDeleted == 0 && b.IsDeleted == 0 && c.IsDeleted == 0)
.Where((a, b, c) => a.IsDeleted == false && b.IsDeleted == false && c.IsDeleted == false)
.WhereIF(groupId.IsNotEmpty(), a => a.GroupId == groupId)
.WhereIF(dto.Name.IsNotEmpty(), a => a.Batching_Name == dto.Name)
.WhereIF(dto.Code.IsNotEmpty(), a => a.Code == dto.Code)
.WhereIF(dto.StockUint.IsNotEmpty(), c => c.Id == dto.StockUint)
.WhereIF(dto.StockUint.IsNotEmpty(), a => a.Id == dto.StockUint)
.WhereIF(dto.Specs.IsNotEmpty(), a => a.Specs == dto.Specs)
.WhereIF(dto.Aittribute.IsNotEmpty(), a => a.Aittribute.ToString() == dto.Aittribute)
.WhereIF(dto.TypeID.IsNotEmpty(), b => b.TypeID == dto.TypeID)
.WhereIF(dto.TypeID.IsNotEmpty(), a => a.TypeID == dto.TypeID)
.WhereIF(dto.Status.HasValue, a => a.Status == dto.Status)
.WhereIF(dto.CreateAt.HasValue, a => SqlFunc.DateIsSame(a.CreateAt, Convert.ToDateTime(dto.CreateAt), DateType.Day))
.WhereIF(dto.UpdateAt.HasValue, a => SqlFunc.DateIsSame(a.UpdateAt, Convert.ToDateTime(dto.UpdateAt), DateType.Day))
@@ -69,7 +69,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
CreateAt = a.CreateAt,
UpdateAt = a.UpdateAt,
Status = a.Status,
StatusText = a.Status.GetDescribe(),
// StatusText = a.Status.GetDescribe(),
batchingType = a.Batching_Type
})
.ToPageListAsync(dto.Current, dto.PageSize, total);
@@ -113,12 +113,12 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
bpaProduct.Batching_Type = dto.batchingType;
await DbClient.Insertable(bpaProduct).IgnoreColumns(true).ExecuteCommandAsync();

var productCode = await DbClient.Queryable<BPA_ProductCode>().Where(x => x.IsDeleted == 0 && x.ProductID == dto.Id).ToListAsync();
var productCode = await DbClient.Queryable<BPA_ProductCode>().Where(x => x.IsDeleted == false && x.ProductID == dto.Id).ToListAsync();
if (productCode.Any())
{
foreach (var x in productCode)
{
x.IsDeleted = 1;
x.IsDeleted = true;
x.DeleteAt = DateTime.Now;
x.DeleteBy = ""; //TODO:
}
@@ -149,18 +149,18 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
var bpaBatching = await DbClient.Queryable<BPA_Batching>().In(ids).ToListAsync();
bpaBatching.ForEach(x =>
{
x.IsDeleted = 1;
x.IsDeleted = true;
x.DeleteAt = DateTime.Now;
x.DeleteBy = ""; //TODO
});
await DbClient.Updateable(bpaBatching).ExecuteCommandAsync();

var productCode = await DbClient.Queryable<BPA_ProductCode>().Where(x => x.IsDeleted == 0 && ids.Contains(x.ProductID)).ToListAsync();
var productCode = await DbClient.Queryable<BPA_ProductCode>().Where(x => x.IsDeleted == false && ids.Contains(x.ProductID)).ToListAsync();
if (productCode.Any())
{
productCode.ForEach(x =>
{
x.IsDeleted = 1;
x.IsDeleted = true;
x.DeleteAt = DateTime.Now;
x.DeleteBy = ""; //TODO
});
@@ -174,7 +174,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
{
var result = await DbClient
.Queryable<BPA_ProductCode>()
.Where(x => x.IsDeleted == 0 && x.ProductID == productId)
.Where(x => x.IsDeleted == false && x.ProductID == productId)
.Select(x => new ProductDetailedDto
{
Id = x.Id,
@@ -222,7 +222,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
var items = new List<ProductInfoDto>();
if (typeValue == "1")
{
items = await DbClient.SqlQueryable<BPA_Batching>(sql).Where(it => it.IsDeleted == 0).Select(a => new ProductInfoDto
items = await DbClient.SqlQueryable<BPA_Batching>(sql).Where(it => it.IsDeleted == false).Select(a => new ProductInfoDto
{
TypeID = a.TypeID,
Status = a.Status
@@ -230,7 +230,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
}
else
{
items = await DbClient.SqlQueryable<BPA_Batching>(sql).Where(it => it.IsDeleted == 0).Select(a => new ProductInfoDto
items = await DbClient.SqlQueryable<BPA_Batching>(sql).Where(it => it.IsDeleted == false).Select(a => new ProductInfoDto
{
StockUint = a.StockUint,
Status = a.Status
@@ -256,7 +256,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
{
if (typeValue == "1")
{
itemCls = await DbClient.Queryable<BPA_ProductType>().Where(it => it.IsDeleted == 0 && it.Id == x.TypeID).ToListAsync();
itemCls = await DbClient.Queryable<BPA_ProductType>().Where(it => it.IsDeleted == false && it.Id == x.TypeID).ToListAsync();
if (itemCls.Count > 0)
{
itemCls.ForEach(x =>
@@ -272,7 +272,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
}
else
{
itemUint = await DbClient.Queryable<BPA_Uint>().Where(it => it.IsDeleted == 0 && it.Id == x.StockUint).ToListAsync();
itemUint = await DbClient.Queryable<BPA_Uint>().Where(it => it.IsDeleted == false && it.Id == x.StockUint).ToListAsync();
if (itemUint.Count > 0)
{
itemUint.ForEach(x =>
@@ -299,7 +299,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
{
if (typeValue == "1")
{
var itemCls = await DbClient.Queryable<BPA_ProductType>().Where(it => it.IsDeleted == 0 && it.Id == x.TypeID).ToListAsync();
var itemCls = await DbClient.Queryable<BPA_ProductType>().Where(it => it.IsDeleted == false && it.Id == x.TypeID).ToListAsync();
if (itemCls.Count > 0)
{
itemCls.ForEach(x =>
@@ -315,7 +315,7 @@ public class ProductRepository : BaseDbRepository<StockDbSugarClient, BPA_Produc
}
else
{
var itemUint = await DbClient.Queryable<BPA_Uint>().Where(it => it.IsDeleted == 0 && it.Id == x.StockUint).ToListAsync();
var itemUint = await DbClient.Queryable<BPA_Uint>().Where(it => it.IsDeleted == false && it.Id == x.StockUint).ToListAsync();
if (itemUint.Count > 0)
{
itemUint.ForEach(x =>


+ 227
- 0
src/BPA.SaaS.Stock.Api.Service/Auth/AuthService.cs Bestand weergeven

@@ -0,0 +1,227 @@
using BPA.Common.Enums.CRM;
using BPA.Component.DTOCommon.BaseDTOs;
using BPA.Component.Extensions;
using BPA.SaaS.Stock.Api.DTO;
using BPA.SaaS.Stock.Api.DTO.Basic.Auth;
using BPA.SaaS.Stock.Api.DTO.Basic.Claim;
using BPA.SaaS.Stock.Api.Entity.SYS;
using BPA.SaaS.Stock.Api.IService.Auth;
using BPA.SaaS.Stock.Api.Repository.Auth;
using BPA.SaaS.Stock.Api.Service.SimpleJWT;
using Microsoft.AspNetCore.Http;

namespace BPA.SaaS.Stock.Api.Service.Auth
{
/// <summary>
/// 登录授权相关
/// </summary>
/// <returns></returns>
public class AuthService : IAuthService
{
private readonly IHttpContextAccessor _httpContextAccessor;
// TODO: 不可以这样访问仓储层
private readonly AuthRepository _authRepository;

/// <summary>
///
/// </summary>
public AuthService(IHttpContextAccessor httpContextAccessor, AuthRepository authRepository)
{
_httpContextAccessor = httpContextAccessor;
_authRepository = authRepository;
}

#region 用户

/// <summary>
/// 用户名密码登录
/// </summary>
/// <param name="LoginType">1平台用户登录,0加盟商登录</param>
/// <param name="input"></param>
/// <returns></returns>
public async Task<BaseResult<LoginOutInfo>> LoginAsync(string LoginType, LoginInput input)
{
// 获取加密后的密码
var encryptPassword = input.Password.ToLower().Md5();

// 判断用户名和密码是否正确 忽略全局过滤器
var user = await _authRepository.DbClient.Queryable<BPA_Users>().Where(u => u.Account.Equals(input.Account)
&& u.Password.Equals(encryptPassword)
&& u.IsDeleted == false
&& u.Status == EnumCommonStatus.ENABLE).FirstAsync();
_ = user ?? throw new Exception(ErrorCode.D1000.GetDescribe());

//获取权限
var company = await _authRepository.DbClient.Queryable<BPA_Company>().FirstAsync(x => x.Id == user.GroupId);
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ClaimConst.CLAINM_USERID, user.Id},
{ClaimConst.LoginType, LoginType},
{ClaimConst.CLAINM_ACCOUNT, user.Account},
{ClaimConst.CLAINM_NAME, user.Name},
{ClaimConst.CLAINM_SUPERADMIN, user.Account == "admin" ? "1" : "Customer"},
{ClaimConst.GroupId, user.GroupId},
{ClaimConst.OrgId, user.SysOrgId},
{ClaimConst.SupplyPlatformId, company?.SupplyPlatformId}
});

// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, 30);

// 设置刷新Token令牌
if (_httpContextAccessor.HttpContext != null) _httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
var loginOutInfo = new LoginOutInfo
{
userID = user.Id,
token = accessToken
};
return BaseResult<LoginOutInfo>.BuildOK(loginOutInfo);
}

/// <summary>
/// 获取当前登录用户信息
/// </summary>
/// <returns></returns>
public async Task<BaseResult<LoginOutput>> GetLoginUserAsync(string logintype)
{
var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.CLAINM_USERID)?.Value;

var user = await _authRepository.DbClient.Queryable<BPA_Users>().Where(u => u.Id == userId).FirstAsync();
if (user != null)
{
return BaseResult<LoginOutput>.BuildOK(new LoginOutput
{
Account = user.Account,
Name = user.Name,
NickName = user.Name,
Avatar = "",
isAdmin = user.Account.Equals("admin"),
Id = user.Id
});
}

throw new Exception("用户不存在");
}

/// <summary>
/// 退出
/// </summary>
/// <returns></returns>
public BaseResult<string> LogoutAsync() => BaseResult<string>.BuildOK("OK");

// /// <summary>
// /// 添加用户
// /// </summary>
// /// <param name="input"></param>
// /// <returns></returns>
// public bool AddUsers(LoginInput input)
// {
// var flg = db.Queryable<BPA_Users>().First(t => t.Account == input.Account);
// if (flg != null)
// throw Oops.Oh("当前用户已存在请勿重复添加");
// BPA_Users users = new BPA_Users()
// {
// Id = Guid.NewGuid().ToString(),
// Account = input.Account,
// CreateAt = DateTime.Now,
// // Name = input.Name,
// Password = MD5Encryption.Encrypt(input.Password),
// //RoleId = input.RoleId,
// Status = CommonStatus.ENABLE,
// CreateBy = App.User.FindFirst(ClaimConst.CLAINM_NAME).Value
// };
// return db.Insertable(users).ExecuteCommand() > 0;
// }
//
// /// <summary>
// /// 重置密码
// /// </summary>
// /// <param name="Id"></param>
// /// <returns></returns>
// public bool RestorePwd(string Id)
// {
// var Acc = App.User?.FindFirst(ClaimConst.CLAINM_ACCOUNT).Value;
// if (string.IsNullOrEmpty(Acc))
// throw Oops.Oh("未登录");
//
// var md5 = MD5Encryption.Encrypt("123456");
// return db.Updateable<BPA_Users>(t => t.Password == md5).Where(t => t.Id == Id).ExecuteCommandHasChange();
// }
//
// /// <summary>
// /// 用户页面分页
// /// </summary>
// /// <param name="input"></param>
// /// <returns></returns>
// public PageUtil UserPage(LoginPageInput input)
// {
// int total = 0;
// var data = db.Queryable<BPA_Users, BPA_Roles>((t, x) => new JoinQueryInfos(JoinType.Left, t.Id == x.Id))
// .Where(t => t.Account.ToLower() != "admin")
// .WhereIF(!string.IsNullOrEmpty(input.Name), t => t.Name.Contains(input.Name))
// .Select((t, x) => new LoginOutput
// {
// Id = t.Id,
// Account = t.Account,
// Name = t.Name,
// Stutas = (int) t.Status,
// RoleName = x.Name
// }).ToPageList(input.PageIndex, input.PageSize, ref total);
// return new PageUtil
// {
// Data = data,
// Total = total
// };
// }
//
// /// <summary>
// /// 修改用户密码
// /// </summary>
// /// <param name="input"></param>
// /// <returns></returns>
// public bool AlterPwd(UserInputDTO input)
// {
// var Old = MD5Encryption.Encrypt(input.OldPwd);
// var New = MD5Encryption.Encrypt(input.NewPwd);
// BPA_Users User = db.Queryable<BPA_Users>().Where(t => t.Id == input.Id && t.Password == Old).First();
// if (User == null) throw Oops.Oh("旧密码错误");
// return db.Updateable<BPA_Users>(t => t.Password == New).Where(t => t.Id == input.Id).ExecuteCommandHasChange();
// }
//
// /// <summary>
// /// 启用禁用
// /// </summary>
// /// <param name="Id"></param>
// /// <param name="type"></param>
// /// <returns></returns>
// public bool AlterUserStatus(string Id, bool type)
// {
// return db.Updateable<BPA_Users>().SetColumnsIF(type == true, t => t.Status == CommonStatus.ENABLE)
// .SetColumnsIF(type == false, t => t.Status == CommonStatus.DISABLE)
// .Where(t => t.Id == Id).ExecuteCommandHasChange();
// }
//
// /// <summary>
// /// 删除用户
// /// </summary>
// /// <param name="Id"></param>
// /// <returns></returns>
// public bool RemovUser(string Id)
// {
// return db.Deleteable<BPA_Users>(t => t.Id == Id).ExecuteCommandHasChange();
// }
//
// /// <summary>
// /// 变更用户权限
// /// </summary>
// /// <param name="Id"></param>
// /// <param name="RoleId"></param>
// /// <returns></returns>
// public bool AlterUserRole(string Id, string RoleId)
// {
// return db.Updateable<BPA_Users>().SetColumns(t => t.Id == RoleId).Where(t => t.Id == Id).ExecuteCommandHasChange();
// }

#endregion
}
}

+ 1
- 0
src/BPA.SaaS.Stock.Api.Service/BPA.SaaS.Stock.Api.Service.csproj Bestand weergeven

@@ -13,5 +13,6 @@
<ProjectReference Include="..\BPA.SaaS.Stock.Api.IService\BPA.SaaS.Stock.Api.IService.csproj" />
<ProjectReference Include="..\BPA.SaaS.Stock.Api.Model\BPA.SaaS.Stock.Api.Model.csproj" />
<ProjectReference Include="..\BPA.SaaS.Stock.Api.IRepository\BPA.SaaS.Stock.Api.IRepository.csproj" />
<ProjectReference Include="..\BPA.SaaS.Stock.Api.Repository\BPA.SaaS.Stock.Api.Repository.csproj" />
</ItemGroup>
</Project>

+ 229
- 0
src/BPA.SaaS.Stock.Api.Service/Cache/SysCacheService.cs Bestand weergeven

@@ -0,0 +1,229 @@
using System.Text;
using BPA.Component.Extensions;
using BPA.SaaS.Stock.Api.DTO.Basic.Cache;
using BPA.SaaS.Stock.Api.DTO.Basic.Claim;
using BPA.SaaS.Stock.Api.IService.Cache;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;

namespace BPA.SaaS.Stock.Api.Service.Cache
{
/// <summary>
/// 系统缓存服务
/// </summary>
public class SysCacheService : ISysCacheService
{
private readonly IDistributedCache _cache;

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

/// <summary>
/// 获取数据范围缓存(机构Id集合)
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<List<long>> GetDataScope(long userId)
{
var cacheKey = CommonConst.CACHE_KEY_DATASCOPE + $"{userId}";
var res = await _cache.GetStringAsync(cacheKey);
return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj<List<long>>();
}

/// <summary>
/// 缓存数据范围(机构Id集合)
/// </summary>
/// <param name="userId"></param>
/// <param name="dataScopes"></param>
/// <returns></returns>
public async Task SetDataScope(long userId, List<long> dataScopes)
{
var cacheKey = CommonConst.CACHE_KEY_DATASCOPE + $"{userId}";
await _cache.SetStringAsync(cacheKey, dataScopes.ToJson());
await AddCacheKey(cacheKey);
}

/// <summary>
/// 获取菜单缓存
/// </summary>
/// <param name="userId"></param>
/// <param name="appCode"></param>
/// <returns></returns>
public async Task<List<AntDesignTreeNode>> GetMenu(long userId, string appCode)
{
var cacheKey = CommonConst.CACHE_KEY_MENU + $"{userId}-{appCode}";
var res = await _cache.GetStringAsync(cacheKey);
return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj<List<AntDesignTreeNode>>();
}

/// <summary>
/// 缓存菜单
/// </summary>
/// <param name="userId"></param>
/// <param name="appCode"></param>
/// <param name="menus"></param>
/// <returns></returns>
public async Task SetMenu(long userId, string appCode, List<AntDesignTreeNode> menus)
{
var cacheKey = CommonConst.CACHE_KEY_MENU + $"{userId}-{appCode}";
await _cache.SetStringAsync(cacheKey, menus.ToJson());

await AddCacheKey(cacheKey);
}

/// <summary>
/// 获取权限缓存(按钮)
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<List<string>> GetPermission(long userId)
{
var cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{userId}";
var res = await _cache.GetStringAsync(cacheKey);
return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj<List<string>>();
}

/// <summary>
/// 缓存权限
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <returns></returns>
public async Task SetPermission(long userId, List<string> permissions)
{
var cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{userId}";
await _cache.SetStringAsync(cacheKey, permissions.ToJson());

await AddCacheKey(cacheKey);
}

/// <summary>
/// 获取所有缓存关键字
/// </summary>
/// <returns></returns>
[HttpGet("/api/sysCache/keyList")]
public async Task<List<string>> GetAllCacheKeys()
{
var res = await _cache.GetStringAsync(CommonConst.CACHE_KEY_ALL);
return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj<List<string>>();
}

/// <summary>
/// 删除指定关键字缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
[HttpGet("/api/sysCache/remove")]
public async Task RemoveAsync(string key)
{
await _cache.RemoveAsync(key);

await DelCacheKey(key);
}

/// <summary>
/// 删除某特征关键字缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task DelByPatternAsync(string key)
{
var allkeys = await GetAllCacheKeys();
var delAllkeys = allkeys.Where(u => u.Contains(key)).ToList();

// 删除相应的缓存
delAllkeys.ForEach(u => { _cache.Remove(u); });

// 更新所有缓存键
allkeys = allkeys.Where(u => !u.Contains(key)).ToList();
await _cache.SetStringAsync(CommonConst.CACHE_KEY_ALL, allkeys.ToJson());
}

/// <summary>
/// 设置缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task SetAsync(string cacheKey, object value)
{
await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(value.ToJson()));

await AddCacheKey(cacheKey);
}

/// <summary>
/// 设置缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task SetStringAsync(string cacheKey, string value)
{
await _cache.SetStringAsync(cacheKey, value);

await AddCacheKey(cacheKey);
}

/// <summary>
/// 获取缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
[HttpGet("sysCache/detail")]
public async Task<string> GetStringAsync(string cacheKey)
{
return await _cache.GetStringAsync(cacheKey);
}

/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task<T> GetAsync<T>(string cacheKey)
{
var res = await _cache.GetAsync(cacheKey);
return res == null ? default : Encoding.UTF8.GetString(res).JsonToObj<T>();
}

/// <summary>
/// 检查给定 key 是否存在
/// </summary>
/// <param name="cacheKey">键</param>
/// <returns></returns>
public bool Exists(string cacheKey)
{
return _cache.Equals(cacheKey);
}

/// <summary>
/// 增加缓存Key
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task AddCacheKey(string cacheKey)
{
var res = await _cache.GetStringAsync(CommonConst.CACHE_KEY_ALL);
var allkeys = string.IsNullOrWhiteSpace(res) ? new List<string>() : res.JsonToObj<List<string>>();
allkeys.Add(cacheKey);
await _cache.SetStringAsync(CommonConst.CACHE_KEY_ALL, allkeys.ToJson());
}

/// <summary>
///
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task DelCacheKey(string cacheKey)
{
var res = await _cache.GetStringAsync(CommonConst.CACHE_KEY_ALL);
var allkeys = string.IsNullOrWhiteSpace(res) ? new List<string>() : res.JsonToObj<List<string>>();
allkeys.Remove(cacheKey);
await _cache.SetStringAsync(CommonConst.CACHE_KEY_ALL, allkeys.ToJson());
}
}
}

+ 455
- 0
src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTEncryption.cs Bestand weergeven

@@ -0,0 +1,455 @@
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

namespace BPA.SaaS.Stock.Api.Service.SimpleJWT;

/// <summary>
/// JWT 加解密
/// </summary>
public class JWTEncryption
{
/// <summary>
/// 生成 Token
/// </summary>
/// <param name="payload"></param>
/// <param name="expiredTime">过期时间(分钟)</param>
/// <returns></returns>
public static string Encrypt(IDictionary<string, object> payload, long? expiredTime = null)
{
var (Payload, JWTSettings) = CombinePayload(payload, expiredTime);
return Encrypt(JWTSettings.IssuerSigningKey, Payload, JWTSettings.Algorithm);
}

/// <summary>
/// 生成 Token
/// </summary>
/// <param name="issuerSigningKey"></param>
/// <param name="payload"></param>
/// <param name="algorithm"></param>
/// <returns></returns>
public static string Encrypt(string issuerSigningKey, IDictionary<string, object> payload, string algorithm = SecurityAlgorithms.HmacSha256)
{
return Encrypt(issuerSigningKey, JsonSerializer.Serialize(payload), algorithm);
}

/// <summary>
/// 生成 Token
/// </summary>
/// <param name="issuerSigningKey"></param>
/// <param name="payload"></param>
/// <param name="algorithm"></param>
/// <returns></returns>
public static string Encrypt(string issuerSigningKey, string payload, string algorithm = SecurityAlgorithms.HmacSha256)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(issuerSigningKey));
var credentials = new SigningCredentials(securityKey, algorithm);

var tokenHandler = new JsonWebTokenHandler();
return tokenHandler.CreateToken(payload, credentials);
}

/// <summary>
/// 生成刷新 Token
/// </summary>
/// <param name="accessToken"></param>
/// <param name="expiredTime">刷新 Token 有效期(分钟)</param>
/// <returns></returns>
public static string GenerateRefreshToken(string accessToken, int expiredTime = 43200)
{
// 分割Token
var tokenParagraphs = accessToken.Split('.', StringSplitOptions.RemoveEmptyEntries);

var s = RandomNumberGenerator.GetInt32(10, tokenParagraphs[1].Length / 2 + 2);
var l = RandomNumberGenerator.GetInt32(3, 13);

var payload = new Dictionary<string, object>
{
{"f", tokenParagraphs[0]},
{"e", tokenParagraphs[2]},
{"s", s},
{"l", l},
{"k", tokenParagraphs[1].Substring(s, l)}
};

return Encrypt(payload, expiredTime);
}

/// <summary>
/// 通过过期Token 和 刷新Token 换取新的 Token
/// </summary>
/// <param name="expiredToken"></param>
/// <param name="refreshToken"></param>
/// <param name="expiredTime">过期时间(分钟)</param>
/// <param name="clockSkew">刷新token容差值,秒做单位</param>
/// <returns></returns>
public static string Exchange(string expiredToken, string refreshToken, long? expiredTime = null, long clockSkew = 5)
{
// 交换刷新Token 必须原Token 已过期
var (_isValid, _, _) = Validate(expiredToken);
if (_isValid) return default;

// 判断刷新Token 是否过期
var (isValid, refreshTokenObj, _) = Validate(refreshToken);
if (!isValid) return default;

// 解析 HttpContext
var httpContext = GetCurrentHttpContext();

// 判断这个刷新Token 是否已刷新过
var blacklistRefreshKey = "BLACKLIST_REFRESH_TOKEN:" + refreshToken;
var distributedCache = httpContext?.RequestServices?.GetService<IDistributedCache>();

// 处理token并发容错问题
var nowTime = DateTimeOffset.UtcNow;
var cachedValue = distributedCache?.GetString(blacklistRefreshKey);
var isRefresh = !string.IsNullOrWhiteSpace(cachedValue); // 判断是否刷新过
if (isRefresh)
{
var refreshTime = new DateTimeOffset(long.Parse(cachedValue), TimeSpan.Zero);
// 处理并发时容差值
if ((nowTime - refreshTime).TotalSeconds > clockSkew) return default;
}

// 分割过期Token
var tokenParagraphs = expiredToken.Split('.', StringSplitOptions.RemoveEmptyEntries);
if (tokenParagraphs.Length < 3) return default;

// 判断各个部分是否匹配
if (!refreshTokenObj.GetPayloadValue<string>("f").Equals(tokenParagraphs[0])) return default;
if (!refreshTokenObj.GetPayloadValue<string>("e").Equals(tokenParagraphs[2])) return default;
if (!tokenParagraphs[1].Substring(refreshTokenObj.GetPayloadValue<int>("s"), refreshTokenObj.GetPayloadValue<int>("l"))
.Equals(refreshTokenObj.GetPayloadValue<string>("k"))) return default;

// 获取过期 Token 的存储信息
var oldToken = ReadJwtToken(expiredToken);
var payload = oldToken.Claims.Where(u => !StationaryClaimTypes.Contains(u.Type))
.ToDictionary(u => u.Type, u => (object) u.Value, new MultiClaimsDictionaryComparer());

// 交换成功后登记刷新Token,标记失效
if (!isRefresh)
{
distributedCache?.SetString(blacklistRefreshKey, nowTime.Ticks.ToString(), new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.FromUnixTimeSeconds(refreshTokenObj.GetPayloadValue<long>(JwtRegisteredClaimNames.Exp))
});
}

return Encrypt(payload, expiredTime);
}

/// <summary>
/// 自动刷新 Token 信息
/// </summary>
/// <param name="context"></param>
/// <param name="httpContext"></param>
/// <param name="expiredTime">新 Token 过期时间(分钟)</param>
/// <param name="refreshTokenExpiredTime">新刷新 Token 有效期(分钟)</param>
/// <param name="tokenPrefix"></param>
/// <param name="clockSkew"></param>
/// <returns></returns>
public static bool AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200,
string tokenPrefix = "Bearer ", long clockSkew = 5)
{
// 如果验证有效,则跳过刷新
if (context.User.Identity.IsAuthenticated) return true;

// 判断是否含有匿名特性
if (httpContext.GetEndpoint()?.Metadata?.GetMetadata<AllowAnonymousAttribute>() != null) return true;

// 获取过期Token 和 刷新Token
var expiredToken = GetJwtBearerToken(httpContext, tokenPrefix: tokenPrefix);
var refreshToken = GetJwtBearerToken(httpContext, "X-Authorization", tokenPrefix: tokenPrefix);
if (string.IsNullOrWhiteSpace(expiredToken) || string.IsNullOrWhiteSpace(refreshToken)) return false;

// 交换新的 Token
var accessToken = Exchange(expiredToken, refreshToken, expiredTime, clockSkew);
if (string.IsNullOrWhiteSpace(accessToken)) return false;

// 读取新的 Token Clamis
var claims = ReadJwtToken(accessToken)?.Claims;
if (claims == null) return false;

// 创建身份信息
var claimIdentity = new ClaimsIdentity("AuthenticationTypes.Federation");
claimIdentity.AddClaims(claims);
var claimsPrincipal = new ClaimsPrincipal(claimIdentity);

// 设置 HttpContext.User 并登录
httpContext.User = claimsPrincipal;
httpContext.SignInAsync(claimsPrincipal);

string accessTokenKey = "access-token", xAccessTokenKey = "x-access-token", accessControlExposeKey = "Access-Control-Expose-Headers";

// 返回新的 Token
httpContext.Response.Headers[accessTokenKey] = accessToken;
// 返回新的 刷新Token
httpContext.Response.Headers[xAccessTokenKey] = GenerateRefreshToken(accessToken, refreshTokenExpiredTime);

// 处理 axios 问题
httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs);
httpContext.Response.Headers[accessControlExposeKey] = string.Join(',', StringValues.Concat(acehs, new StringValues(new[] {accessTokenKey, xAccessTokenKey})).Distinct());

return true;
}

/// <summary>
/// 验证 Token
/// </summary>
/// <param name="accessToken"></param>
/// <returns></returns>
public static (bool IsValid, JsonWebToken Token, TokenValidationResult validationResult) Validate(string accessToken)
{
var jwtSettings = GetJWTSettings();
if (jwtSettings == null) return (false, default, default);

// 加密Key
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey));
var creds = new SigningCredentials(key, jwtSettings.Algorithm);

// 创建Token验证参数
var tokenValidationParameters = CreateTokenValidationParameters(jwtSettings);
if (tokenValidationParameters.IssuerSigningKey == null) tokenValidationParameters.IssuerSigningKey = creds.Key;

// 验证 Token
var tokenHandler = new JsonWebTokenHandler();
try
{
var tokenValidationResult = tokenHandler.ValidateToken(accessToken, tokenValidationParameters);
if (!tokenValidationResult.IsValid) return (false, null, tokenValidationResult);

var jsonWebToken = tokenValidationResult.SecurityToken as JsonWebToken;
return (true, jsonWebToken, tokenValidationResult);
}
catch
{
return (false, default, default);
}
}

/// <summary>
/// 验证 Token
/// </summary>
/// <param name="httpContext"></param>
/// <param name="token"></param>
/// <param name="headerKey"></param>
/// <param name="tokenPrefix"></param>
/// <returns></returns>
public static bool ValidateJwtBearerToken(DefaultHttpContext httpContext, out JsonWebToken token, string headerKey = "Authorization", string tokenPrefix = "Bearer ")
{
// 获取 token
var accessToken = GetJwtBearerToken(httpContext, headerKey, tokenPrefix);
if (string.IsNullOrWhiteSpace(accessToken))
{
token = null;
return false;
}

// 验证token
var (IsValid, Token, _) = Validate(accessToken);
token = IsValid ? Token : null;

return IsValid;
}

/// <summary>
/// 读取 Token,不含验证
/// </summary>
/// <param name="accessToken"></param>
/// <returns></returns>
public static JsonWebToken ReadJwtToken(string accessToken)
{
var tokenHandler = new JsonWebTokenHandler();
if (tokenHandler.CanReadToken(accessToken))
{
return tokenHandler.ReadJsonWebToken(accessToken);
}

return default;
}

/// <summary>
/// 获取 JWT Bearer Token
/// </summary>
/// <param name="httpContext"></param>
/// <param name="headerKey"></param>
/// <param name="tokenPrefix"></param>
/// <returns></returns>
public static string GetJwtBearerToken(DefaultHttpContext httpContext, string headerKey = "Authorization", string tokenPrefix = "Bearer ")
{
// 判断请求报文头中是否有 "Authorization" 报文头
var bearerToken = httpContext.Request.Headers[headerKey].ToString();
if (string.IsNullOrWhiteSpace(bearerToken)) return default;

var prefixLenght = tokenPrefix.Length;
return bearerToken.StartsWith(tokenPrefix, true, null) && bearerToken.Length > prefixLenght ? bearerToken[prefixLenght..] : default;
}

/// <summary>
/// 获取 JWT 配置
/// </summary>
/// <returns></returns>
public static JWTSettingsOptions GetJWTSettings()
{
return FrameworkApp.GetMethod("GetOptions").MakeGenericMethod(typeof(JWTSettingsOptions)).Invoke(null, new object[] {null}) as JWTSettingsOptions ??
SetDefaultJwtSettings(new JWTSettingsOptions());
}

/// <summary>
/// 生成Token验证参数
/// </summary>
/// <param name="jwtSettings"></param>
/// <returns></returns>
public static TokenValidationParameters CreateTokenValidationParameters(JWTSettingsOptions jwtSettings)
{
return new TokenValidationParameters
{
// 验证签发方密钥
ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey.Value,
// 签发方密钥
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey)),
// 验证签发方
ValidateIssuer = jwtSettings.ValidateIssuer.Value,
// 设置签发方
ValidIssuer = jwtSettings.ValidIssuer,
// 验证签收方
ValidateAudience = jwtSettings.ValidateAudience.Value,
// 设置接收方
ValidAudience = jwtSettings.ValidAudience,
// 验证生存期
ValidateLifetime = jwtSettings.ValidateLifetime.Value,
// 过期时间容错值
ClockSkew = TimeSpan.FromSeconds(jwtSettings.ClockSkew.Value),
};
}

/// <summary>
/// 组合 Claims 负荷
/// </summary>
/// <param name="payload"></param>
/// <param name="expiredTime">过期时间,单位:分钟</param>
/// <returns></returns>
private static (IDictionary<string, object> Payload, JWTSettingsOptions JWTSettings) CombinePayload(IDictionary<string, object> payload, long? expiredTime = null)
{
var jwtSettings = GetJWTSettings();
var datetimeOffset = DateTimeOffset.UtcNow;

if (!payload.ContainsKey(JwtRegisteredClaimNames.Iat))
{
payload.Add(JwtRegisteredClaimNames.Iat, datetimeOffset.ToUnixTimeSeconds());
}

if (!payload.ContainsKey(JwtRegisteredClaimNames.Nbf))
{
payload.Add(JwtRegisteredClaimNames.Nbf, datetimeOffset.ToUnixTimeSeconds());
}

if (!payload.ContainsKey(JwtRegisteredClaimNames.Exp))
{
var minute = expiredTime ?? jwtSettings?.ExpiredTime ?? 20;
payload.Add(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(minute).ToUnixTimeSeconds());
}

if (!payload.ContainsKey(JwtRegisteredClaimNames.Iss))
{
payload.Add(JwtRegisteredClaimNames.Iss, jwtSettings?.ValidIssuer);
}

if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud))
{
payload.Add(JwtRegisteredClaimNames.Aud, jwtSettings?.ValidAudience);
}

return (payload, jwtSettings);
}

/// <summary>
/// 设置默认 Jwt 配置
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
internal static JWTSettingsOptions SetDefaultJwtSettings(JWTSettingsOptions options)
{
options.ValidateIssuerSigningKey ??= true;
if (options.ValidateIssuerSigningKey == true)
{
options.IssuerSigningKey ??= "U2FsdGVkX1+6H3D8Q//yQMhInzTdRZI9DbUGetbyaag=";
}

options.ValidateIssuer ??= true;
if (options.ValidateIssuer == true)
{
options.ValidIssuer ??= "dotnetchina";
}

options.ValidateAudience ??= true;
if (options.ValidateAudience == true)
{
options.ValidAudience ??= "powerby Furion";
}

options.ValidateLifetime ??= true;
if (options.ValidateLifetime == true)
{
options.ClockSkew ??= 10;
}

options.ExpiredTime ??= 20;
options.Algorithm ??= SecurityAlgorithms.HmacSha256;

return options;
}

/// <summary>
/// 获取当前的 HttpContext
/// </summary>
/// <returns></returns>
private static HttpContext GetCurrentHttpContext()
{
return FrameworkApp.GetProperty("HttpContext").GetValue(null) as HttpContext;
}

/// <summary>
/// 固定的 Claim 类型
/// </summary>
private static readonly string[] StationaryClaimTypes = new[]
{JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp, JwtRegisteredClaimNames.Iss, JwtRegisteredClaimNames.Aud};

/// <summary>
/// 框架 App 静态类
/// </summary>
internal static Type FrameworkApp { get; set; }

/// <summary>
/// 获取框架上下文
/// </summary>
/// <returns></returns>
internal static Assembly GetFrameworkContext(Assembly callAssembly)
{
if (FrameworkApp != null) return FrameworkApp.Assembly;

// 获取 Furion 程序集名称
var furionAssemblyName = callAssembly.GetReferencedAssemblies()
.FirstOrDefault(u => u.Name == "Furion" || u.Name == "Furion.Pure")
?? throw new InvalidOperationException("No `Furion` assembly installed in the current project was detected.");

// 加载 Furion 程序集
var furionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(furionAssemblyName);

// 获取 Furion.App 静态类
FrameworkApp = furionAssembly.GetType("Furion.App");

return furionAssembly;
}
}

+ 34
- 0
src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTSettingsOptions.cs Bestand weergeven

@@ -0,0 +1,34 @@
namespace BPA.SaaS.Stock.Api.Service;

public class JWTSettingsOptions
{
/// <summary>验证签发方密钥</summary>
public bool? ValidateIssuerSigningKey { get; set; }

/// <summary>签发方密钥</summary>
public string IssuerSigningKey { get; set; }

/// <summary>验证签发方</summary>
public bool? ValidateIssuer { get; set; }

/// <summary>签发方</summary>
public string ValidIssuer { get; set; }

/// <summary>验证签收方</summary>
public bool? ValidateAudience { get; set; }

/// <summary>签收方</summary>
public string ValidAudience { get; set; }

/// <summary>验证生存期</summary>
public bool? ValidateLifetime { get; set; }

/// <summary>过期时间容错值,解决服务器端时间不同步问题(秒)</summary>
public long? ClockSkew { get; set; }

/// <summary>过期时间(分钟)</summary>
public long? ExpiredTime { get; set; }

/// <summary>加密算法</summary>
public string Algorithm { get; set; }
}

+ 15
- 0
src/BPA.SaaS.Stock.Api.Service/SimpleJWT/MultiClaimsDictionaryComparer.cs Bestand weergeven

@@ -0,0 +1,15 @@
namespace BPA.SaaS.Stock.Api.Service.SimpleJWT;

internal sealed class MultiClaimsDictionaryComparer : IEqualityComparer<string>
{
/// <summary>设置字符串永不相等</summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public bool Equals(string x, string y) => x != y;

/// <summary>返回字符串 hashCode</summary>
/// <param name="obj"></param>
/// <returns></returns>
public int GetHashCode(string obj) => obj.GetHashCode();
}

+ 38
- 0
src/BPA.SaaS.Stock.Api.WebApi/Controllers/Auth/AuthController.cs Bestand weergeven

@@ -0,0 +1,38 @@
using BPA.Component.DTOCommon.BaseDTOs;
using BPA.SaaS.Stock.Api.DTO.Basic.Auth;
using BPA.SaaS.Stock.Api.IService.Auth;
using Microsoft.AspNetCore.Mvc;

namespace BPA.SaaS.Stock.Api.WebApi.Controllers.Auth;

[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;

public AuthController(IAuthService authService)
{
_authService = authService;
}

/// <summary>
/// 获取当前登录用户信息
/// </summary>
/// <returns></returns>
Task<BaseResult<LoginOutput>> GetLoginUserAsync(string logintype) => _authService.GetLoginUserAsync(logintype);

/// <summary>
/// 用户名密码登录
/// </summary>
/// <param name="LoginType">1平台用户登录,0加盟商登录</param>
/// <param name="input"></param>
/// <returns></returns>
Task<BaseResult<LoginOutInfo>> LoginAsync(string LoginType, LoginInput input) => _authService.LoginAsync(LoginType, input);

/// <summary>
/// 退出
/// </summary>
/// <returns></returns>
BaseResult<string> LogoutAsync() => _authService.LogoutAsync();
}

+ 7
- 0
src/SQLScript/20220418.sql Bestand weergeven

@@ -0,0 +1,7 @@
--SqlServer script
--修改所有表中的'IsDeleted'的字段类型为'bit'
alter table BPA_Company alter column IsDeleted bit not null
alter table BPA_Dict_Data alter column IsDeleted bit not null
alter table BPA_Dict_Type alter column IsDeleted bit not null
alter table BPA_Menu alter column IsDeleted bit not null
alter table BPA_SplitPlan_Franchisee alter column IsDeleted bit not null

Laden…
Annuleren
Opslaan