节点版本,自动新增默认值
智能大石头 authored at 2024-01-17 00:52:13
13.54 KiB
Stardust
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using NewLife;
using NewLife.Data;
using NewLife.Log;
using Stardust.Models;
using XCode;

namespace Stardust.Data.Nodes;

/// <summary>节点升级通道</summary>
public enum NodeChannels
{
    /// <summary>发布</summary>
    Release = 1,

    /// <summary>测试</summary>
    Beta = 2,

    /// <summary>开发</summary>
    Develop = 3,
}

/// <summary>节点版本。发布更新</summary>
public partial class NodeVersion : Entity<NodeVersion>
{
    #region 对象操作
    static NodeVersion()
    {
        // 累加字段
        //var df = Meta.Factory.AdditionalFields;
        //df.Add(__.ProductID);

        // 过滤器 UserModule、TimeModule、IPModule
        Meta.Modules.Add<UserModule>();
        Meta.Modules.Add<TimeModule>();
        Meta.Modules.Add<IPModule>();
    }

    /// <summary>验证数据,通过抛出异常的方式提示验证失败。</summary>
    /// <param name="isNew">是否插入</param>
    public override void Valid(Boolean isNew)
    {
        // 如果没有脏数据,则不需要进行任何处理
        if (!HasDirty) return;

        // 重新聚合规则
        if (!Dirtys[__.Strategy] && Rules != null)
        {
            var sb = new StringBuilder();
            foreach (var item in Rules)
            {
                if (sb.Length > 0) sb.Append(";");
                sb.AppendFormat("{0}={1}", item.Key, item.Value.Join(","));
            }
            Strategy = sb.ToString();
        }

        // 默认通道
        if (Channel < NodeChannels.Release || Channel > NodeChannels.Develop) Channel = NodeChannels.Release;

        base.Valid(isNew);
    }

    /// <summary>加载后,释放规则</summary>
    protected override void OnLoad()
    {
        base.OnLoad();

        var dic = Strategy.SplitAsDictionary("=", ";");
        Rules = dic.ToDictionary(e => e.Key, e => e.Value.Split(","), StringComparer.OrdinalIgnoreCase);
    }

    protected override void InitData()
    {
        if (Meta.Count > 0) return;

        var entity = new NodeVersion
        {
            Version = "agent60-0101",
            ProductCode = "StarAgent",
            Enable = false,
            Force = true,
            Channel = NodeChannels.Release,
            Strategy = "framework=6.*;version<=2.9.2024.0101",
            Description = "星尘代理StarAgent升级策略(dotNet6.0)",
        };
        entity.Insert();

        entity = new NodeVersion
        {
            Version = "agent80-0101",
            ProductCode = "StarAgent",
            Enable = false,
            Force = true,
            Channel = NodeChannels.Release,
            Strategy = "framework=8.*;version<=2.9.2024.0101",
            Description = "星尘代理StarAgent升级策略(dotNet8.0),同时支持net6/net7的StarAgent在安装net8后升级",
        };
        entity.Insert();

        entity = new NodeVersion
        {
            Version = "CrazyCoder-0101",
            ProductCode = "CrazyCoder",
            Enable = false,
            Force = true,
            Channel = NodeChannels.Release,
            Strategy = "framework=8.*;version<=2.9.2024.0101",
            Description = "码神工具升级策略",
        };
        entity.Insert();

        entity = new NodeVersion
        {
            Version = "v8.0.1-aspnet",
            ProductCode = "dotnet",
            Enable = false,
            Force = true,
            Channel = NodeChannels.Release,
            Strategy = "framework<=7.1;oskind=centos,ubuntu,smartos;version>=2.9.2024.0101",
            Source = "/files/dotnet/",
            Description = "NET8运行时升级策略,在CentOS/Ubuntu/SmartOS等系统上,自动安装net8",
        };
        entity.Insert();

        entity = new NodeVersion
        {
            Version = "v8.0.1-host",
            ProductCode = "dotnet",
            Enable = false,
            Force = true,
            Channel = NodeChannels.Release,
            Strategy = "framework<=7.1;oskind=win20*;version>=2.9.2024.0101",
            Source = "/files/dotnet/",
            Description = "NET8运行时升级策略,在Windows服务器系统上,自动安装net8",
        };
        entity.Insert();

        entity = new NodeVersion
        {
            Version = "v8.0.1-desktop",
            ProductCode = "dotnet",
            Enable = false,
            Force = true,
            Channel = NodeChannels.Release,
            Strategy = "framework<=7.1;oskind=win1*;version>=2.9.2024.0101",
            Source = "/files/dotnet/",
            Description = "NET8运行时升级策略,在win10/win11系统上,自动安装net8",
        };
        entity.Insert();
    }
    #endregion

    #region 扩展属性
    /// <summary>规则集合</summary>
    [XmlIgnore]
    public IDictionary<String, String[]> Rules { get; set; }
    #endregion

    #region 扩展查询
    /// <summary>根据编号查找</summary>
    /// <param name="id">编号</param>
    /// <returns>实体对象</returns>
    public static NodeVersion 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="version"></param>
    /// <returns></returns>
    public static NodeVersion FindByVersion(String version)
    {
        if (version.IsNullOrEmpty()) return null;

        return Find(_.Version == version);
    }
    #endregion

    #region 高级查询

    /// <summary>
    /// 高级查询
    /// </summary>
    /// <param name="start">开始时间</param>
    /// <param name="end">结束时间</param>
    /// <param name="enable">启用</param>
    /// <param name="key">关键词</param>
    /// <param name="p">分页</param>
    /// <returns></returns>
    public static IList<NodeVersion> Search(DateTime start, DateTime end, Boolean? enable, String key, PageParameter p)
    {
        var exp = new WhereExpression();
        if (enable != null) exp &= _.Enable == enable;
        exp &= _.UpdateTime.Between(start, end);
        exp &= SearchWhereByKeys(key);

        return FindAll(exp, p);
    }

    #endregion

    #region 业务操作
    /// <summary>获取有效</summary>
    /// <param name="channel"></param>
    /// <returns></returns>
    public static IList<NodeVersion> GetValids(NodeChannels channel)
    {
        var list = Meta.Cache.FindAll(e => e.Enable);
        if (list.Count == 0) return list;

        if (channel >= NodeChannels.Release) list = list.Where(e => e.Channel == channel).ToList();

        // 按照编号降序,最大100个
        list = list.OrderByDescending(e => e.ID).Take(100).ToList();

        return list;
    }

    /// <summary>应用策略是否匹配指定节点</summary>
    /// <param name="node"></param>
    /// <returns></returns>
    public Boolean Match(Node node)
    {
        var rs = MatchResult(node);
        if (rs == null) return true;

        DefaultSpan.Current?.AppendTag($"[{ID}][{Version}] {rs}");

        return false;
    }

    /// <summary>应用策略是否匹配指定节点</summary>
    /// <param name="node"></param>
    /// <returns></returns>
    public String MatchResult(Node node)
    {
        // 没有使用该规则,直接过
        if (Rules.TryGetValue("version", out var vs))
        {
            var ver = node.Version;
            if (ver.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(ver, StringComparison.OrdinalIgnoreCase)))
                return $"[{ver}] not Match {vs.Join(",")}";
        }
        else if (Rules.TryGetValue("version>", out vs))
        {
            var ver = node.Version;
            if (node.Version.IsNullOrEmpty()) return "Version is null";
            if (!System.Version.TryParse(ver, out var ver1)) return $"Version=[{ver}] is invalid";
            if (!System.Version.TryParse(vs[0], out var ver2)) return $"vs[0]=[{vs[0]}] is invalid";

            if (ver1 < ver2) return $"Version=[{ver1}] < {ver2}";
        }
        else if (Rules.TryGetValue("version<", out vs))
        {
            var ver = node.Version;
            if (node.Version.IsNullOrEmpty()) return "Version is null";
            if (!System.Version.TryParse(ver, out var ver1)) return $"Version=[{ver}] is invalid";
            if (!System.Version.TryParse(vs[0], out var ver2)) return $"vs[0]=[{vs[0]}] is invalid";

            if (ver1 > ver2) return $"Version=[{ver1}] > {ver2}";
        }

        if (Rules.TryGetValue("node", out vs))
        {
            var code = node.Code;
            var name = node.Name;
            if (code.IsNullOrEmpty() && name.IsNullOrEmpty()) return "Node is null";
            if ((code.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(code, StringComparison.OrdinalIgnoreCase))) &&
                (name.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(name, StringComparison.OrdinalIgnoreCase))))
                return $"[{code}/{name}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("category", out vs))
        {
            var category = node.Category;
            if (category.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(category, StringComparison.OrdinalIgnoreCase)))
                return $"[{category}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("runtime", out vs))
        {
            var runtime = node.Runtime;
            if (runtime.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(runtime))) return $"[{runtime}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("framework", out vs))
        {
            var str = !node.Frameworks.IsNullOrEmpty() ? node.Frameworks : node.Framework;
            var frameworks = str?.Split(",");
            if (frameworks == null || frameworks.Length == 0) return "Frameworks is null";

            // 本节点拥有的所有框架,任意框架匹配任意规则,即可认为匹配
            var flag = false;
            foreach (var item in frameworks)
            {
                if (vs.Any(e => e.IsMatch(item)))
                {
                    flag = true;
                    break;
                }
            }
            if (!flag) return $"[{str}] not Match {vs.Join(",")}";
        }
        else if (Rules.TryGetValue("framework>", out vs))
        {
            var str = node.Framework;
            if (str.IsNullOrEmpty()) return "Version is null";
            if (!System.Version.TryParse(str, out var ver1)) return $"Framework=[{str}] is invalid";
            if (!System.Version.TryParse(vs[0], out var ver2)) return $"vs[0]=[{vs[0]}] is invalid";

            if (ver1 < ver2) return $"Framework=[{ver1}] < {ver2}";
        }
        else if (Rules.TryGetValue("framework<", out vs))
        {
            var str = node.Framework;
            if (str.IsNullOrEmpty()) return "Version is null";
            if (!System.Version.TryParse(str, out var ver1)) return $"Framework=[{str}] is invalid";
            if (!System.Version.TryParse(vs[0], out var ver2)) return $"vs[0]=[{vs[0]}] is invalid";

            if (ver1 > ver2) return $"Framework=[{ver1}] > {ver2}";
        }

        if (Rules.TryGetValue("os", out vs))
        {
            var os = node.OS;
            if (os.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(os, StringComparison.OrdinalIgnoreCase)))
                return $"[{os}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("oskind", out vs))
        {
            var os = node.OSKind;
            if (os <= 0) return "OSKind is null";

            var flag = false;
            foreach (var item in vs)
            {
                if (item.ToInt() == (Int32)os)
                {
                    flag = true;
                    break;
                }
                if (Enum.TryParse<OSKinds>(item, true, out var v) && v == os)
                {
                    flag = true;
                    break;
                }
            }
            if (!flag) return $"[{os}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("arch", out vs))
        {
            var arch = node.Architecture;
            if (arch.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(arch, StringComparison.OrdinalIgnoreCase)))
                return $"[{arch}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("province", out vs))
        {
            var code = node.ProvinceID + "";
            var name = node.ProvinceName;
            if (code.IsNullOrEmpty() && name.IsNullOrEmpty()) return "Province is null";
            if ((code.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(code, StringComparison.OrdinalIgnoreCase))) &&
                (name.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(name, StringComparison.OrdinalIgnoreCase))))
                return $"[{code}/{name}] not Match {vs.Join(",")}";
        }

        if (Rules.TryGetValue("city", out vs))
        {
            var code = node.CityID + "";
            var name = node.CityName;
            if (code.IsNullOrEmpty() && name.IsNullOrEmpty()) return "City is null";
            if ((code.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(code, StringComparison.OrdinalIgnoreCase))) &&
                (name.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(name, StringComparison.OrdinalIgnoreCase))))
                return $"[{code}/{name}] not Match {vs.Join(",")}";
        }

        //if (Rules.TryGetValue("product", out vs))
        //{
        //    var product = node.ProductCode;
        //    if (product.IsNullOrEmpty() || !vs.Any(e => e.IsMatch(product))) return false;
        //}

        return null;
    }
    #endregion
}