@@ -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 | |||
@@ -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; } | |||
@@ -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> | |||
/// 数据范围(机构)信息 | |||
@@ -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, | |||
} | |||
} |
@@ -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;*/ | |||
} |
@@ -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"; | |||
// } | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
namespace BPA.SaaS.Stock.Api.IRepository.Auth; | |||
public interface IAuthRepository | |||
{ | |||
} |
@@ -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> |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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) | |||
{ | |||
} | |||
} |
@@ -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.. | |||
@@ -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 => | |||
@@ -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 | |||
} | |||
} |
@@ -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> |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; } | |||
} |
@@ -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(); | |||
} |
@@ -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(); | |||
} |
@@ -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 |