From 6cf4222e9b333337719cd073b4db01833f2157b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Thu, 25 Jul 2024 17:33:39 +0800 Subject: [PATCH] =?UTF-8?q?[feat]=E6=94=AF=E6=8C=81=E5=81=87=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E4=BB=A5=E5=8F=8A=E6=95=B0=E6=8D=AE=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewLife.Cube/Common/OAuthController.cs | 2 +- NewLife.Cube/Entity/Cube.htm | 22 + NewLife.Cube/Entity/Cube.xml | 2 + .../OAuth\351\205\215\347\275\256.Biz.cs" | 397 +++++++++--------- .../Entity/OAuth\351\205\215\347\275\256.cs" | 16 + ...47\224\250\347\263\273\347\273\237.Biz.cs" | 18 +- ...24\347\224\250\347\263\273\347\273\237.cs" | 16 + NewLife.Cube/Services/TokenService.cs | 29 +- NewLife.Cube/Web/OAuthServer.cs | 2 +- NewLife.CubeNC/Common/EntityController.cs | 95 ++++- .../Common/ReadOnlyEntityController.cs | 2 +- NewLife.CubeNC/ViewModels/LoginConfigModel.cs | 2 +- .../Views/Shared/_List_Data_Action.cshtml | 1 + 13 files changed, 365 insertions(+), 239 deletions(-) diff --git a/NewLife.Cube/Common/OAuthController.cs b/NewLife.Cube/Common/OAuthController.cs index be78f5ff..401fe8f1 100644 --- a/NewLife.Cube/Common/OAuthController.cs +++ b/NewLife.Cube/Common/OAuthController.cs @@ -55,7 +55,7 @@ public TokenModel Token([FromBody] TokenInModel model) var (jwt, ex) = _tokenService.DecodeTokenWithError(model.refresh_token, set.JwtSecret); // 验证应用 - var app = App.FindByName(jwt?.Subject); + var app = _tokenService.FindByName(jwt?.Subject); if (app == null || !app.Enable) ex ??= new ApiException(403, $"无效应用[{jwt.Subject}]"); diff --git a/NewLife.Cube/Entity/Cube.htm b/NewLife.Cube/Entity/Cube.htm index c987aa88..6bad1bc4 100644 --- a/NewLife.Cube/Entity/Cube.htm +++ b/NewLife.Cube/Entity/Cube.htm @@ -754,6 +754,17 @@

OAuth配置(OAuthConfig)

是否抓取头像并保存到本地 + + IsDeleted + 删除 + Boolean + + + + N + 是否已删除,可恢复 + + CreateUserID 创建者 @@ -1713,6 +1724,17 @@

应用系统(OAuthApp)

+ + IsDeleted + 删除 + Boolean + + + + N + 是否已删除,可恢复 + + CreateUserID 创建者 diff --git a/NewLife.Cube/Entity/Cube.xml b/NewLife.Cube/Entity/Cube.xml index 67cf28b7..ecd1289b 100644 --- a/NewLife.Cube/Entity/Cube.xml +++ b/NewLife.Cube/Entity/Cube.xml @@ -122,6 +122,7 @@ + @@ -234,6 +235,7 @@ + diff --git "a/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.Biz.cs" "b/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.Biz.cs" index 7c559816..50952d70 100644 --- "a/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.Biz.cs" +++ "b/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.Biz.cs" @@ -1,234 +1,229 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using NewLife.Cube.Web.Models; using NewLife.Data; using NewLife.Log; using NewLife.Security; using NewLife.Serialization; using XCode; -using XCode.Membership; -namespace NewLife.Cube.Entity +namespace NewLife.Cube.Entity; + +/// OAuth2.0授权类型 +public enum GrantTypes +{ + /// + /// 授权码 + /// + AuthorizationCode = 0, + + /// + /// 隐藏式 + /// + Implicit, + + /// + /// 密码式 + /// + Password, + + /// + /// 客户端凭证 + /// + ClientCredentials, +} + +/// OAuth配置。需要连接的OAuth认证方 +public partial class OAuthConfig : Entity { - /// OAuth2.0授权类型 - public enum GrantTypes + #region 对象操作 + static OAuthConfig() { - /// - /// 授权码 - /// - AuthorizationCode = 0, - - /// - /// 隐藏式 - /// - Implicit, - - /// - /// 密码式 - /// - Password, - - /// - /// 客户端凭证 - /// - ClientCredentials, + // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx + //var df = Meta.Factory.AdditionalFields; + //df.Add(nameof(CreateUserID)); + + // 过滤器 UserModule、TimeModule、IPModule + Meta.Modules.Add(); + Meta.Modules.Add(); + Meta.Modules.Add(); + + // 单对象缓存 + var sc = Meta.SingleCache; + sc.FindSlaveKeyMethod = k => Find(_.Name == k); + sc.GetSlaveKeyMethod = e => e.Name; } - /// OAuth配置。需要连接的OAuth认证方 - public partial class OAuthConfig : Entity + /// 验证并修补数据,通过抛出异常的方式提示验证失败。 + /// 是否插入 + public override void Valid(Boolean isNew) { - #region 对象操作 - static OAuthConfig() - { - // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx - //var df = Meta.Factory.AdditionalFields; - //df.Add(nameof(CreateUserID)); - - // 过滤器 UserModule、TimeModule、IPModule - Meta.Modules.Add(); - Meta.Modules.Add(); - Meta.Modules.Add(); - - // 单对象缓存 - var sc = Meta.SingleCache; - sc.FindSlaveKeyMethod = k => Find(_.Name == k); - sc.GetSlaveKeyMethod = e => e.Name; - } + // 如果没有脏数据,则不需要进行任何处理 + if (!HasDirty) return; - /// 验证并修补数据,通过抛出异常的方式提示验证失败。 - /// 是否插入 - public override void Valid(Boolean isNew) - { - // 如果没有脏数据,则不需要进行任何处理 - if (!HasDirty) return; - - // 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框 - if (Name.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Name), "名称不能为空!"); - - // 建议先调用基类方法,基类方法会做一些统一处理 - base.Valid(isNew); - - // 不要写AuthUrl默认地址,否则会影响微信登录 - if (Name.EqualIgnoreCase("NewLife")) - { - if (AuthUrl.IsNullOrEmpty()) AuthUrl = "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}"; - if (AccessUrl.IsNullOrEmpty()) AccessUrl = "access_token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}"; - } - - if (FieldMap.IsNullOrEmpty()) - FieldMap = new OAuthFieldMap().ToJson(true); - else - FieldMap = FieldMap.ToJsonEntity().ToJson(true); - } + // 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框 + if (Name.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Name), "名称不能为空!"); - /// 首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法 - [EditorBrowsable(EditorBrowsableState.Never)] - protected override void InitData() + // 建议先调用基类方法,基类方法会做一些统一处理 + base.Valid(isNew); + + // 不要写AuthUrl默认地址,否则会影响微信登录 + if (Name.EqualIgnoreCase("NewLife")) { - // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用 - if (Meta.Session.Count > 0) return; - - if (XTrace.Debug) XTrace.WriteLine("开始初始化OAuthConfig[OAuth配置]数据……"); - - //Add("NewLife", "新生命用户中心", "/Content/images/logo/NewLife.png"); - var entity = new OAuthConfig - { - Name = "NewLife", - NickName = "新生命用户中心", - Logo = "/Content/images/logo/NewLife.png", - - Server = "https://sso.newlifex.com/sso", - AppId = "NewLife.Cube", - Secret = Rand.NextString(16), - - Enable = true, - Debug = true, - Visible = true, - AutoRegister = true, - }; - entity.Insert(); - - Add("QQ", "QQ", "/Content/images/logo/QQ.png"); - Add("Github", "Github", "/Content/images/logo/Github.png"); - Add("Baidu", "百度", "/Content/images/logo/Baidu.png"); - Add("Ding", "钉钉", "/Content/images/logo/Ding.png", "snsapi_qrlogin扫码登录,snsapi_auth钉钉内免登,snsapi_login密码登录"); - Add("QyWeiXin", "企业微信", "/Content/images/logo/QyWeiXin.png"); - //Add("Weixin", "微信公众号", "/Content/images/logo/Weixin.png", "snsapi_base静默登录,snsapi_userinfo需要用户关注后授权"); - var cfg = new OAuthConfig - { - Name = "Weixin", - NickName = "微信公众号", - Logo = "/Content/images/logo/Weixin.png", - Remark = "snsapi_base静默登录,snsapi_userinfo需要用户关注后授权", - - Visible = false, - AutoRegister = true, - }; - cfg.Insert(); - - Add("OpenWeixin", "微信开放平台", "/Content/images/logo/Weixin.png", "snsapi_login用于扫码登录"); - Add("Microsoft", "微软", "/Content/images/logo/Microsoft.png"); - //Add("Weibo", "微博", "/Content/images/logo/Weibo.png"); - //Add("Taobao", "淘宝", "/Content/images/logo/Taobao.png"); - //Add("Alipay", "支付宝", "/Content/images/logo/Alipay.png"); - - if (XTrace.Debug) XTrace.WriteLine("完成初始化OAuthConfig[OAuth配置]数据!"); + if (AuthUrl.IsNullOrEmpty()) AuthUrl = "authorize?response_type={response_type}&client_id={key}&redirect_uri={redirect}&state={state}&scope={scope}"; + if (AccessUrl.IsNullOrEmpty()) AccessUrl = "access_token?grant_type=authorization_code&client_id={key}&client_secret={secret}&code={code}&state={state}&redirect_uri={redirect}"; } - /// 已重载。显示友好名称 - /// - public override String ToString() => !NickName.IsNullOrEmpty() ? NickName : Name; - #endregion + if (FieldMap.IsNullOrEmpty()) + FieldMap = new OAuthFieldMap().ToJson(true); + else + FieldMap = FieldMap.ToJsonEntity().ToJson(true); + } - #region 扩展属性 - #endregion + /// 首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法 + [EditorBrowsable(EditorBrowsableState.Never)] + protected override void InitData() + { + // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用 + if (Meta.Session.Count > 0) return; + + if (XTrace.Debug) XTrace.WriteLine("开始初始化OAuthConfig[OAuth配置]数据……"); - #region 扩展查询 - /// 根据编号查找 - /// 编号 - /// 实体对象 - public static OAuthConfig FindByID(Int32 id) + //Add("NewLife", "新生命用户中心", "/Content/images/logo/NewLife.png"); + var entity = new OAuthConfig { - if (id <= 0) return null; + Name = "NewLife", + NickName = "新生命用户中心", + Logo = "/Content/images/logo/NewLife.png", + + Server = "https://sso.newlifex.com/sso", + AppId = "NewLife.Cube", + Secret = Rand.NextString(16), + + Enable = true, + Debug = true, + Visible = true, + AutoRegister = true, + }; + entity.Insert(); + + Add("QQ", "QQ", "/Content/images/logo/QQ.png"); + Add("Github", "Github", "/Content/images/logo/Github.png"); + Add("Baidu", "百度", "/Content/images/logo/Baidu.png"); + Add("Ding", "钉钉", "/Content/images/logo/Ding.png", "snsapi_qrlogin扫码登录,snsapi_auth钉钉内免登,snsapi_login密码登录"); + Add("QyWeiXin", "企业微信", "/Content/images/logo/QyWeiXin.png"); + //Add("Weixin", "微信公众号", "/Content/images/logo/Weixin.png", "snsapi_base静默登录,snsapi_userinfo需要用户关注后授权"); + var cfg = new OAuthConfig + { + Name = "Weixin", + NickName = "微信公众号", + Logo = "/Content/images/logo/Weixin.png", + Remark = "snsapi_base静默登录,snsapi_userinfo需要用户关注后授权", + + Visible = false, + AutoRegister = true, + }; + cfg.Insert(); + + Add("OpenWeixin", "微信开放平台", "/Content/images/logo/Weixin.png", "snsapi_login用于扫码登录"); + Add("Microsoft", "微软", "/Content/images/logo/Microsoft.png"); + //Add("Weibo", "微博", "/Content/images/logo/Weibo.png"); + //Add("Taobao", "淘宝", "/Content/images/logo/Taobao.png"); + //Add("Alipay", "支付宝", "/Content/images/logo/Alipay.png"); + + if (XTrace.Debug) XTrace.WriteLine("完成初始化OAuthConfig[OAuth配置]数据!"); + } - // 实体缓存 - if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.ID == id); + /// 已重载。显示友好名称 + /// + public override String ToString() => !NickName.IsNullOrEmpty() ? NickName : Name; + #endregion - // 单对象缓存 - return Meta.SingleCache[id]; + #region 扩展属性 + #endregion - //return Find(_.ID == id); - } + #region 扩展查询 + /// 根据编号查找 + /// 编号 + /// 实体对象 + public static OAuthConfig FindByID(Int32 id) + { + if (id <= 0) return null; - /// 根据名称查找 - /// 名称 - /// 实体对象 - public static OAuthConfig FindByName(String name) - { - // 实体缓存 - if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Name.EqualIgnoreCase(name)); + // 实体缓存 + if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.ID == id); - // 单对象缓存 - //return Meta.SingleCache.GetItemWithSlaveKey(name) as OAuthConfig; + // 单对象缓存 + return Meta.SingleCache[id]; - return Find(_.Name == name); - } - #endregion - - #region 高级查询 - /// 高级查询 - /// 名称。AppID - /// 更新时间开始 - /// 更新时间结束 - /// 关键字 - /// 分页参数信息。可携带统计和数据权限扩展查询等信息 - /// 实体列表 - public static IList Search(String name, DateTime start, DateTime end, String key, PageParameter page) - { - var exp = new WhereExpression(); + //return Find(_.ID == id); + } + + /// 根据名称查找 + /// 名称 + /// 实体对象 + public static OAuthConfig FindByName(String name) + { + // 实体缓存 + if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Name.EqualIgnoreCase(name)); - if (!name.IsNullOrEmpty()) exp &= _.Name == name; - exp &= _.UpdateTime.Between(start, end); - if (!key.IsNullOrEmpty()) exp &= _.Server.Contains(key) | _.AccessServer.Contains(key) | _.AppId.Contains(key) | _.Secret.Contains(key) | _.Scope.Contains(key) | _.AppUrl.Contains(key) | _.CreateIP.Contains(key) | _.UpdateIP.Contains(key) | _.Remark.Contains(key); + // 单对象缓存 + //return Meta.SingleCache.GetItemWithSlaveKey(name) as OAuthConfig; - return FindAll(exp, page); - } - #endregion - - #region 业务操作 - /// 添加配置 - /// - /// - /// - /// - /// - public static OAuthConfig Add(String name, String nickName, String logo, String remark = null) + return Find(_.Name == name); + } + #endregion + + #region 高级查询 + /// 高级查询 + /// 名称。AppID + /// 更新时间开始 + /// 更新时间结束 + /// 关键字 + /// 分页参数信息。可携带统计和数据权限扩展查询等信息 + /// 实体列表 + public static IList Search(String name, DateTime start, DateTime end, String key, PageParameter page) + { + var exp = new WhereExpression(); + + if (!name.IsNullOrEmpty()) exp &= _.Name == name; + exp &= _.UpdateTime.Between(start, end); + if (!key.IsNullOrEmpty()) exp &= _.Server.Contains(key) | _.AccessServer.Contains(key) | _.AppId.Contains(key) | _.Secret.Contains(key) | _.Scope.Contains(key) | _.AppUrl.Contains(key) | _.CreateIP.Contains(key) | _.UpdateIP.Contains(key) | _.Remark.Contains(key); + + return FindAll(exp, page); + } + #endregion + + #region 业务操作 + /// 添加配置 + /// + /// + /// + /// + /// + public static OAuthConfig Add(String name, String nickName, String logo, String remark = null) + { + var entity = new OAuthConfig { - var entity = new OAuthConfig - { - Name = name, - NickName = nickName, - Logo = logo, - Visible = true, - AutoRegister = true, - Remark = remark, - }; - - entity.Insert(); - - return entity; - } + Name = name, + NickName = nickName, + Logo = logo, + Visible = true, + AutoRegister = true, + Remark = remark, + }; - /// 获取全部有效设置 - /// 授权类型 - /// - public static IList GetValids(GrantTypes grantType) => FindAllWithCache().Where(e => e.Enable && e.GrantType == grantType).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList(); + entity.Insert(); - /// 获取全部有效且可见设置 - /// - public static IList GetVisibles() => FindAllWithCache().Where(e => e.Enable && e.Visible).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList(); - #endregion + return entity; } + + /// 获取全部有效设置 + /// 授权类型 + /// + public static IList GetValids(GrantTypes grantType) => FindAllWithCache().Where(e => e.Enable && !e.IsDeleted && e.GrantType == grantType).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList(); + + /// 获取全部有效且可见设置 + /// + public static IList GetVisibles() => FindAllWithCache().Where(e => e.Enable && !e.IsDeleted && e.Visible).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList(); + #endregion } \ No newline at end of file diff --git "a/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.cs" "b/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.cs" index 56a85cbe..a84dcdff 100644 --- "a/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.cs" +++ "b/NewLife.Cube/Entity/OAuth\351\205\215\347\275\256.cs" @@ -206,6 +206,14 @@ public partial class OAuthConfig [BindColumn("FetchAvatar", "抓取头像。是否抓取头像并保存到本地", "")] public Boolean FetchAvatar { get => _FetchAvatar; set { if (OnPropertyChanging("FetchAvatar", value)) { _FetchAvatar = value; OnPropertyChanged("FetchAvatar"); } } } + private Boolean _IsDeleted; + /// 删除。是否已删除,可恢复 + [DisplayName("删除")] + [Description("删除。是否已删除,可恢复")] + [DataObjectField(false, false, false, 0)] + [BindColumn("IsDeleted", "删除。是否已删除,可恢复", "")] + public Boolean IsDeleted { get => _IsDeleted; set { if (OnPropertyChanging("IsDeleted", value)) { _IsDeleted = value; OnPropertyChanged("IsDeleted"); } } } + private Int32 _CreateUserID; /// 创建者 [Category("扩展")] @@ -301,6 +309,7 @@ public override Object this[String name] "SecurityKey" => _SecurityKey, "FieldMap" => _FieldMap, "FetchAvatar" => _FetchAvatar, + "IsDeleted" => _IsDeleted, "CreateUserID" => _CreateUserID, "CreateTime" => _CreateTime, "CreateIP" => _CreateIP, @@ -337,6 +346,7 @@ public override Object this[String name] case "SecurityKey": _SecurityKey = Convert.ToString(value); break; case "FieldMap": _FieldMap = Convert.ToString(value); break; case "FetchAvatar": _FetchAvatar = value.ToBoolean(); break; + case "IsDeleted": _IsDeleted = value.ToBoolean(); break; case "CreateUserID": _CreateUserID = value.ToInt(); break; case "CreateTime": _CreateTime = value.ToDateTime(); break; case "CreateIP": _CreateIP = Convert.ToString(value); break; @@ -429,6 +439,9 @@ public partial class _ /// 抓取头像。是否抓取头像并保存到本地 public static readonly Field FetchAvatar = FindByName("FetchAvatar"); + /// 删除。是否已删除,可恢复 + public static readonly Field IsDeleted = FindByName("IsDeleted"); + /// 创建者 public static readonly Field CreateUserID = FindByName("CreateUserID"); @@ -525,6 +538,9 @@ public partial class __ /// 抓取头像。是否抓取头像并保存到本地 public const String FetchAvatar = "FetchAvatar"; + /// 删除。是否已删除,可恢复 + public const String IsDeleted = "IsDeleted"; + /// 创建者 public const String CreateUserID = "CreateUserID"; diff --git "a/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.Biz.cs" "b/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.Biz.cs" index 80e871e2..456e4082 100644 --- "a/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.Biz.cs" +++ "b/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.Biz.cs" @@ -167,16 +167,16 @@ public Boolean ValidSource(String ip) return true; } - /// 验证应用密钥是否有效 - /// - /// - public static App Valid(String appkey) - { - var app = FindBySecret(appkey); - if (app == null || !app.Enable) throw new XException("非法授权!"); + ///// 验证应用密钥是否有效 + ///// + ///// + //public static App Valid(String appkey) + //{ + // var app = FindBySecret(appkey); + // if (app == null || !app.Enable) throw new XException("非法授权!"); - return app; - } + // return app; + //} /// 写应用历史 /// diff --git "a/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.cs" "b/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.cs" index 6814a3cc..4dad30ce 100644 --- "a/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.cs" +++ "b/NewLife.Cube/Entity/\345\272\224\347\224\250\347\263\273\347\273\237.cs" @@ -168,6 +168,14 @@ public partial class App [BindColumn("LastAuth", "最后请求", "")] public DateTime LastAuth { get => _LastAuth; set { if (OnPropertyChanging("LastAuth", value)) { _LastAuth = value; OnPropertyChanged("LastAuth"); } } } + private Boolean _IsDeleted; + /// 删除。是否已删除,可恢复 + [DisplayName("删除")] + [Description("删除。是否已删除,可恢复")] + [DataObjectField(false, false, false, 0)] + [BindColumn("IsDeleted", "删除。是否已删除,可恢复", "")] + public Boolean IsDeleted { get => _IsDeleted; set { if (OnPropertyChanging("IsDeleted", value)) { _IsDeleted = value; OnPropertyChanged("IsDeleted"); } } } + private Int32 _CreateUserID; /// 创建者 [Category("扩展")] @@ -258,6 +266,7 @@ public override Object this[String name] "Expired" => _Expired, "Auths" => _Auths, "LastAuth" => _LastAuth, + "IsDeleted" => _IsDeleted, "CreateUserID" => _CreateUserID, "CreateTime" => _CreateTime, "CreateIP" => _CreateIP, @@ -289,6 +298,7 @@ public override Object this[String name] case "Expired": _Expired = value.ToDateTime(); break; case "Auths": _Auths = value.ToInt(); break; case "LastAuth": _LastAuth = value.ToDateTime(); break; + case "IsDeleted": _IsDeleted = value.ToBoolean(); break; case "CreateUserID": _CreateUserID = value.ToInt(); break; case "CreateTime": _CreateTime = value.ToDateTime(); break; case "CreateIP": _CreateIP = Convert.ToString(value); break; @@ -366,6 +376,9 @@ public partial class _ /// 最后请求 public static readonly Field LastAuth = FindByName("LastAuth"); + /// 删除。是否已删除,可恢复 + public static readonly Field IsDeleted = FindByName("IsDeleted"); + /// 创建者 public static readonly Field CreateUserID = FindByName("CreateUserID"); @@ -447,6 +460,9 @@ public partial class __ /// 最后请求 public const String LastAuth = "LastAuth"; + /// 删除。是否已删除,可恢复 + public const String IsDeleted = "IsDeleted"; + /// 创建者 public const String CreateUserID = "CreateUserID"; diff --git a/NewLife.Cube/Services/TokenService.cs b/NewLife.Cube/Services/TokenService.cs index 16ef1f75..dd86c1d4 100644 --- a/NewLife.Cube/Services/TokenService.cs +++ b/NewLife.Cube/Services/TokenService.cs @@ -9,6 +9,28 @@ namespace NewLife.Cube.Services; /// 应用服务 public class TokenService { + /// 根据名称查找 + /// + /// + public App FindByName(String name) + { + var app = App.FindByName(name); + if (app == null || app.IsDeleted) return null; + + return app; + } + + /// 根据密钥查找 + /// + /// + public App FindBySecret(String appKey) + { + var app = App.FindBySecret(appKey); + if (app == null || app.IsDeleted) return null; + + return app; + } + /// 验证应用密码,不存在时新增 /// /// @@ -21,7 +43,7 @@ public App Authorize(String username, String password, Boolean autoRegister, Str //if (password.IsNullOrEmpty()) throw new ArgumentNullException(nameof(password)); // 查找应用 - var app = App.FindByName(username); + var app = FindByName(username); // 查找或创建应用,避免多线程创建冲突 app ??= App.GetOrAdd(username, App.FindByName, k => new App { @@ -45,6 +67,7 @@ public App Authorize(String username, String password, Boolean autoRegister, Str /// /// /// + /// /// public TokenModel IssueToken(String name, String secret, Int32 expire, String id = null) { @@ -134,7 +157,7 @@ public TokenModel ValidAndIssueToken(String name, String token, String secret, I if (!jwt.TryDecode(token, out var message)) throw new ApiException(403, $"非法访问[{jwt.Subject}],{message}"); // 验证应用 - var app = App.FindByName(jwt.Subject) + var app = FindByName(jwt.Subject) ?? throw new ApiException(403, $"无效应用[{jwt.Subject}]"); if (!app.Enable) throw new ApiException(403, $"已停用应用[{jwt.Subject}]"); @@ -161,7 +184,7 @@ public TokenModel ValidAndIssueToken(String name, String token, String secret, I if (!jwt.TryDecode(token, out var message)) ex = new ApiException(403, $"非法访问 {message}"); // 验证应用 - var app = App.FindByName(jwt.Subject); + var app = FindByName(jwt.Subject); if ((app == null || !app.Enable) && ex == null) ex = new ApiException(401, $"无效应用[{jwt.Subject}]"); return (app, ex); diff --git a/NewLife.Cube/Web/OAuthServer.cs b/NewLife.Cube/Web/OAuthServer.cs index 00c5db84..1f178f93 100644 --- a/NewLife.Cube/Web/OAuthServer.cs +++ b/NewLife.Cube/Web/OAuthServer.cs @@ -47,7 +47,7 @@ public virtual App Auth(String client_id, String client_secret, String ip) app.Insert(); } - if (!app.Enable) throw new XException("应用[{0}]不可用", client_id); + if (!app.Enable || app.IsDeleted) throw new XException("应用[{0}]不可用", client_id); if (app.Expired.Year > 2000 && app.Expired < DateTime.Now) throw new XException("应用[{0}]已过期", client_id); if (!ip.IsNullOrEmpty() && !app.ValidSource(ip)) throw new XException("来源地址不合法 {0}", ip); diff --git a/NewLife.CubeNC/Common/EntityController.cs b/NewLife.CubeNC/Common/EntityController.cs index 23e5f92f..ca1a47ee 100644 --- a/NewLife.CubeNC/Common/EntityController.cs +++ b/NewLife.CubeNC/Common/EntityController.cs @@ -12,6 +12,7 @@ using NewLife.Serialization; using NewLife.Web; using XCode; +using XCode.Configuration; using XCode.Membership; namespace NewLife.Cube; @@ -44,19 +45,34 @@ public virtual ActionResult Delete(String id) { var url = Request.GetReferer(); + var act = "删除"; var entity = FindData(id); var rs = false; var err = ""; try { - if (Valid(entity, DataObjectMethodType.Delete, true)) + // 假删除与还原 + var fi = GetDeleteField(); + if (fi != null) { - OnDelete(entity); + var restore = GetRequest("restore").ToBoolean(); + entity.SetItem(fi.Name, !restore); + if (restore) act = "恢复"; - rs = true; + if (Valid(entity, DataObjectMethodType.Update, true)) + OnUpdate(entity); + else + err = "验证失败"; } else - err = "验证失败"; + { + if (Valid(entity, DataObjectMethodType.Delete, true)) + OnDelete(entity); + else + err = "验证失败"; + } + + rs = true; } catch (Exception ex) { @@ -65,19 +81,21 @@ public virtual ActionResult Delete(String id) //if (LogOnChange) LogProvider.Provider.WriteLog("Delete", entity, err); if (Request.IsAjaxRequest()) - return JsonRefresh("删除失败!" + err); + return JsonRefresh($"{act}失败!{err}"); throw; } if (Request.IsAjaxRequest()) - return JsonRefresh(rs ? "删除成功!" : "删除失败!" + err); + return JsonRefresh(rs ? $"{act}成功!" : $"{act}失败!{err}"); else if (!url.IsNullOrEmpty()) return Redirect(url); else return RedirectToAction("Index"); } + private static FieldItem GetDeleteField() => Factory.Fields.FirstOrDefault(e => e.Name.EqualIgnoreCase("Deleted", "IsDelete", "IsDeleted") && e.Type == typeof(Boolean)); + /// 表单,添加/修改 /// [EntityAuthorize(PermissionFlags.Insert)] @@ -639,10 +657,14 @@ public virtual ActionResult SetEnable(Int64 id = 0, Boolean enable = true) [DisplayName("删除选中")] public virtual ActionResult DeleteSelect() { - var count = 0; + var total = 0; + var success = 0; var keys = SelectKeys; if (keys != null && keys.Length > 0) { + // 假删除 + var fi = GetDeleteField(); + using var tran = Entity.Meta.CreateTrans(); var list = new List(); foreach (var item in keys) @@ -651,15 +673,28 @@ public virtual ActionResult DeleteSelect() if (entity != null) { // 验证数据权限 - if (Valid(entity, DataObjectMethodType.Delete, true)) list.Add(entity); - - count++; + if (fi != null) + { + entity.SetItem(fi.Name, true); + if (Valid(entity, DataObjectMethodType.Update, true)) list.Add(entity); + } + else + { + if (Valid(entity, DataObjectMethodType.Delete, true)) list.Add(entity); + } } } - list.Delete(); + + total = list.Count; + if (fi != null) + success = list.Update(); + else + success = list.Delete(); + tran.Commit(); } - return JsonRefresh($"共删除{count}行数据"); + + return JsonRefresh($"共删除{total}行数据,成功{success}行"); } /// 删除全部 @@ -670,7 +705,11 @@ public virtual ActionResult DeleteAll() { var url = Request.GetReferer(); - var count = 0; + // 假删除 + var fi = GetDeleteField(); + + var total = 0; + var success = 0; var p = Session[CacheKey] as Pager; p = new Pager(p); if (p != null) @@ -683,25 +722,37 @@ public virtual ActionResult DeleteAll() // 不要查记录数 p.RetrieveTotalCount = false; - var list = SearchData(p).ToList(); - if (list.Count == 0) break; + var data = SearchData(p).ToList(); + if (data.Count == 0) break; + + total += data.Count; - count += list.Count; - //list.Delete(); using var tran = Entity.Meta.CreateTrans(); - var list2 = new List(); - foreach (var entity in list) + var list = new List(); + foreach (var entity in data) { // 验证数据权限 - if (Valid(entity, DataObjectMethodType.Delete, true)) list2.Add(entity); + if (fi != null) + { + entity.SetItem(fi.Name, true); + if (Valid(entity, DataObjectMethodType.Update, true)) list.Add(entity); + } + else + { + if (Valid(entity, DataObjectMethodType.Delete, true)) list.Add(entity); + } } - list2.Delete(); + + if (fi != null) + success += list.Update(); + else + success += list.Delete(); tran.Commit(); } } if (Request.IsAjaxRequest()) - return JsonRefresh($"共删除{count}行数据"); + return JsonRefresh($"共删除{total}行数据,成功{success}行"); else if (!url.IsNullOrEmpty()) return Redirect(url); else diff --git a/NewLife.CubeNC/Common/ReadOnlyEntityController.cs b/NewLife.CubeNC/Common/ReadOnlyEntityController.cs index 64cbadaf..ec17cca8 100644 --- a/NewLife.CubeNC/Common/ReadOnlyEntityController.cs +++ b/NewLife.CubeNC/Common/ReadOnlyEntityController.cs @@ -605,7 +605,7 @@ protected virtual String ValidToken(String token) var app = App.FindBySecret(token); if (app != null) { - if (!app.Enable) throw new XException("非法授权!"); + if (!app.Enable || app.IsDeleted) throw new XException("非法授权!"); return app?.ToString(); } diff --git a/NewLife.CubeNC/ViewModels/LoginConfigModel.cs b/NewLife.CubeNC/ViewModels/LoginConfigModel.cs index 0ecc4fc2..231cc32d 100644 --- a/NewLife.CubeNC/ViewModels/LoginConfigModel.cs +++ b/NewLife.CubeNC/ViewModels/LoginConfigModel.cs @@ -41,7 +41,7 @@ public class LoginConfigModel /// 提供者 /// public List Providers => - OAuthConfig.FindAllWithCache().Where(w=>w.Enable).Select(s => + OAuthConfig.GetVisibles().Select(s => { var m = new OAuthConfigModel(); m.Copy(s); diff --git a/NewLife.CubeNC/Views/Shared/_List_Data_Action.cshtml b/NewLife.CubeNC/Views/Shared/_List_Data_Action.cshtml index 6518bf21..0cdac542 100644 --- a/NewLife.CubeNC/Views/Shared/_List_Data_Action.cshtml +++ b/NewLife.CubeNC/Views/Shared/_List_Data_Action.cshtml @@ -31,6 +31,7 @@ else if (this.Has(PermissionFlags.Detail)) var fi = (fact == null || fact.Fields == null) ? null : fact.Fields.FirstOrDefault(e => e.Name.EqualIgnoreCase("Deleted", "IsDelete", "IsDeleted")); if (fi != null && fi.Type == typeof(Boolean) && (Boolean)entity[fi.Name]) { + rv["restore"] = 1; 恢复 }