diff --git a/BPA.SaaS.Stock.Api.sln b/BPA.SaaS.Stock.Api.sln
index 3d730eb..3f6207b 100644
--- a/BPA.SaaS.Stock.Api.sln
+++ b/BPA.SaaS.Stock.Api.sln
@@ -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
diff --git a/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginInput.cs b/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginInput.cs
index 446fa61..22ed81e 100644
--- a/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginInput.cs
+++ b/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginInput.cs
@@ -20,13 +20,16 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
/// 123456
[Required(ErrorMessage = "密码不能为空")]
public string Password { get; set; }
+
public string RoleId { get; set; }
public string Name { get; set; }
+
///
/// 0正常 1停用 2删除
///
public int Stutas { get; set; }
}
+
//public class PersonValidator : AbstractValidator
//{
// 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; }
diff --git a/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginOutput.cs b/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginOutput.cs
index dbae463..3df2f94 100644
--- a/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginOutput.cs
+++ b/src/BPA.SaaS.Stock.Api.DTO/Basic/Auth/LoginOutput.cs
@@ -72,7 +72,6 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
///
public String Tel { get; set; }
-
///
/// 最后登陆IP
///
@@ -101,7 +100,7 @@ namespace BPA.SaaS.Stock.Api.DTO.Basic.Auth
///
/// 登录菜单信息---AntDesign版本菜单
///
- public List Menus { get; set; } = new List();
+ public List Menus { get; set; } = new();
///
/// 数据范围(机构)信息
diff --git a/src/BPA.SaaS.Stock.Api.DTO/EnumErrorCode.cs b/src/BPA.SaaS.Stock.Api.DTO/EnumErrorCode.cs
new file mode 100644
index 0000000..910fad7
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.DTO/EnumErrorCode.cs
@@ -0,0 +1,394 @@
+using System.ComponentModel;
+
+namespace BPA.SaaS.Stock.Api.DTO
+{
+ ///
+ /// 系统错误码
+ ///
+ public enum ErrorCode
+ {
+ ///
+ /// 用户名或密码不正确
+ ///
+ [Description("用户名或密码不正确")]
+ D1000,
+
+ ///
+ /// 非法操作!禁止删除自己
+ ///
+ [Description("非法操作,禁止删除自己")]
+ D1001,
+
+ ///
+ /// 记录不存在
+ ///
+ [Description("记录不存在")]
+ D1002,
+
+ ///
+ /// 账号已存在
+ ///
+ [Description("账号已存在")]
+ D1003,
+
+ ///
+ /// 旧密码不匹配
+ ///
+ [Description("旧密码输入错误")]
+ D1004,
+
+ ///
+ /// 测试数据禁止更改admin密码
+ ///
+ [Description("测试数据禁止更改用户【admin】密码")]
+ D1005,
+
+ ///
+ /// 数据已存在
+ ///
+ [Description("数据已存在")]
+ D1006,
+
+ ///
+ /// 数据不存在或含有关联引用,禁止删除
+ ///
+ [Description("数据不存在或含有关联引用,禁止删除")]
+ D1007,
+
+ ///
+ /// 禁止为管理员分配角色
+ ///
+ [Description("禁止为管理员分配角色")]
+ D1008,
+
+ ///
+ /// 重复数据或记录含有不存在数据
+ ///
+ [Description("重复数据或记录含有不存在数据")]
+ D1009,
+
+ ///
+ /// 禁止为超级管理员角色分配权限
+ ///
+ [Description("禁止为超级管理员角色分配权限")]
+ D1010,
+
+ ///
+ /// 非法数据
+ ///
+ [Description("非法数据")]
+ D1011,
+
+ ///
+ /// Id不能为空
+ ///
+ [Description("Id不能为空")]
+ D1012,
+
+ ///
+ /// 所属机构不在自己的数据范围内
+ ///
+ [Description("没有权限操作该数据")]
+ D1013,
+
+ ///
+ /// 禁止删除超级管理员
+ ///
+ [Description("禁止删除超级管理员")]
+ D1014,
+
+ ///
+ /// 禁止修改超级管理员状态
+ ///
+ [Description("禁止修改超级管理员状态")]
+ D1015,
+
+ ///
+ /// 没有权限
+ ///
+ [Description("没有权限")]
+ D1016,
+
+ ///
+ /// 账号已冻结
+ ///
+ [Description("账号已冻结")]
+ D1017,
+
+ ///
+ /// 父机构不存在
+ ///
+ [Description("父机构不存在")]
+ D2000,
+
+ ///
+ /// 当前机构Id不能与父机构Id相同
+ ///
+ [Description("当前机构Id不能与父机构Id相同")]
+ D2001,
+
+ ///
+ /// 已有相同组织机构,编码或名称相同
+ ///
+ [Description("已有相同组织机构,编码或名称相同")]
+ D2002,
+
+ ///
+ /// 没有权限操作机构
+ ///
+ [Description("没有权限操作机构")]
+ D2003,
+
+ ///
+ /// 该机构下有员工禁止删除
+ ///
+ [Description("该机构下有员工禁止删除")]
+ D2004,
+
+ ///
+ /// 附属机构下有员工禁止删除
+ ///
+ [Description("附属机构下有员工禁止删除")]
+ D2005,
+
+ ///
+ /// 只能增加下级机构
+ ///
+ [Description("只能增加下级机构")]
+ D2006,
+
+ ///
+ /// 字典类型不存在
+ ///
+ [Description("字典类型不存在")]
+ D3000,
+
+ ///
+ /// 字典类型已存在
+ ///
+ [Description("字典类型已存在,名称或编码重复")]
+ D3001,
+
+ ///
+ /// 字典类型下面有字典值禁止删除
+ ///
+ [Description("字典类型下面有字典值禁止删除")]
+ D3002,
+
+ ///
+ /// 字典值已存在
+ ///
+ [Description("字典值已存在,名称或编码重复")]
+ D3003,
+
+ ///
+ /// 字典值不存在
+ ///
+ [Description("字典值不存在")]
+ D3004,
+
+ ///
+ /// 字典状态错误
+ ///
+ [Description("字典状态错误")]
+ D3005,
+
+ ///
+ /// 菜单已存在
+ ///
+ [Description("菜单已存在")]
+ D4000,
+
+ ///
+ /// 路由地址为空
+ ///
+ [Description("路由地址为空")]
+ D4001,
+
+ ///
+ /// 打开方式为空
+ ///
+ [Description("打开方式为空")]
+ D4002,
+
+ ///
+ /// 权限标识格式为空
+ ///
+ [Description("权限标识格式为空")]
+ D4003,
+
+ ///
+ /// 权限标识格式错误
+ ///
+ [Description("权限标识格式错误")]
+ D4004,
+
+ ///
+ /// 权限不存在
+ ///
+ [Description("权限不存在")]
+ D4005,
+
+ ///
+ /// 父级菜单不能为当前节点,请重新选择父级菜单
+ ///
+ [Description("父级菜单不能为当前节点,请重新选择父级菜单")]
+ D4006,
+
+ ///
+ /// 不能移动根节点
+ ///
+ [Description("不能移动根节点")]
+ D4007,
+
+ ///
+ /// 已存在同名或同编码应用
+ ///
+ [Description("已存在同名或同编码应用")]
+ D5000,
+
+ ///
+ /// 默认激活系统只能有一个
+ ///
+ [Description("默认激活系统只能有一个")]
+ D5001,
+
+ ///
+ /// 该应用下有菜单禁止删除
+ ///
+ [Description("该应用下有菜单禁止删除")]
+ D5002,
+
+ ///
+ /// 已存在同名或同编码应用
+ ///
+ [Description("已存在同名或同编码应用")]
+ D5003,
+
+ ///
+ /// 已存在同名或同编码职位
+ ///
+ [Description("已存在同名或同编码职位")]
+ D6000,
+
+ ///
+ /// 该职位下有员工禁止删除
+ ///
+ [Description("该职位下有员工禁止删除")]
+ D6001,
+
+ ///
+ /// 通知公告状态错误
+ ///
+ [Description("通知公告状态错误")]
+ D7000,
+
+ ///
+ /// 通知公告删除失败
+ ///
+ [Description("通知公告删除失败")]
+ D7001,
+
+ ///
+ /// 通知公告编辑失败
+ ///
+ [Description("通知公告编辑失败,类型必须为草稿")]
+ D7002,
+
+ ///
+ /// 文件不存在
+ ///
+ [Description("文件不存在")]
+ D8000,
+
+ ///
+ /// 已存在同名或同编码参数配置
+ ///
+ [Description("已存在同名或同编码参数配置")]
+ D9000,
+
+ ///
+ /// 禁止删除系统参数
+ ///
+ [Description("禁止删除系统参数")]
+ D9001,
+
+ ///
+ /// 已存在同名任务调度
+ ///
+ [Description("已存在同名任务调度")]
+ D1100,
+
+ ///
+ /// 任务调度不存在
+ ///
+ [Description("任务调度不存在")]
+ D1101,
+
+ ///
+ /// 演示环境禁止修改数据
+ ///
+ [Description("演示环境禁止修改数据")]
+ D1200,
+
+ ///
+ /// 已存在同名或同管理员或同主机租户
+ ///
+ [Description("已存在同名或同主机租户")]
+ D1300,
+
+ ///
+ /// 该表代码模板已经生成过
+ ///
+ [Description("该表代码模板已经生成过")]
+ D1400,
+
+ ///
+ /// 该类型不存在
+ ///
+ [Description("该类型不存在")]
+ D1501,
+
+ ///
+ /// 该字段不存在
+ ///
+ [Description("该字段不存在")]
+ D1502,
+
+ ///
+ /// 该类型不是枚举类型
+ ///
+ [Description("该类型不是枚举类型")]
+ D1503,
+
+ ///
+ /// 该实体不存在
+ ///
+ [Description("该实体不存在")]
+ D1504,
+
+ ///
+ /// 父菜单不存在
+ ///
+ [Description("父菜单不存在")]
+ D1505,
+
+ ///
+ /// 已存在同名或同编码项目
+ ///
+ [Description("已存在同名或同编码项目")]
+ xg1000,
+
+ ///
+ /// 已存在相同证件号码人员
+ ///
+ [Description("已存在相同证件号码人员")]
+ xg1001,
+
+ ///
+ /// 检测数据不存在
+ ///
+ [Description("检测数据不存在")]
+ xg1002,
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Entity/BaseGroupIdEntity.cs b/src/BPA.SaaS.Stock.Api.Entity/BaseGroupIdEntity.cs
index ebd5b47..5cc4d06 100644
--- a/src/BPA.SaaS.Stock.Api.Entity/BaseGroupIdEntity.cs
+++ b/src/BPA.SaaS.Stock.Api.Entity/BaseGroupIdEntity.cs
@@ -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;*/
}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Entity/BaseOPEntity.cs b/src/BPA.SaaS.Stock.Api.Entity/BaseOPEntity.cs
index ae375c9..a12f3e3 100644
--- a/src/BPA.SaaS.Stock.Api.Entity/BaseOPEntity.cs
+++ b/src/BPA.SaaS.Stock.Api.Entity/BaseOPEntity.cs
@@ -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=> 已经被标记删除
///
- [SugarColumn(ColumnDataType = "int", ColumnDescription = "是否删除", IsNullable = false)]
- public int IsDeleted { get; set; } = 0;
+ [SugarColumn(ColumnDataType = "bit", ColumnDescription = "是否删除", IsNullable = false)]
+ public bool IsDeleted { get; set; }
///
/// 删除时间
@@ -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";
// }
//
// ///
// /// 修改
// ///
- // 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";
// }
//
// ///
// /// 删除
// ///
- // 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";
// }
}
}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Entity/Entity.cs b/src/BPA.SaaS.Stock.Api.Entity/Entity.cs
index 6a53f0f..19d2617 100644
--- a/src/BPA.SaaS.Stock.Api.Entity/Entity.cs
+++ b/src/BPA.SaaS.Stock.Api.Entity/Entity.cs
@@ -6,14 +6,12 @@ namespace BPA.SaaS.Stock.Api.Entity
///
/// 自定义实体基类
///
- public abstract class Entity: BaseOPEntity
+ public abstract class Entity : BaseOPEntity
{
///
/// 状态
///
[SugarColumn(ColumnDataType = "int", ColumnDescription = "状态", IsNullable = false)]
public EnumCommonStatus Status { get; set; } = EnumCommonStatus.ENABLE;
-
-
}
-}
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.IRepository/Auth/IAuthRepository.cs b/src/BPA.SaaS.Stock.Api.IRepository/Auth/IAuthRepository.cs
new file mode 100644
index 0000000..eeea2bc
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.IRepository/Auth/IAuthRepository.cs
@@ -0,0 +1,6 @@
+namespace BPA.SaaS.Stock.Api.IRepository.Auth;
+
+public interface IAuthRepository
+{
+
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.IRepository/BPA.SaaS.Stock.Api.IRepository.csproj b/src/BPA.SaaS.Stock.Api.IRepository/BPA.SaaS.Stock.Api.IRepository.csproj
index 01bbf6e..e566d4c 100644
--- a/src/BPA.SaaS.Stock.Api.IRepository/BPA.SaaS.Stock.Api.IRepository.csproj
+++ b/src/BPA.SaaS.Stock.Api.IRepository/BPA.SaaS.Stock.Api.IRepository.csproj
@@ -12,4 +12,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.IService/Auth/IAuthService.cs b/src/BPA.SaaS.Stock.Api.IService/Auth/IAuthService.cs
new file mode 100644
index 0000000..8ab147a
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.IService/Auth/IAuthService.cs
@@ -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
+ {
+ ///
+ /// 获取当前登录用户信息
+ ///
+ ///
+ Task> GetLoginUserAsync(string logintype);
+
+ ///
+ /// 用户名密码登录
+ ///
+ /// 1平台用户登录,0加盟商登录
+ ///
+ ///
+ Task> LoginAsync(string LoginType, LoginInput input);
+
+ ///
+ /// 退出
+ ///
+ ///
+ BaseResult LogoutAsync();
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.IService/Cache/ISysCacheService.cs b/src/BPA.SaaS.Stock.Api.IService/Cache/ISysCacheService.cs
new file mode 100644
index 0000000..212f341
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.IService/Cache/ISysCacheService.cs
@@ -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> GetAllCacheKeys();
+
+ Task GetAsync(string cacheKey);
+
+ Task> GetDataScope(long userId);
+
+ Task> GetMenu(long userId, string appCode);
+
+ Task> GetPermission(long userId);
+
+ Task GetStringAsync(string cacheKey);
+
+ Task RemoveAsync(string key);
+
+ Task SetAsync(string cacheKey, object value);
+
+ Task SetDataScope(long userId, List dataScopes);
+
+ Task SetMenu(long userId, string appCode, List menus);
+
+ Task SetPermission(long userId, List permissions);
+
+ Task SetStringAsync(string cacheKey, string value);
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Repository/Auth/AuthRepository.cs b/src/BPA.SaaS.Stock.Api.Repository/Auth/AuthRepository.cs
new file mode 100644
index 0000000..c538290
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.Repository/Auth/AuthRepository.cs
@@ -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, IAuthRepository
+{
+ public AuthRepository(StockDbSugarClient dbClient) : base(dbClient)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Repository/Product/ProductCodeRepository.cs b/src/BPA.SaaS.Stock.Api.Repository/Product/ProductCodeRepository.cs
index 5ac5340..a685fac 100644
--- a/src/BPA.SaaS.Stock.Api.Repository/Product/ProductCodeRepository.cs
+++ b/src/BPA.SaaS.Stock.Api.Repository/Product/ProductCodeRepository.cs
@@ -20,7 +20,7 @@ public class ProductCodeRepository : BaseDbRepository 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..
diff --git a/src/BPA.SaaS.Stock.Api.Repository/Product/ProductRepository.cs b/src/BPA.SaaS.Stock.Api.Repository/Product/ProductRepository.cs
index 7d9f3f8..9e02ce3 100644
--- a/src/BPA.SaaS.Stock.Api.Repository/Product/ProductRepository.cs
+++ b/src/BPA.SaaS.Stock.Api.Repository/Product/ProductRepository.cs
@@ -20,7 +20,7 @@ public class ProductRepository : BaseDbRepository((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((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().Where(x => x.IsDeleted == 0 && x.ProductID == dto.Id).ToListAsync();
+ var productCode = await DbClient.Queryable().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().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().Where(x => x.IsDeleted == 0 && ids.Contains(x.ProductID)).ToListAsync();
+ var productCode = await DbClient.Queryable().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()
- .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();
if (typeValue == "1")
{
- items = await DbClient.SqlQueryable(sql).Where(it => it.IsDeleted == 0).Select(a => new ProductInfoDto
+ items = await DbClient.SqlQueryable(sql).Where(it => it.IsDeleted == false).Select(a => new ProductInfoDto
{
TypeID = a.TypeID,
Status = a.Status
@@ -230,7 +230,7 @@ public class ProductRepository : BaseDbRepository(sql).Where(it => it.IsDeleted == 0).Select(a => new ProductInfoDto
+ items = await DbClient.SqlQueryable(sql).Where(it => it.IsDeleted == false).Select(a => new ProductInfoDto
{
StockUint = a.StockUint,
Status = a.Status
@@ -256,7 +256,7 @@ public class ProductRepository : BaseDbRepository().Where(it => it.IsDeleted == 0 && it.Id == x.TypeID).ToListAsync();
+ itemCls = await DbClient.Queryable().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().Where(it => it.IsDeleted == 0 && it.Id == x.StockUint).ToListAsync();
+ itemUint = await DbClient.Queryable().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().Where(it => it.IsDeleted == 0 && it.Id == x.TypeID).ToListAsync();
+ var itemCls = await DbClient.Queryable().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().Where(it => it.IsDeleted == 0 && it.Id == x.StockUint).ToListAsync();
+ var itemUint = await DbClient.Queryable().Where(it => it.IsDeleted == false && it.Id == x.StockUint).ToListAsync();
if (itemUint.Count > 0)
{
itemUint.ForEach(x =>
diff --git a/src/BPA.SaaS.Stock.Api.Service/Auth/AuthService.cs b/src/BPA.SaaS.Stock.Api.Service/Auth/AuthService.cs
new file mode 100644
index 0000000..93d2183
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.Service/Auth/AuthService.cs
@@ -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
+{
+ ///
+ /// 登录授权相关
+ ///
+ ///
+ public class AuthService : IAuthService
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ // TODO: 不可以这样访问仓储层
+ private readonly AuthRepository _authRepository;
+
+ ///
+ ///
+ ///
+ public AuthService(IHttpContextAccessor httpContextAccessor, AuthRepository authRepository)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ _authRepository = authRepository;
+ }
+
+ #region 用户
+
+ ///
+ /// 用户名密码登录
+ ///
+ /// 1平台用户登录,0加盟商登录
+ ///
+ ///
+ public async Task> LoginAsync(string LoginType, LoginInput input)
+ {
+ // 获取加密后的密码
+ var encryptPassword = input.Password.ToLower().Md5();
+
+ // 判断用户名和密码是否正确 忽略全局过滤器
+ var user = await _authRepository.DbClient.Queryable().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().FirstAsync(x => x.Id == user.GroupId);
+ var accessToken = JWTEncryption.Encrypt(new Dictionary
+ {
+ {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.BuildOK(loginOutInfo);
+ }
+
+ ///
+ /// 获取当前登录用户信息
+ ///
+ ///
+ public async Task> GetLoginUserAsync(string logintype)
+ {
+ var userId = _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.CLAINM_USERID)?.Value;
+
+ var user = await _authRepository.DbClient.Queryable().Where(u => u.Id == userId).FirstAsync();
+ if (user != null)
+ {
+ return BaseResult.BuildOK(new LoginOutput
+ {
+ Account = user.Account,
+ Name = user.Name,
+ NickName = user.Name,
+ Avatar = "",
+ isAdmin = user.Account.Equals("admin"),
+ Id = user.Id
+ });
+ }
+
+ throw new Exception("用户不存在");
+ }
+
+ ///
+ /// 退出
+ ///
+ ///
+ public BaseResult LogoutAsync() => BaseResult.BuildOK("OK");
+
+ // ///
+ // /// 添加用户
+ // ///
+ // ///
+ // ///
+ // public bool AddUsers(LoginInput input)
+ // {
+ // var flg = db.Queryable().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;
+ // }
+ //
+ // ///
+ // /// 重置密码
+ // ///
+ // ///
+ // ///
+ // 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(t => t.Password == md5).Where(t => t.Id == Id).ExecuteCommandHasChange();
+ // }
+ //
+ // ///
+ // /// 用户页面分页
+ // ///
+ // ///
+ // ///
+ // public PageUtil UserPage(LoginPageInput input)
+ // {
+ // int total = 0;
+ // var data = db.Queryable((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
+ // };
+ // }
+ //
+ // ///
+ // /// 修改用户密码
+ // ///
+ // ///
+ // ///
+ // public bool AlterPwd(UserInputDTO input)
+ // {
+ // var Old = MD5Encryption.Encrypt(input.OldPwd);
+ // var New = MD5Encryption.Encrypt(input.NewPwd);
+ // BPA_Users User = db.Queryable().Where(t => t.Id == input.Id && t.Password == Old).First();
+ // if (User == null) throw Oops.Oh("旧密码错误");
+ // return db.Updateable(t => t.Password == New).Where(t => t.Id == input.Id).ExecuteCommandHasChange();
+ // }
+ //
+ // ///
+ // /// 启用禁用
+ // ///
+ // ///
+ // ///
+ // ///
+ // public bool AlterUserStatus(string Id, bool type)
+ // {
+ // return db.Updateable().SetColumnsIF(type == true, t => t.Status == CommonStatus.ENABLE)
+ // .SetColumnsIF(type == false, t => t.Status == CommonStatus.DISABLE)
+ // .Where(t => t.Id == Id).ExecuteCommandHasChange();
+ // }
+ //
+ // ///
+ // /// 删除用户
+ // ///
+ // ///
+ // ///
+ // public bool RemovUser(string Id)
+ // {
+ // return db.Deleteable(t => t.Id == Id).ExecuteCommandHasChange();
+ // }
+ //
+ // ///
+ // /// 变更用户权限
+ // ///
+ // ///
+ // ///
+ // ///
+ // public bool AlterUserRole(string Id, string RoleId)
+ // {
+ // return db.Updateable().SetColumns(t => t.Id == RoleId).Where(t => t.Id == Id).ExecuteCommandHasChange();
+ // }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Service/BPA.SaaS.Stock.Api.Service.csproj b/src/BPA.SaaS.Stock.Api.Service/BPA.SaaS.Stock.Api.Service.csproj
index 6d5e75d..b138df9 100644
--- a/src/BPA.SaaS.Stock.Api.Service/BPA.SaaS.Stock.Api.Service.csproj
+++ b/src/BPA.SaaS.Stock.Api.Service/BPA.SaaS.Stock.Api.Service.csproj
@@ -13,5 +13,6 @@
+
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Service/Cache/SysCacheService.cs b/src/BPA.SaaS.Stock.Api.Service/Cache/SysCacheService.cs
new file mode 100644
index 0000000..0050736
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.Service/Cache/SysCacheService.cs
@@ -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
+{
+ ///
+ /// 系统缓存服务
+ ///
+ public class SysCacheService : ISysCacheService
+ {
+ private readonly IDistributedCache _cache;
+
+ public SysCacheService(IDistributedCache cache)
+ {
+ _cache = cache;
+ }
+
+ ///
+ /// 获取数据范围缓存(机构Id集合)
+ ///
+ ///
+ ///
+ public async Task> GetDataScope(long userId)
+ {
+ var cacheKey = CommonConst.CACHE_KEY_DATASCOPE + $"{userId}";
+ var res = await _cache.GetStringAsync(cacheKey);
+ return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj>();
+ }
+
+ ///
+ /// 缓存数据范围(机构Id集合)
+ ///
+ ///
+ ///
+ ///
+ public async Task SetDataScope(long userId, List dataScopes)
+ {
+ var cacheKey = CommonConst.CACHE_KEY_DATASCOPE + $"{userId}";
+ await _cache.SetStringAsync(cacheKey, dataScopes.ToJson());
+ await AddCacheKey(cacheKey);
+ }
+
+ ///
+ /// 获取菜单缓存
+ ///
+ ///
+ ///
+ ///
+ public async Task> 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>();
+ }
+
+ ///
+ /// 缓存菜单
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task SetMenu(long userId, string appCode, List menus)
+ {
+ var cacheKey = CommonConst.CACHE_KEY_MENU + $"{userId}-{appCode}";
+ await _cache.SetStringAsync(cacheKey, menus.ToJson());
+
+ await AddCacheKey(cacheKey);
+ }
+
+ ///
+ /// 获取权限缓存(按钮)
+ ///
+ ///
+ ///
+ public async Task> GetPermission(long userId)
+ {
+ var cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{userId}";
+ var res = await _cache.GetStringAsync(cacheKey);
+ return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj>();
+ }
+
+ ///
+ /// 缓存权限
+ ///
+ ///
+ ///
+ ///
+ public async Task SetPermission(long userId, List permissions)
+ {
+ var cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{userId}";
+ await _cache.SetStringAsync(cacheKey, permissions.ToJson());
+
+ await AddCacheKey(cacheKey);
+ }
+
+ ///
+ /// 获取所有缓存关键字
+ ///
+ ///
+ [HttpGet("/api/sysCache/keyList")]
+ public async Task> GetAllCacheKeys()
+ {
+ var res = await _cache.GetStringAsync(CommonConst.CACHE_KEY_ALL);
+ return string.IsNullOrWhiteSpace(res) ? null : res.JsonToObj>();
+ }
+
+ ///
+ /// 删除指定关键字缓存
+ ///
+ ///
+ ///
+ [HttpGet("/api/sysCache/remove")]
+ public async Task RemoveAsync(string key)
+ {
+ await _cache.RemoveAsync(key);
+
+ await DelCacheKey(key);
+ }
+
+ ///
+ /// 删除某特征关键字缓存
+ ///
+ ///
+ ///
+ 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());
+ }
+
+ ///
+ /// 设置缓存
+ ///
+ ///
+ ///
+ ///
+ public async Task SetAsync(string cacheKey, object value)
+ {
+ await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(value.ToJson()));
+
+ await AddCacheKey(cacheKey);
+ }
+
+ ///
+ /// 设置缓存
+ ///
+ ///
+ ///
+ ///
+ public async Task SetStringAsync(string cacheKey, string value)
+ {
+ await _cache.SetStringAsync(cacheKey, value);
+
+ await AddCacheKey(cacheKey);
+ }
+
+ ///
+ /// 获取缓存
+ ///
+ ///
+ ///
+ [HttpGet("sysCache/detail")]
+ public async Task GetStringAsync(string cacheKey)
+ {
+ return await _cache.GetStringAsync(cacheKey);
+ }
+
+ ///
+ /// 获取缓存
+ ///
+ ///
+ ///
+ ///
+ public async Task GetAsync(string cacheKey)
+ {
+ var res = await _cache.GetAsync(cacheKey);
+ return res == null ? default : Encoding.UTF8.GetString(res).JsonToObj();
+ }
+
+ ///
+ /// 检查给定 key 是否存在
+ ///
+ /// 键
+ ///
+ public bool Exists(string cacheKey)
+ {
+ return _cache.Equals(cacheKey);
+ }
+
+ ///
+ /// 增加缓存Key
+ ///
+ ///
+ ///
+ public async Task AddCacheKey(string cacheKey)
+ {
+ var res = await _cache.GetStringAsync(CommonConst.CACHE_KEY_ALL);
+ var allkeys = string.IsNullOrWhiteSpace(res) ? new List() : res.JsonToObj>();
+ allkeys.Add(cacheKey);
+ await _cache.SetStringAsync(CommonConst.CACHE_KEY_ALL, allkeys.ToJson());
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task DelCacheKey(string cacheKey)
+ {
+ var res = await _cache.GetStringAsync(CommonConst.CACHE_KEY_ALL);
+ var allkeys = string.IsNullOrWhiteSpace(res) ? new List() : res.JsonToObj>();
+ allkeys.Remove(cacheKey);
+ await _cache.SetStringAsync(CommonConst.CACHE_KEY_ALL, allkeys.ToJson());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTEncryption.cs b/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTEncryption.cs
new file mode 100644
index 0000000..1f9703b
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTEncryption.cs
@@ -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;
+
+///
+/// JWT 加解密
+///
+public class JWTEncryption
+{
+ ///
+ /// 生成 Token
+ ///
+ ///
+ /// 过期时间(分钟)
+ ///
+ public static string Encrypt(IDictionary payload, long? expiredTime = null)
+ {
+ var (Payload, JWTSettings) = CombinePayload(payload, expiredTime);
+ return Encrypt(JWTSettings.IssuerSigningKey, Payload, JWTSettings.Algorithm);
+ }
+
+ ///
+ /// 生成 Token
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string Encrypt(string issuerSigningKey, IDictionary payload, string algorithm = SecurityAlgorithms.HmacSha256)
+ {
+ return Encrypt(issuerSigningKey, JsonSerializer.Serialize(payload), algorithm);
+ }
+
+ ///
+ /// 生成 Token
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+
+ ///
+ /// 生成刷新 Token
+ ///
+ ///
+ /// 刷新 Token 有效期(分钟)
+ ///
+ 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
+ {
+ {"f", tokenParagraphs[0]},
+ {"e", tokenParagraphs[2]},
+ {"s", s},
+ {"l", l},
+ {"k", tokenParagraphs[1].Substring(s, l)}
+ };
+
+ return Encrypt(payload, expiredTime);
+ }
+
+ ///
+ /// 通过过期Token 和 刷新Token 换取新的 Token
+ ///
+ ///
+ ///
+ /// 过期时间(分钟)
+ /// 刷新token容差值,秒做单位
+ ///
+ 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();
+
+ // 处理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("f").Equals(tokenParagraphs[0])) return default;
+ if (!refreshTokenObj.GetPayloadValue("e").Equals(tokenParagraphs[2])) return default;
+ if (!tokenParagraphs[1].Substring(refreshTokenObj.GetPayloadValue("s"), refreshTokenObj.GetPayloadValue("l"))
+ .Equals(refreshTokenObj.GetPayloadValue("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(JwtRegisteredClaimNames.Exp))
+ });
+ }
+
+ return Encrypt(payload, expiredTime);
+ }
+
+ ///
+ /// 自动刷新 Token 信息
+ ///
+ ///
+ ///
+ /// 新 Token 过期时间(分钟)
+ /// 新刷新 Token 有效期(分钟)
+ ///
+ ///
+ ///
+ 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() != 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;
+ }
+
+ ///
+ /// 验证 Token
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 验证 Token
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 读取 Token,不含验证
+ ///
+ ///
+ ///
+ public static JsonWebToken ReadJwtToken(string accessToken)
+ {
+ var tokenHandler = new JsonWebTokenHandler();
+ if (tokenHandler.CanReadToken(accessToken))
+ {
+ return tokenHandler.ReadJsonWebToken(accessToken);
+ }
+
+ return default;
+ }
+
+ ///
+ /// 获取 JWT Bearer Token
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 获取 JWT 配置
+ ///
+ ///
+ public static JWTSettingsOptions GetJWTSettings()
+ {
+ return FrameworkApp.GetMethod("GetOptions").MakeGenericMethod(typeof(JWTSettingsOptions)).Invoke(null, new object[] {null}) as JWTSettingsOptions ??
+ SetDefaultJwtSettings(new JWTSettingsOptions());
+ }
+
+ ///
+ /// 生成Token验证参数
+ ///
+ ///
+ ///
+ 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),
+ };
+ }
+
+ ///
+ /// 组合 Claims 负荷
+ ///
+ ///
+ /// 过期时间,单位:分钟
+ ///
+ private static (IDictionary Payload, JWTSettingsOptions JWTSettings) CombinePayload(IDictionary 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);
+ }
+
+ ///
+ /// 设置默认 Jwt 配置
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 获取当前的 HttpContext
+ ///
+ ///
+ private static HttpContext GetCurrentHttpContext()
+ {
+ return FrameworkApp.GetProperty("HttpContext").GetValue(null) as HttpContext;
+ }
+
+ ///
+ /// 固定的 Claim 类型
+ ///
+ private static readonly string[] StationaryClaimTypes = new[]
+ {JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp, JwtRegisteredClaimNames.Iss, JwtRegisteredClaimNames.Aud};
+
+ ///
+ /// 框架 App 静态类
+ ///
+ internal static Type FrameworkApp { get; set; }
+
+ ///
+ /// 获取框架上下文
+ ///
+ ///
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTSettingsOptions.cs b/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTSettingsOptions.cs
new file mode 100644
index 0000000..40578bc
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/JWTSettingsOptions.cs
@@ -0,0 +1,34 @@
+namespace BPA.SaaS.Stock.Api.Service;
+
+public class JWTSettingsOptions
+{
+ /// 验证签发方密钥
+ public bool? ValidateIssuerSigningKey { get; set; }
+
+ /// 签发方密钥
+ public string IssuerSigningKey { get; set; }
+
+ /// 验证签发方
+ public bool? ValidateIssuer { get; set; }
+
+ /// 签发方
+ public string ValidIssuer { get; set; }
+
+ /// 验证签收方
+ public bool? ValidateAudience { get; set; }
+
+ /// 签收方
+ public string ValidAudience { get; set; }
+
+ /// 验证生存期
+ public bool? ValidateLifetime { get; set; }
+
+ /// 过期时间容错值,解决服务器端时间不同步问题(秒)
+ public long? ClockSkew { get; set; }
+
+ /// 过期时间(分钟)
+ public long? ExpiredTime { get; set; }
+
+ /// 加密算法
+ public string Algorithm { get; set; }
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/MultiClaimsDictionaryComparer.cs b/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/MultiClaimsDictionaryComparer.cs
new file mode 100644
index 0000000..ea83e57
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.Service/SimpleJWT/MultiClaimsDictionaryComparer.cs
@@ -0,0 +1,15 @@
+namespace BPA.SaaS.Stock.Api.Service.SimpleJWT;
+
+internal sealed class MultiClaimsDictionaryComparer : IEqualityComparer
+{
+ /// 设置字符串永不相等
+ ///
+ ///
+ ///
+ public bool Equals(string x, string y) => x != y;
+
+ /// 返回字符串 hashCode
+ ///
+ ///
+ public int GetHashCode(string obj) => obj.GetHashCode();
+}
\ No newline at end of file
diff --git a/src/BPA.SaaS.Stock.Api.WebApi/Controllers/Auth/AuthController.cs b/src/BPA.SaaS.Stock.Api.WebApi/Controllers/Auth/AuthController.cs
new file mode 100644
index 0000000..3e26454
--- /dev/null
+++ b/src/BPA.SaaS.Stock.Api.WebApi/Controllers/Auth/AuthController.cs
@@ -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;
+ }
+
+ ///
+ /// 获取当前登录用户信息
+ ///
+ ///
+ Task> GetLoginUserAsync(string logintype) => _authService.GetLoginUserAsync(logintype);
+
+ ///
+ /// 用户名密码登录
+ ///
+ /// 1平台用户登录,0加盟商登录
+ ///
+ ///
+ Task> LoginAsync(string LoginType, LoginInput input) => _authService.LoginAsync(LoginType, input);
+
+ ///
+ /// 退出
+ ///
+ ///
+ BaseResult LogoutAsync() => _authService.LogoutAsync();
+}
\ No newline at end of file
diff --git a/src/SQLScript/20220418.sql b/src/SQLScript/20220418.sql
new file mode 100644
index 0000000..09294d9
--- /dev/null
+++ b/src/SQLScript/20220418.sql
@@ -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