使用Remoting架构模型

This commit is contained in:
大石头 2024-06-24 13:54:50 +08:00
parent 15717165a7
commit 28225811ec
10 changed files with 110 additions and 138 deletions

View File

@ -32,7 +32,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NewLife.Stardust" Version="2.9.2024.422-beta0942" />
<PackageReference Include="NewLife.Stardust" Version="2.9.2024.620-beta1617" />
</ItemGroup>
<ItemGroup>

View File

@ -8,6 +8,7 @@ using NewLife.Caching;
using NewLife.Log;
using NewLife.Net;
using NewLife.Remoting;
using NewLife.Remoting.Models;
using NewLife.Threading;
namespace AntJob.Server;
@ -95,7 +96,7 @@ class AntService : IApi, IActionFilter
[Api(nameof(Login))]
public LoginResponse Login(LoginModel model)
{
if (model.User.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.User));
if (model.Code.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.Code));
var (app, rs) = _appService.Login(model, _Net.Remote.Host);

View File

@ -5,6 +5,7 @@ using AntJob.Models;
using NewLife;
using NewLife.Log;
using NewLife.Remoting;
using NewLife.Remoting.Models;
using NewLife.Security;
using NewLife.Web;
@ -22,29 +23,29 @@ public class AppService
/// <returns></returns>
public (App, LoginResponse) Login(LoginModel model, String ip)
{
if (model.User.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.User));
if (model.Code.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.Code));
_log.Info("[{0}]从[{1}]登录[{2}@{3}]", model.User, ip, model.Machine, model.ProcessId);
_log.Info("[{0}]从[{1}]登录[{2}@{3}]", model.Code, ip, model.Machine, model.ProcessId);
// 找应用
var autoReg = false;
var app = App.FindByName(model.User);
if (app == null || app.Secret.MD5() != model.Pass)
var app = App.FindByName(model.Code);
if (app == null || app.Secret.MD5() != model.Secret)
{
app = CheckApp(app, model.User, model.Pass, ip);
if (app == null) throw new ArgumentOutOfRangeException(nameof(model.User));
app = CheckApp(app, model.Code, model.Secret, ip);
if (app == null) throw new ArgumentOutOfRangeException(nameof(model.Code));
autoReg = true;
}
if (app == null) throw new Exception($"应用[{model.User}]不存在!");
if (app == null) throw new Exception($"应用[{model.Code}]不存在!");
if (!app.Enable) throw new Exception("已禁用!");
// 核对密码
if (!autoReg && !app.Secret.IsNullOrEmpty())
{
var pass2 = app.Secret.MD5();
if (model.Pass != pass2) throw new Exception("密码错误!");
if (model.Secret != pass2) throw new Exception("密码错误!");
}
// 版本和编译时间
@ -60,9 +61,9 @@ public class AppService
online.CompileTime = model.Compile;
online.Save();
WriteHistory(app, autoReg ? "注册" : "登录", true, $"[{model.User}/{model.Pass}]在[{model.Machine}@{model.ProcessId}]登录[{app}]成功");
WriteHistory(app, autoReg ? "注册" : "登录", true, $"[{model.Code}/{model.Secret}]在[{model.Machine}@{model.ProcessId}]登录[{app}]成功");
var rs = new LoginResponse { Name = app.Name, DisplayName = app.DisplayName };
var rs = new LoginResponse { Name = app.Name };
if (autoReg) rs.Secret = app.Secret;
return (app, rs);

View File

@ -14,6 +14,7 @@ using NewLife.Caching;
using NewLife.Cube;
using NewLife.Log;
using NewLife.Remoting;
using NewLife.Remoting.Models;
using NewLife.Serialization;
using NewLife.Web;
using IActionFilter = Microsoft.AspNetCore.Mvc.Filters.IActionFilter;
@ -101,7 +102,7 @@ public class AntJobController : ControllerBase, IActionFilter
[HttpPost(nameof(Login))]
public LoginResponse Login(LoginModel model)
{
if (model.User.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.User));
if (model.Code.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.Code));
var (app, rs) = _appService.Login(model, UserHost);
@ -123,7 +124,7 @@ public class AntJobController : ControllerBase, IActionFilter
// 密码模式
if (model.grant_type == "password")
{
var (app, rs) = _appService.Login(new LoginModel { User = model.UserName, Pass = model.Password }, ip);
var (app, rs) = _appService.Login(new LoginModel { Code = model.UserName, Secret = model.Password }, ip);
var tokenModel = _appService.IssueToken(app.Name, set);

View File

@ -45,9 +45,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NewLife.Core" Version="10.10.2024.601" />
<PackageReference Include="NewLife.Remoting" Version="2.8.2024.419-beta0703" />
<PackageReference Include="NewLife.Stardust" Version="2.9.2024.422-beta0942" />
<PackageReference Include="NewLife.Core" Version="10.10.2024.620-beta1127" />
<PackageReference Include="NewLife.Remoting" Version="3.0.2024.624-beta0109" />
<PackageReference Include="NewLife.Stardust" Version="2.9.2024.620-beta1617" />
</ItemGroup>
<ItemGroup>

View File

@ -2,12 +2,13 @@
using System.Reflection;
using NewLife;
using NewLife.Configuration;
using NewLife.Remoting.Clients;
namespace AntJob;
/// <summary>蚂蚁配置。主要用于网络型调度系统</summary>
[Config("Ant")]
public class AntSetting : Config<AntSetting>
public class AntSetting : Config<AntSetting>, IClientSetting
{
#region
/// <summary>调试开关。默认false</summary>
@ -25,6 +26,8 @@ public class AntSetting : Config<AntSetting>
/// <summary>应用密钥。</summary>
[Description("应用密钥。")]
public String Secret { get; set; }
String IClientSetting.Code { get => AppID; set => AppID = value; }
#endregion
#region

View File

@ -1,13 +1,15 @@
namespace AntJob.Models;
using NewLife.Remoting.Models;
namespace AntJob.Models;
/// <summary>登录模型</summary>
public class LoginModel
public class LoginModel : LoginRequest
{
/// <summary>用户名</summary>
public String User { get; set; }
///// <summary>用户名</summary>
//public String User { get; set; }
/// <summary>用户名</summary>
public String Pass { get; set; }
///// <summary>用户名</summary>
//public String Pass { get; set; }
/// <summary>显示名</summary>
public String DisplayName { get; set; }
@ -18,22 +20,22 @@ public class LoginModel
/// <summary>进程Id</summary>
public Int32 ProcessId { get; set; }
/// <summary>版本</summary>
public String Version { get; set; }
///// <summary>版本</summary>
//public String Version { get; set; }
/// <summary>编译时间</summary>
public DateTime Compile { get; set; }
}
/// <summary>登录响应</summary>
public class LoginResponse
{
/// <summary>名称</summary>
public String Name { get; set; }
///// <summary>登录响应</summary>
//public class LoginResponse
//{
// /// <summary>名称</summary>
// public String Name { get; set; }
/// <summary>密钥。仅注册时返回</summary>
public String Secret { get; set; }
// /// <summary>密钥。仅注册时返回</summary>
// public String Secret { get; set; }
/// <summary>显示名</summary>
public String DisplayName { get; set; }
}
// /// <summary>显示名</summary>
// public String DisplayName { get; set; }
//}

View File

@ -4,124 +4,107 @@ using System.Reflection;
using AntJob.Data;
using AntJob.Models;
using NewLife;
using NewLife.Net;
using NewLife.Model;
using NewLife.Reflection;
using NewLife.Remoting;
using NewLife.Serialization;
using NewLife.Remoting.Clients;
using NewLife.Remoting.Models;
namespace AntJob.Providers;
/// <summary>蚂蚁客户端</summary>
public class AntClient : ApiClient
public class AntClient : ClientBase
{
private readonly AntSetting _setting;
#region
/// <summary>用户名</summary>
public String UserName { get; set; }
#endregion
/// <summary>密码</summary>
public String Password { get; set; }
#region
/// <summary>实例化</summary>
public AntClient() => Prefix = "Ant/";
/// <summary>是否已登录</summary>
public Boolean Logined { get; set; }
/// <summary>最后一次登录成功后的消息</summary>
public LoginResponse Info { get; private set; }
/// <summary>实例化</summary>
/// <param name="setting"></param>
public AntClient(AntSetting setting) : base(setting)
{
_setting = setting;
Prefix = _setting.Server.StartsWithIgnoreCase("http://", "https://") ? "AntJob/" : "Ant/";
}
#endregion
#region
/// <summary>实例化</summary>
public AntClient()
/// <summary>初始化</summary>
protected override void OnInit()
{
ShowError = true;
}
var provider = ServiceProvider ??= ObjectContainer.Provider;
/// <summary>实例化</summary>
/// <param name="uri"></param>
public AntClient(String uri) : this()
{
if (!uri.IsNullOrEmpty())
// 找到容器注册默认的模型实现供后续InvokeAsync时自动创建正确的模型对象
var container = ModelExtension.GetService<IObjectContainer>(provider) ?? ObjectContainer.Current;
if (container != null)
{
var ss = uri.Split(",", ";");
Servers = ss;
var u = new Uri(ss[0]);
var us = u.UserInfo.Split(":");
if (us.Length > 0) UserName = us[0];
if (us.Length > 1) Password = us[1];
container.TryAddTransient<ILoginRequest, LoginModel>();
//container.TryAddTransient<ILoginResponse, LoginResponse>();
//container.TryAddTransient<ILogoutResponse, LogoutResponse>();
//container.TryAddTransient<IPingRequest, PingInfo>();
//container.TryAddTransient<IPingResponse, PingResponse>();
//container.TryAddTransient<IUpgradeInfo, UpgradeInfo>();
}
Prefix = _setting.Server.StartsWithIgnoreCase("http://", "https://") ? "AntJob/" : "Ant/";
base.OnInit();
}
#endregion
#region
/// <summary>连接后自动登录</summary>
/// <param name="client">客户端</param>
/// <param name="force">强制登录</param>
protected override async Task<Object> OnLoginAsync(ISocketClient client, Boolean force)
/// <summary>创建登录请求</summary>
/// <returns></returns>
public override ILoginRequest BuildLoginRequest()
{
if (Logined && !force) return null;
var asmx = AssemblyX.Entry;
var title = asmx?.Asm.GetCustomAttribute<AssemblyTitleAttribute>();
var dis = asmx?.Asm.GetCustomAttribute<DisplayNameAttribute>();
var des = asmx?.Asm.GetCustomAttribute<DescriptionAttribute>();
var dname = title?.Title ?? dis?.DisplayName ?? des?.Description;
var arg = new LoginModel
var request = base.BuildLoginRequest();
if (request is LoginModel model)
{
User = UserName,
Pass = Password.IsNullOrEmpty() ? null : Password.MD5(),
DisplayName = dname,
Machine = Environment.MachineName,
ProcessId = Process.GetCurrentProcess().Id,
Version = asmx.Version,
Compile = asmx.Compile,
};
var asmx = AssemblyX.Entry;
var title = asmx?.Asm.GetCustomAttribute<AssemblyTitleAttribute>();
var dis = asmx?.Asm.GetCustomAttribute<DisplayNameAttribute>();
var des = asmx?.Asm.GetCustomAttribute<DescriptionAttribute>();
var dname = title?.Title ?? dis?.DisplayName ?? des?.Description;
var rs = await base.InvokeWithClientAsync<LoginResponse>(client, "Login", arg);
var set = AntSetting.Current;
if (set.Debug) Log?.Info("登录{0}成功!{1}", client, rs.ToJson());
// 保存下发密钥
if (!rs.Secret.IsNullOrEmpty())
{
set.Secret = rs.Secret;
set.Save();
model.DisplayName = dname;
model.Machine = Environment.MachineName;
model.ProcessId = Process.GetCurrentProcess().Id;
model.Compile = asmx.Compile;
}
Logined = true;
return Info = rs;
return request;
}
#endregion
#region
/// <summary>获取指定名称的作业</summary>
/// <returns></returns>
public IJob[] GetJobs() => Invoke<JobModel[]>(nameof(GetJobs));
public IJob[] GetJobs() => InvokeAsync<JobModel[]>(nameof(GetJobs)).Result;
/// <summary>批量添加作业</summary>
/// <param name="jobs"></param>
/// <returns></returns>
public String[] AddJobs(IJob[] jobs) => Invoke<String[]>(nameof(AddJobs), new { jobs });
public String[] AddJobs(IJob[] jobs) => InvokeAsync<String[]>(nameof(AddJobs), new { jobs }).Result;
/// <summary>申请作业任务</summary>
/// <param name="job">作业</param>
/// <param name="topic">主题</param>
/// <param name="count">要申请的任务个数</param>
/// <returns></returns>
public ITask[] Acquire(String job, String topic, Int32 count) => Invoke<TaskModel[]>(nameof(Acquire), new AcquireModel
public ITask[] Acquire(String job, String topic, Int32 count) => InvokeAsync<TaskModel[]>(nameof(Acquire), new AcquireModel
{
Job = job,
Topic = topic,
Count = count,
});
}).Result;
/// <summary>生产消息</summary>
/// <param name="model">模型</param>
/// <returns></returns>
public Int32 Produce(ProduceModel model) => Invoke<Int32>(nameof(Produce), model);
public Int32 Produce(ProduceModel model) => InvokeAsync<Int32>(nameof(Produce), model).Result;
/// <summary>报告状态(进度、成功、错误)</summary>
/// <param name="task"></param>
@ -134,7 +117,7 @@ public class AntClient : ApiClient
{
try
{
return Invoke<Boolean>(nameof(Report), task);
return InvokeAsync<Boolean>(nameof(Report), task).Result;
}
catch (Exception ex)
{
@ -147,6 +130,6 @@ public class AntClient : ApiClient
/// <summary>获取当前应用的所有在线实例</summary>
/// <returns></returns>
public IPeer[] GetPeers() => Invoke<PeerModel[]>(nameof(GetPeers));
public IPeer[] GetPeers() => InvokeAsync<PeerModel[]>(nameof(GetPeers)).Result;
#endregion
}

View File

@ -1,5 +1,4 @@
using System.Threading.Tasks;
using AntJob.Data;
using AntJob.Data;
using AntJob.Handlers;
using AntJob.Models;
using NewLife;
@ -8,21 +7,9 @@ using NewLife.Threading;
namespace AntJob.Providers;
/// <summary>网络任务提供者</summary>
public class NetworkJobProvider : JobProvider
public class NetworkJobProvider(AntSetting setting) : JobProvider
{
#region
/// <summary>调试,打开编码日志</summary>
public Boolean Debug { get; set; }
/// <summary>调度中心地址</summary>
public String Server { get; set; }
/// <summary>应用编号</summary>
public String AppId { get; set; }
/// <summary>应用密钥</summary>
public String Secret { get; set; }
/// <summary>客户端</summary>
public AntClient Ant { get; set; }
@ -37,6 +24,8 @@ public class NetworkJobProvider : JobProvider
{
base.Dispose(disposing);
Stop();
_timer.TryDispose();
_timer = null;
}
@ -46,21 +35,15 @@ public class NetworkJobProvider : JobProvider
/// <summary>开始</summary>
public override void Start()
{
var svr = Server;
WriteLog("正在连接调度中心:{0}", svr);
WriteLog("正在连接调度中心:{0}", setting.Server);
// 使用配置中心账号
var ant = new AntClient(svr)
var ant = new AntClient(setting)
{
UserName = AppId,
Password = Secret,
Tracer = Tracer,
Log = Log,
};
if (Debug) ant.EncoderLog = Log;
ant.Open();
ant.Login().Wait();
// 断开前一个连接
Ant.TryDispose();
@ -118,6 +101,8 @@ public class NetworkJobProvider : JobProvider
/// <summary>停止</summary>
public override void Stop()
{
Ant?.Logout(nameof(Stop)).Wait(1_000);
// 断开前一个连接
Ant.TryDispose();
Ant = null;
@ -274,6 +259,7 @@ public class NetworkJobProvider : JobProvider
#region
private TimerX _timer;
private void DoCheckPeer(Object state)
{
var ps = Ant?.GetPeers();

View File

@ -93,13 +93,8 @@ public class Scheduler : DisposeBase
}
else
{
var rpc = new NetworkJobProvider
{
Debug = debug,
Server = server,
AppId = appId,
Secret = secret,
};
var set = AntSetting.Current;
var rpc = new NetworkJobProvider(set);
Provider = rpc;
}