新增Anolis龙蜥操作系统
大石头 编写于 2024-05-19 18:25:03
Stardust
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using NewLife;
using NewLife.Caching;
using NewLife.Common;
using NewLife.Configuration;
using NewLife.Http;
using NewLife.Log;
using NewLife.Model;
using NewLife.Reflection;
using NewLife.Remoting;
using NewLife.Security;
using Stardust.Configs;
using Stardust.Models;
using Stardust.Monitors;
using Stardust.Registry;
using Stardust.Services;
#if NET45_OR_GREATER || NETCOREAPP || NETSTANDARD
using TaskEx = System.Threading.Tasks.Task;
#endif

namespace Stardust;

/// <summary>星尘工厂</summary>
/// <remarks>
/// 星尘代理 https://newlifex.com/blood/staragent_install
/// 监控中心 https://newlifex.com/blood/stardust_monitor
/// 配置中心 https://newlifex.com/blood/stardust_configcenter
/// </remarks>
public class StarFactory : DisposeBase
{
    #region 属性
    /// <summary>服务器地址</summary>
    public String? Server { get; set; }

    /// <summary>应用</summary>
    public String? AppId { get; set; }

    /// <summary>应用名</summary>
    public String? AppName { get; set; }

    /// <summary>应用密钥</summary>
    public String? Secret { get; set; }

    /// <summary>实例。应用可能多实例部署,ip@proccessid</summary>
    public String? ClientId { get; set; }

    ///// <summary>服务名</summary>
    //public String ServiceName { get; set; }

    /// <summary>客户端</summary>
    public IApiClient? Client => _client;

    /// <summary>应用客户端</summary>
    public AppClient? App => _client;

    /// <summary>配置信息。从配置中心返回的信息头</summary>
    public ConfigInfo? ConfigInfo => (_config as StarHttpConfigProvider)?.ConfigInfo;

    /// <summary>本地星尘代理</summary>
    public LocalStarClient? Local { get; private set; }

    private AppClient? _client;
    private TokenHttpFilter? _tokenFilter;
    #endregion

    #region 构造
    /// <summary>
    /// 实例化星尘工厂,先后读取appsettings.json、本地StarAgent、star.config
    /// </summary>
    public StarFactory() => Init();

    /// <summary>实例化星尘工厂,指定地址、应用和密钥,创建工厂</summary>
    /// <param name="server">服务端地址。为空时先后读取appsettings.json、本地StarAgent、star.config,初始值为空,不连接服务端</param>
    /// <param name="appId">应用标识。为空时读取star.config,初始值为入口程序集名称</param>
    /// <param name="secret">应用密钥。为空时读取star.config,初始值为空</param>
    /// <returns></returns>
    public StarFactory(String? server, String? appId, String? secret)
    {
        Server = server;
        AppId = appId;
        Secret = secret;

        Init();
    }

    /// <summary>销毁</summary>
    /// <param name="disposing"></param>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        _tracer.TryDispose();
        _config.TryDispose();
    }

    private void Init()
    {
        XTrace.WriteLine("正在初始化星尘……");

        Local = new LocalStarClient { Log = Log };

        // 从命令行读取参数
        var args = Environment.GetCommandLineArgs();
        if (args != null && args.Length > 0)
        {
            for (var i = 0; i < args.Length; i++)
            {
                var key = args[i].TrimStart('-');
                var p = key.IndexOf('=');
                if (p > 0)
                {
                    var value = key.Substring(p + 1);
                    key = key.Substring(0, p);
                    if (Server.IsNullOrEmpty() && key.EqualIgnoreCase("StarServer"))
                        Server = value;
                    else if (AppId.IsNullOrEmpty() && key.EqualIgnoreCase("StarAppId"))
                        AppId = value;
                    else if (Secret.IsNullOrEmpty() && key.EqualIgnoreCase("StarSecret"))
                        Secret = value;
                }
            }
        }

        // 从环境变量读取星尘地址、应用Id、密钥,方便容器化部署
        if (Server.IsNullOrEmpty()) Server = Environment.GetEnvironmentVariable("StarServer");
        if (AppId.IsNullOrEmpty()) AppId = Environment.GetEnvironmentVariable("StarAppId");
        if (Secret.IsNullOrEmpty()) Secret = Environment.GetEnvironmentVariable("StarSecret");

        // 不区分大小写识别环境变量
        foreach (DictionaryEntry item in Environment.GetEnvironmentVariables())
        {
            var key = item.Key + "";
            var value = item.Value + "";
            if (Server.IsNullOrEmpty() && key.EqualIgnoreCase("StarServer"))
                Server = value;
            else if (AppId.IsNullOrEmpty() && key.EqualIgnoreCase("StarAppId"))
                AppId = value;
            else if (Secret.IsNullOrEmpty() && key.EqualIgnoreCase("StarSecret"))
                Secret = value;
        }

        // 读取本地appsetting
        if (Server.IsNullOrEmpty() && File.Exists("appsettings.Development.json".GetFullPath()))
        {
            using var json = new JsonConfigProvider { FileName = "appsettings.Development.json" };
            json.LoadAll();

            Server = json["StarServer"];
            AppId = json["StarAppId"];
            Secret = json["StarSecret"];
        }
        if (Server.IsNullOrEmpty() && File.Exists("appsettings.json".GetFullPath()))
        {
            using var json = new JsonConfigProvider { FileName = "appsettings.json" };
            json.LoadAll();

            Server = json["StarServer"];
            AppId = json["StarAppId"];
            Secret = json["StarSecret"];
        }

        if (!Server.IsNullOrEmpty() && Local.Server.IsNullOrEmpty()) Local.Server = Server;

        var flag = false;
        var set = StarSetting.Current;

        if (AppId != "StarAgent")
        {
            // 借助本地StarAgent获取服务器地址
            var sw = Stopwatch.StartNew();
            try
            {
                //XTrace.WriteLine("正在探测本机星尘代理……");
                var inf = Local.GetInfo();
                var server = inf?.Server;
                if (!server.IsNullOrEmpty())
                {
                    if (Server.IsNullOrEmpty()) Server = server;
                    XTrace.WriteLine("星尘探测:{0} Cost={1}ms", server, sw.ElapsedMilliseconds);

                    if (set.Server.IsNullOrEmpty())
                    {
                        set.Server = server;
                        flag = true;
                    }
                }
                else
                    XTrace.WriteLine("星尘探测:StarAgent Not Found, Cost={0}ms", sw.ElapsedMilliseconds);

                if (inf != null && !inf.PluginServer.IsNullOrEmpty())
                {
                    var core = NewLife.Setting.Current;
                    if (!inf.PluginServer.EqualIgnoreCase(core.PluginServer))
                    {
                        XTrace.WriteLine("插件服务器PluginServer变更为 {0}", inf.PluginServer);
                        core.PluginServer = inf.PluginServer;
                        core.Save();
                    }
                }
            }
            catch (Exception ex)
            {
                XTrace.Log.Error("星尘探测失败!{0} Cost={1}ms", ex.Message, sw.ElapsedMilliseconds);
            }
        }

        // 如果探测不到本地应用,则使用配置
        if (Server.IsNullOrEmpty()) Server = set.Server;
        if (AppId.IsNullOrEmpty()) AppId = set.AppKey;
        if (Secret.IsNullOrEmpty()) Secret = set.Secret;

        if (flag) set.Save();

        // 生成ClientId,用于唯一标识当前实例,默认IP@pid
        try
        {
            // 从SysConfig读取系统名称,其受到命令行参数-Name和环境变量Name影响,方便单应用多部署(参数区分应用名)
            var sys = SysConfig.Current;
            if (AppId.IsNullOrEmpty()) AppId = sys.Name;
            if (AppName.IsNullOrEmpty()) AppName = sys.DisplayName;

            var executing = AssemblyX.Create(Assembly.GetExecutingAssembly());
            var asm = AssemblyX.Entry ?? executing;
            if (asm != null)
            {
                if (AppId.IsNullOrEmpty()) AppId = asm.Name;
                if (AppName.IsNullOrEmpty()) AppName = asm.Title;
            }

            ClientId = $"{NetHelper.MyIP()}@{Process.GetCurrentProcess().Id}";
        }
        catch
        {
            ClientId = Rand.NextString(8);
        }

        XTrace.WriteLine("星尘分布式服务 Server={0} AppId={1} ClientId={2}", Server, AppId, ClientId);

        Valid();

        var ioc = ObjectContainer.Current;
        ioc.AddSingleton(this);
        ioc.AddSingleton(p => Tracer ?? DefaultTracer.Instance ?? (DefaultTracer.Instance ??= new DefaultTracer()));
        //ioc.AddSingleton(p => Config);
        ioc.AddSingleton(p => Service!);

        // 替换为混合配置提供者,优先本地配置
        ioc.AddSingleton(p => GetConfig()!);

        ioc.TryAddSingleton(XTrace.Log);
        ioc.TryAddSingleton(typeof(ICacheProvider), typeof(CacheProvider));
    }

    [MemberNotNullWhen(true, nameof(_client))]
    private Boolean Valid()
    {
        //if (Server.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Server));
        //if (AppId.IsNullOrEmpty()) throw new ArgumentNullException(nameof(AppId));

        if (Server.IsNullOrEmpty() || AppId.IsNullOrEmpty()) return false;

        if (_client == null)
        {
            if (!AppId.IsNullOrEmpty()) _tokenFilter = new TokenHttpFilter
            {
                UserName = AppId,
                Password = Secret,
                ClientId = ClientId,
            };

            var client = new AppClient(Server)
            {
                Factory = this,
                AppId = AppId,
                AppName = AppName,
                ClientId = ClientId,
                NodeCode = Local?.Info?.Code,
                Filter = _tokenFilter,
                UseWebSocket = true,

                Log = Log,
            };

            //var set = StarSetting.Current;
            //if (set.Debug) client.Log = XTrace.Log;
            client.WriteInfoEvent("应用启动", $"pid={Process.GetCurrentProcess().Id}");

            _client = client;

            InitTracer();

            client.Tracer = _tracer;
            client.Start();

            // 注册StarServer环境变量,子进程共享
            Environment.SetEnvironmentVariable("StarServer", Server);
        }

        return true;
    }
    #endregion

    #region 监控中心
    private StarTracer? _tracer;
    /// <summary>监控中心</summary>
    public ITracer? Tracer
    {
        get
        {
            if (_tracer == null)
            {
                if (!Valid()) return null;

                InitTracer();
            }

            return _tracer;
        }
    }

    private void InitTracer()
    {
        if (Server.IsNullOrEmpty()) return;

        XTrace.WriteLine("星尘监控中心:采样并定期上报应用性能埋点数据,包括Api接口、Http请求、数据库操作、Redis操作等。可用于监控系统健康状态,分析分布式系统的性能瓶颈。");

        var tracer = new StarTracer(Server)
        {
            AppId = AppId,
            AppName = AppName,
            //Secret = Secret,
            ClientId = ClientId,
            Client = _client,

            Log = Log
        };

        tracer.AttachGlobal();
        _tracer = tracer;
    }
    #endregion

    #region 配置中心
    private HttpConfigProvider? _config;
    /// <summary>配置中心。务必在数据库操作和生成雪花Id之前使用激活</summary>
    /// <remarks>
    /// 文档 https://newlifex.com/blood/stardust_configcenter
    /// </remarks>
    public IConfigProvider? Config
    {
        get
        {
            if (_config == null)
            {
                if (!Valid()) return null;

                XTrace.WriteLine("星尘配置中心:提供集中配置管理能力,自动从配置中心加载配置数据,包括XCode数据库连接。配置中心同时支持分配应用实例的唯一WorkerId,确保Snowflake算法能够生成绝对唯一的雪花Id");

                var config = new StarHttpConfigProvider
                {
                    Server = Server!,
                    AppId = AppId!,
                    //Secret = Secret,
                    ClientId = ClientId,
                    Client = _client,
                };
                //if (!ClientId.IsNullOrEmpty()) config.ClientId = ClientId;
                config.Attach(_client);

                //!! 不需要默认加载,直到首次使用配置数据时才加载。因为有可能应用并不使用配置中心,仅仅是获取这个对象。避免网络不通时的报错日志
                //config.LoadAll();

                _config = config;
            }

            return _config;
        }
    }

    private IConfigProvider? _configProvider;
    /// <summary>设置本地配置提供者,该提供者将跟星尘配置结合到一起,形成复合配置提供者</summary>
    /// <param name="configProvider"></param>
    public void SetLocalConfig(IConfigProvider configProvider)
    {
        if (configProvider == null) return;

        var cfg = Config;
        if (cfg != null)
            _configProvider = new CompositeConfigProvider(configProvider, cfg);
        else
            _configProvider = configProvider;
    }

    /// <summary>获取复合配置提供者</summary>
    /// <returns></returns>
    public IConfigProvider? GetConfig() => _configProvider ?? Config;
    #endregion

    #region 注册中心
    private Boolean _initService;
    /// <summary>注册中心,服务注册与发现</summary>
    public IRegistry? Service
    {
        get
        {
            if (!_initService)
            {
                if (!Valid()) return null;

                _initService = true;
                //_appClient = _client as AppClient;

                XTrace.WriteLine("星尘注册中心:提供服务注册与发布能力");
            }

            return _client;
        }
    }

    /// <summary>为指定服务创建客户端,从星尘注册中心获取服务地址。单例,应避免频繁创建客户端</summary>
    /// <param name="serviceName"></param>
    /// <param name="tag"></param>
    /// <returns></returns>
    public IApiClient CreateForService(String serviceName, String? tag = null) => TaskEx.Run(() => CreateForServiceAsync(serviceName, tag)).Result;

    /// <summary>为指定服务创建客户端,从星尘注册中心获取服务地址。单例,应避免频繁创建客户端</summary>
    /// <param name="serviceName"></param>
    /// <param name="tag"></param>
    /// <returns></returns>
    public Task<IApiClient> CreateForServiceAsync(String serviceName, String? tag = null) => Service!.CreateForServiceAsync(serviceName, tag);

    /// <summary>发布服务</summary>
    /// <param name="serviceName">服务名</param>
    /// <param name="address">服务地址</param>
    /// <param name="tag">特性标签</param>
    /// <param name="health">健康监测接口地址</param>
    /// <returns></returns>
    public Task<PublishServiceInfo> RegisterAsync(String serviceName, String address, String? tag = null, String? health = null) => Service!.RegisterAsync(serviceName, address, tag, health);

    /// <summary>消费得到服务地址信息</summary>
    /// <param name="serviceName">服务名</param>
    /// <param name="minVersion">最小版本</param>
    /// <param name="tag">特性标签。只要包含该特性的服务提供者</param>
    /// <returns></returns>
    public Task<String[]> ResolveAddressAsync(String serviceName, String? minVersion = null, String? tag = null) => Service!.ResolveAddressAsync(serviceName, minVersion, tag);
    #endregion

    #region 其它
    /// <summary>发送节点命令。通知节点更新、安装和启停应用等</summary>
    /// <param name="nodeCode"></param>
    /// <param name="command"></param>
    /// <param name="argument"></param>
    /// <param name="expire"></param>
    /// <param name="timeout"></param>
    /// <returns></returns>
    public async Task<Int32> SendNodeCommand(String nodeCode, String command, String? argument = null, Int32 expire = 3600, Int32 timeout = 5)
    {
        if (!Valid()) return -1;

        return await _client.PostAsync<Int32>("Node/SendCommand", new CommandInModel
        {
            Code = nodeCode,
            Command = command,
            Argument = argument,
            Expire = expire,
            Timeout = timeout
        });
    }

    /// <summary>发送应用命令。通知应用刷新配置信息和服务信息等</summary>
    /// <param name="appId"></param>
    /// <param name="command"></param>
    /// <param name="argument"></param>
    /// <param name="expire"></param>
    /// <param name="timeout"></param>
    /// <returns></returns>
    public async Task<Int32> SendAppCommand(String appId, String command, String? argument = null, Int32 expire = 3600, Int32 timeout = 5)
    {
        if (!Valid()) return -1;

        return await _client.PostAsync<Int32>("App/SendCommand", new CommandInModel
        {
            Code = appId,
            Command = command,
            Argument = argument,
            Expire = expire,
            Timeout = timeout
        });
    }

    /// <summary>设置看门狗超时时间</summary>
    /// <param name="timeout">超时时间,单位秒。0表示关闭看门狗</param>
    /// <returns></returns>
    public Boolean SetWatchdog(Int32 timeout)
    {
        if (!Valid()) return false;

        var client = _client;
        if (client == null) return false;

        client.WatchdogTimeout = timeout;

        return true;
    }
    #endregion

    #region 日志
    /// <summary>日志。默认 XTrace.Log</summary>
    public ILog Log { get; set; } = XTrace.Log;
    #endregion
}