[feat]支持假删除以及数据恢复
This commit is contained in:
parent
ee3324af21
commit
6cf4222e9b
|
@ -55,7 +55,7 @@ public class OAuthController : ControllerBase
|
|||
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}]");
|
||||
|
||||
|
|
|
@ -754,6 +754,17 @@
|
|||
<td>是否抓取头像并保存到本地</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>IsDeleted</td>
|
||||
<td>删除</td>
|
||||
<td>Boolean</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>N</td>
|
||||
<td>是否已删除,可恢复</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>CreateUserID</td>
|
||||
<td>创建者</td>
|
||||
|
@ -1713,6 +1724,17 @@
|
|||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>IsDeleted</td>
|
||||
<td>删除</td>
|
||||
<td>Boolean</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>N</td>
|
||||
<td>是否已删除,可恢复</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>CreateUserID</td>
|
||||
<td>创建者</td>
|
||||
|
|
|
@ -122,6 +122,7 @@
|
|||
<Column Name="SecurityKey" DataType="String" Length="500" Description="安全密钥。公钥,用于RSA加密用户密码,在通信链路上保护用户密码安全,密钥前面可以增加keyName,形成keyName$keyValue,用于向服务端指示所使用的密钥标识,方便未来更换密钥。" />
|
||||
<Column Name="FieldMap" DataType="String" Length="500" Description="字段映射。SSO用户字段如何映射到OAuthClient内部属性" />
|
||||
<Column Name="FetchAvatar" DataType="Boolean" Description="抓取头像。是否抓取头像并保存到本地" />
|
||||
<Column Name="IsDeleted" DataType="Boolean" Description="删除。是否已删除,可恢复" />
|
||||
<Column Name="CreateUserID" DataType="Int32" Description="创建者" Category="扩展" />
|
||||
<Column Name="CreateTime" DataType="DateTime" Description="创建时间" Category="扩展" />
|
||||
<Column Name="CreateIP" DataType="String" Description="创建地址" Category="扩展" />
|
||||
|
@ -234,6 +235,7 @@
|
|||
<Column Name="Expired" DataType="DateTime" Description="过期时间。空表示永不过期" />
|
||||
<Column Name="Auths" DataType="Int32" Description="次数" />
|
||||
<Column Name="LastAuth" DataType="DateTime" Description="最后请求" />
|
||||
<Column Name="IsDeleted" DataType="Boolean" Description="删除。是否已删除,可恢复" />
|
||||
<Column Name="CreateUserID" DataType="Int32" Description="创建者" Category="扩展" />
|
||||
<Column Name="CreateTime" DataType="DateTime" Description="创建时间" Category="扩展" />
|
||||
<Column Name="CreateIP" DataType="String" Description="创建地址" Category="扩展" />
|
||||
|
|
|
@ -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;
|
||||
|
||||
/// <summary>OAuth2.0授权类型</summary>
|
||||
public enum GrantTypes
|
||||
{
|
||||
/// <summary>OAuth2.0授权类型</summary>
|
||||
public enum GrantTypes
|
||||
/// <summary>
|
||||
/// 授权码
|
||||
/// </summary>
|
||||
AuthorizationCode = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏式
|
||||
/// </summary>
|
||||
Implicit,
|
||||
|
||||
/// <summary>
|
||||
/// 密码式
|
||||
/// </summary>
|
||||
Password,
|
||||
|
||||
/// <summary>
|
||||
/// 客户端凭证
|
||||
/// </summary>
|
||||
ClientCredentials,
|
||||
}
|
||||
|
||||
/// <summary>OAuth配置。需要连接的OAuth认证方</summary>
|
||||
public partial class OAuthConfig : Entity<OAuthConfig>
|
||||
{
|
||||
#region 对象操作
|
||||
static OAuthConfig()
|
||||
{
|
||||
/// <summary>
|
||||
/// 授权码
|
||||
/// </summary>
|
||||
AuthorizationCode = 0,
|
||||
// 累加字段,生成 Update xx Set Count=Count+1234 Where xxx
|
||||
//var df = Meta.Factory.AdditionalFields;
|
||||
//df.Add(nameof(CreateUserID));
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏式
|
||||
/// </summary>
|
||||
Implicit,
|
||||
// 过滤器 UserModule、TimeModule、IPModule
|
||||
Meta.Modules.Add<UserModule>();
|
||||
Meta.Modules.Add<TimeModule>();
|
||||
Meta.Modules.Add<IPModule>();
|
||||
|
||||
/// <summary>
|
||||
/// 密码式
|
||||
/// </summary>
|
||||
Password,
|
||||
|
||||
/// <summary>
|
||||
/// 客户端凭证
|
||||
/// </summary>
|
||||
ClientCredentials,
|
||||
// 单对象缓存
|
||||
var sc = Meta.SingleCache;
|
||||
sc.FindSlaveKeyMethod = k => Find(_.Name == k);
|
||||
sc.GetSlaveKeyMethod = e => e.Name;
|
||||
}
|
||||
|
||||
/// <summary>OAuth配置。需要连接的OAuth认证方</summary>
|
||||
public partial class OAuthConfig : Entity<OAuthConfig>
|
||||
/// <summary>验证并修补数据,通过抛出异常的方式提示验证失败。</summary>
|
||||
/// <param name="isNew">是否插入</param>
|
||||
public override void Valid(Boolean isNew)
|
||||
{
|
||||
#region 对象操作
|
||||
static OAuthConfig()
|
||||
// 如果没有脏数据,则不需要进行任何处理
|
||||
if (!HasDirty) return;
|
||||
|
||||
// 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框
|
||||
if (Name.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Name), "名称不能为空!");
|
||||
|
||||
// 建议先调用基类方法,基类方法会做一些统一处理
|
||||
base.Valid(isNew);
|
||||
|
||||
// 不要写AuthUrl默认地址,否则会影响微信登录
|
||||
if (Name.EqualIgnoreCase("NewLife"))
|
||||
{
|
||||
// 累加字段,生成 Update xx Set Count=Count+1234 Where xxx
|
||||
//var df = Meta.Factory.AdditionalFields;
|
||||
//df.Add(nameof(CreateUserID));
|
||||
|
||||
// 过滤器 UserModule、TimeModule、IPModule
|
||||
Meta.Modules.Add<UserModule>();
|
||||
Meta.Modules.Add<TimeModule>();
|
||||
Meta.Modules.Add<IPModule>();
|
||||
|
||||
// 单对象缓存
|
||||
var sc = Meta.SingleCache;
|
||||
sc.FindSlaveKeyMethod = k => Find(_.Name == k);
|
||||
sc.GetSlaveKeyMethod = e => e.Name;
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>验证并修补数据,通过抛出异常的方式提示验证失败。</summary>
|
||||
/// <param name="isNew">是否插入</param>
|
||||
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<OAuthFieldMap>().ToJson(true);
|
||||
}
|
||||
|
||||
/// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected override void InitData()
|
||||
{
|
||||
// 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配置]数据!");
|
||||
}
|
||||
|
||||
/// <summary>已重载。显示友好名称</summary>
|
||||
/// <returns></returns>
|
||||
public override String ToString() => !NickName.IsNullOrEmpty() ? NickName : Name;
|
||||
#endregion
|
||||
|
||||
#region 扩展属性
|
||||
#endregion
|
||||
|
||||
#region 扩展查询
|
||||
/// <summary>根据编号查找</summary>
|
||||
/// <param name="id">编号</param>
|
||||
/// <returns>实体对象</returns>
|
||||
public static OAuthConfig FindByID(Int32 id)
|
||||
{
|
||||
if (id <= 0) return null;
|
||||
|
||||
// 实体缓存
|
||||
if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.ID == id);
|
||||
|
||||
// 单对象缓存
|
||||
return Meta.SingleCache[id];
|
||||
|
||||
//return Find(_.ID == id);
|
||||
}
|
||||
|
||||
/// <summary>根据名称查找</summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>实体对象</returns>
|
||||
public static OAuthConfig FindByName(String name)
|
||||
{
|
||||
// 实体缓存
|
||||
if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Name.EqualIgnoreCase(name));
|
||||
|
||||
// 单对象缓存
|
||||
//return Meta.SingleCache.GetItemWithSlaveKey(name) as OAuthConfig;
|
||||
|
||||
return Find(_.Name == name);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 高级查询
|
||||
/// <summary>高级查询</summary>
|
||||
/// <param name="name">名称。AppID</param>
|
||||
/// <param name="start">更新时间开始</param>
|
||||
/// <param name="end">更新时间结束</param>
|
||||
/// <param name="key">关键字</param>
|
||||
/// <param name="page">分页参数信息。可携带统计和数据权限扩展查询等信息</param>
|
||||
/// <returns>实体列表</returns>
|
||||
public static IList<OAuthConfig> 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 业务操作
|
||||
/// <summary>添加配置</summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="nickName"></param>
|
||||
/// <param name="logo"></param>
|
||||
/// <param name="remark"></param>
|
||||
/// <returns></returns>
|
||||
public static OAuthConfig Add(String name, String nickName, String logo, String remark = null)
|
||||
{
|
||||
var entity = new OAuthConfig
|
||||
{
|
||||
Name = name,
|
||||
NickName = nickName,
|
||||
Logo = logo,
|
||||
Visible = true,
|
||||
AutoRegister = true,
|
||||
Remark = remark,
|
||||
};
|
||||
|
||||
entity.Insert();
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>获取全部有效设置</summary>
|
||||
/// <param name="grantType">授权类型</param>
|
||||
/// <returns></returns>
|
||||
public static IList<OAuthConfig> GetValids(GrantTypes grantType) => FindAllWithCache().Where(e => e.Enable && e.GrantType == grantType).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList();
|
||||
|
||||
/// <summary>获取全部有效且可见设置</summary>
|
||||
/// <returns></returns>
|
||||
public static IList<OAuthConfig> GetVisibles() => FindAllWithCache().Where(e => e.Enable && e.Visible).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList();
|
||||
#endregion
|
||||
if (FieldMap.IsNullOrEmpty())
|
||||
FieldMap = new OAuthFieldMap().ToJson(true);
|
||||
else
|
||||
FieldMap = FieldMap.ToJsonEntity<OAuthFieldMap>().ToJson(true);
|
||||
}
|
||||
|
||||
/// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected override void InitData()
|
||||
{
|
||||
// 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配置]数据!");
|
||||
}
|
||||
|
||||
/// <summary>已重载。显示友好名称</summary>
|
||||
/// <returns></returns>
|
||||
public override String ToString() => !NickName.IsNullOrEmpty() ? NickName : Name;
|
||||
#endregion
|
||||
|
||||
#region 扩展属性
|
||||
#endregion
|
||||
|
||||
#region 扩展查询
|
||||
/// <summary>根据编号查找</summary>
|
||||
/// <param name="id">编号</param>
|
||||
/// <returns>实体对象</returns>
|
||||
public static OAuthConfig FindByID(Int32 id)
|
||||
{
|
||||
if (id <= 0) return null;
|
||||
|
||||
// 实体缓存
|
||||
if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.ID == id);
|
||||
|
||||
// 单对象缓存
|
||||
return Meta.SingleCache[id];
|
||||
|
||||
//return Find(_.ID == id);
|
||||
}
|
||||
|
||||
/// <summary>根据名称查找</summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>实体对象</returns>
|
||||
public static OAuthConfig FindByName(String name)
|
||||
{
|
||||
// 实体缓存
|
||||
if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Name.EqualIgnoreCase(name));
|
||||
|
||||
// 单对象缓存
|
||||
//return Meta.SingleCache.GetItemWithSlaveKey(name) as OAuthConfig;
|
||||
|
||||
return Find(_.Name == name);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 高级查询
|
||||
/// <summary>高级查询</summary>
|
||||
/// <param name="name">名称。AppID</param>
|
||||
/// <param name="start">更新时间开始</param>
|
||||
/// <param name="end">更新时间结束</param>
|
||||
/// <param name="key">关键字</param>
|
||||
/// <param name="page">分页参数信息。可携带统计和数据权限扩展查询等信息</param>
|
||||
/// <returns>实体列表</returns>
|
||||
public static IList<OAuthConfig> 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 业务操作
|
||||
/// <summary>添加配置</summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="nickName"></param>
|
||||
/// <param name="logo"></param>
|
||||
/// <param name="remark"></param>
|
||||
/// <returns></returns>
|
||||
public static OAuthConfig Add(String name, String nickName, String logo, String remark = null)
|
||||
{
|
||||
var entity = new OAuthConfig
|
||||
{
|
||||
Name = name,
|
||||
NickName = nickName,
|
||||
Logo = logo,
|
||||
Visible = true,
|
||||
AutoRegister = true,
|
||||
Remark = remark,
|
||||
};
|
||||
|
||||
entity.Insert();
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>获取全部有效设置</summary>
|
||||
/// <param name="grantType">授权类型</param>
|
||||
/// <returns></returns>
|
||||
public static IList<OAuthConfig> GetValids(GrantTypes grantType) => FindAllWithCache().Where(e => e.Enable && !e.IsDeleted && e.GrantType == grantType).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList();
|
||||
|
||||
/// <summary>获取全部有效且可见设置</summary>
|
||||
/// <returns></returns>
|
||||
public static IList<OAuthConfig> GetVisibles() => FindAllWithCache().Where(e => e.Enable && !e.IsDeleted && e.Visible).OrderByDescending(e => e.Sort).ThenByDescending(e => e.ID).ToList();
|
||||
#endregion
|
||||
}
|
|
@ -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;
|
||||
/// <summary>删除。是否已删除,可恢复</summary>
|
||||
[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;
|
||||
/// <summary>创建者</summary>
|
||||
[Category("扩展")]
|
||||
|
@ -301,6 +309,7 @@ public partial class OAuthConfig
|
|||
"SecurityKey" => _SecurityKey,
|
||||
"FieldMap" => _FieldMap,
|
||||
"FetchAvatar" => _FetchAvatar,
|
||||
"IsDeleted" => _IsDeleted,
|
||||
"CreateUserID" => _CreateUserID,
|
||||
"CreateTime" => _CreateTime,
|
||||
"CreateIP" => _CreateIP,
|
||||
|
@ -337,6 +346,7 @@ public partial class OAuthConfig
|
|||
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 OAuthConfig
|
|||
/// <summary>抓取头像。是否抓取头像并保存到本地</summary>
|
||||
public static readonly Field FetchAvatar = FindByName("FetchAvatar");
|
||||
|
||||
/// <summary>删除。是否已删除,可恢复</summary>
|
||||
public static readonly Field IsDeleted = FindByName("IsDeleted");
|
||||
|
||||
/// <summary>创建者</summary>
|
||||
public static readonly Field CreateUserID = FindByName("CreateUserID");
|
||||
|
||||
|
@ -525,6 +538,9 @@ public partial class OAuthConfig
|
|||
/// <summary>抓取头像。是否抓取头像并保存到本地</summary>
|
||||
public const String FetchAvatar = "FetchAvatar";
|
||||
|
||||
/// <summary>删除。是否已删除,可恢复</summary>
|
||||
public const String IsDeleted = "IsDeleted";
|
||||
|
||||
/// <summary>创建者</summary>
|
||||
public const String CreateUserID = "CreateUserID";
|
||||
|
||||
|
|
|
@ -167,16 +167,16 @@ public partial class App : Entity<App>
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>验证应用密钥是否有效</summary>
|
||||
/// <param name="appkey"></param>
|
||||
/// <returns></returns>
|
||||
public static App Valid(String appkey)
|
||||
{
|
||||
var app = FindBySecret(appkey);
|
||||
if (app == null || !app.Enable) throw new XException("非法授权!");
|
||||
///// <summary>验证应用密钥是否有效</summary>
|
||||
///// <param name="appkey"></param>
|
||||
///// <returns></returns>
|
||||
//public static App Valid(String appkey)
|
||||
//{
|
||||
// var app = FindBySecret(appkey);
|
||||
// if (app == null || !app.Enable) throw new XException("非法授权!");
|
||||
|
||||
return app;
|
||||
}
|
||||
// return app;
|
||||
//}
|
||||
|
||||
/// <summary>写应用历史</summary>
|
||||
/// <param name="action"></param>
|
||||
|
|
|
@ -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;
|
||||
/// <summary>删除。是否已删除,可恢复</summary>
|
||||
[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;
|
||||
/// <summary>创建者</summary>
|
||||
[Category("扩展")]
|
||||
|
@ -258,6 +266,7 @@ public partial class App
|
|||
"Expired" => _Expired,
|
||||
"Auths" => _Auths,
|
||||
"LastAuth" => _LastAuth,
|
||||
"IsDeleted" => _IsDeleted,
|
||||
"CreateUserID" => _CreateUserID,
|
||||
"CreateTime" => _CreateTime,
|
||||
"CreateIP" => _CreateIP,
|
||||
|
@ -289,6 +298,7 @@ public partial class App
|
|||
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 App
|
|||
/// <summary>最后请求</summary>
|
||||
public static readonly Field LastAuth = FindByName("LastAuth");
|
||||
|
||||
/// <summary>删除。是否已删除,可恢复</summary>
|
||||
public static readonly Field IsDeleted = FindByName("IsDeleted");
|
||||
|
||||
/// <summary>创建者</summary>
|
||||
public static readonly Field CreateUserID = FindByName("CreateUserID");
|
||||
|
||||
|
@ -447,6 +460,9 @@ public partial class App
|
|||
/// <summary>最后请求</summary>
|
||||
public const String LastAuth = "LastAuth";
|
||||
|
||||
/// <summary>删除。是否已删除,可恢复</summary>
|
||||
public const String IsDeleted = "IsDeleted";
|
||||
|
||||
/// <summary>创建者</summary>
|
||||
public const String CreateUserID = "CreateUserID";
|
||||
|
||||
|
|
|
@ -9,6 +9,28 @@ namespace NewLife.Cube.Services;
|
|||
/// <summary>应用服务</summary>
|
||||
public class TokenService
|
||||
{
|
||||
/// <summary>根据名称查找</summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public App FindByName(String name)
|
||||
{
|
||||
var app = App.FindByName(name);
|
||||
if (app == null || app.IsDeleted) return null;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>根据密钥查找</summary>
|
||||
/// <param name="appKey"></param>
|
||||
/// <returns></returns>
|
||||
public App FindBySecret(String appKey)
|
||||
{
|
||||
var app = App.FindBySecret(appKey);
|
||||
if (app == null || app.IsDeleted) return null;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>验证应用密码,不存在时新增</summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
|
@ -21,7 +43,7 @@ public class TokenService
|
|||
//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 class TokenService
|
|||
/// <param name="name"></param>
|
||||
/// <param name="secret"></param>
|
||||
/// <param name="expire"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public TokenModel IssueToken(String name, String secret, Int32 expire, String id = null)
|
||||
{
|
||||
|
@ -134,7 +157,7 @@ public class TokenService
|
|||
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 class TokenService
|
|||
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);
|
||||
|
|
|
@ -47,7 +47,7 @@ public class OAuthServer
|
|||
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);
|
||||
|
|
|
@ -12,6 +12,7 @@ using NewLife.Remoting;
|
|||
using NewLife.Serialization;
|
||||
using NewLife.Web;
|
||||
using XCode;
|
||||
using XCode.Configuration;
|
||||
using XCode.Membership;
|
||||
|
||||
namespace NewLife.Cube;
|
||||
|
@ -44,19 +45,34 @@ public class EntityController<TEntity, TModel> : ReadOnlyEntityController<TEntit
|
|||
{
|
||||
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 class EntityController<TEntity, TModel> : ReadOnlyEntityController<TEntit
|
|||
//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));
|
||||
|
||||
/// <summary>表单,添加/修改</summary>
|
||||
/// <returns></returns>
|
||||
[EntityAuthorize(PermissionFlags.Insert)]
|
||||
|
@ -639,10 +657,14 @@ public class EntityController<TEntity, TModel> : ReadOnlyEntityController<TEntit
|
|||
[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<TEntity>.Meta.CreateTrans();
|
||||
var list = new List<IEntity>();
|
||||
foreach (var item in keys)
|
||||
|
@ -651,15 +673,28 @@ public class EntityController<TEntity, TModel> : ReadOnlyEntityController<TEntit
|
|||
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}行");
|
||||
}
|
||||
|
||||
/// <summary>删除全部</summary>
|
||||
|
@ -670,7 +705,11 @@ public class EntityController<TEntity, TModel> : ReadOnlyEntityController<TEntit
|
|||
{
|
||||
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 class EntityController<TEntity, TModel> : ReadOnlyEntityController<TEntit
|
|||
// 不要查记录数
|
||||
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<TEntity>.Meta.CreateTrans();
|
||||
var list2 = new List<IEntity>();
|
||||
foreach (var entity in list)
|
||||
var list = new List<IEntity>();
|
||||
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
|
||||
|
|
|
@ -605,7 +605,7 @@ public class ReadOnlyEntityController<TEntity> : ControllerBaseX where TEntity :
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace NewLife.Cube.ViewModels
|
|||
/// 提供者
|
||||
/// </summary>
|
||||
public List<OAuthConfigModel> Providers =>
|
||||
OAuthConfig.FindAllWithCache().Where(w=>w.Enable).Select(s =>
|
||||
OAuthConfig.GetVisibles().Select(s =>
|
||||
{
|
||||
var m = new OAuthConfigModel();
|
||||
m.Copy(s);
|
||||
|
|
|
@ -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;
|
||||
<i class="glyphicon glyphicon-transfer" style="color: green;"></i>
|
||||
<a href="@Url.Action("Delete", rv)" data-action="action" data-confirm="确认恢复?">恢复</a>
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue