Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加对应用部署版本策略的支持 #57

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Stardust.Data/Deployment/Model.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<Column Name="DeployId" ColumnName="AppId" DataType="Int32" Map="AppDeploy@Id@$" Description="应用部署集。对应AppDeploy" />
<Column Name="Version" DataType="String" Master="True" Nullable="False" Description="版本。如2.3.2022.0911,字符串比较大小" />
<Column Name="Enable" DataType="Boolean" Description="启用" />
<Column Name="Strategy" DataType="String" Length="500" Description="策略。升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version&gt;=1.0;runtime/framework/os/oskind/arch/province/city" />
<Column Name="Url" DataType="String" ItemType="file" Length="500" Description="资源地址。一般打包为Zip包,StarAgent下载后解压缩覆盖" />
<Column Name="Overwrite" DataType="String" Length="100" Description="覆盖文件。需要拷贝覆盖已存在的文件或子目录,支持*模糊匹配,多文件分号隔开。如果目标文件不存在,配置文件等自动拷贝" />
<Column Name="Size" DataType="Int64" ItemType="GMK" Description="文件大小" />
Expand Down
11 changes: 11 additions & 0 deletions Stardust.Data/Deployment/Stardust.htm
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,17 @@ <h3>部署版本(AppDeployVersion)</h3>
<td>N</td>
<td></td>
</tr>

<tr>
<td>Strategy</td>
<td>策略</td>
<td>String</td>
<td>500</td>
<td></td>
<td></td>
<td></td>
<td>升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version>=1.0;runtime/framework/os/oskind/arch/province/city</td>
</tr>

<tr>
<td>Url</td>
Expand Down
199 changes: 198 additions & 1 deletion Stardust.Data/Deployment/部署版本.Biz.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
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.Data.Nodes;
using Stardust.Models;
using XCode;

namespace Stardust.Data.Deployment;
Expand Down Expand Up @@ -34,6 +39,18 @@ public override void Valid(Boolean isNew)
// 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框
if (Version.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Version), "版本不能为空!");

// 重新聚合规则
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();
}

// 建议先调用基类方法,基类方法会做一些统一处理
base.Valid(isNew);

Expand All @@ -53,9 +70,21 @@ public override void Valid(Boolean isNew)
// 检查唯一索引
// CheckExist(isNew, nameof(DeployId), nameof(Version));
}

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

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

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

#region 扩展查询
Expand Down Expand Up @@ -96,7 +125,7 @@ public static IList<AppDeployVersion> FindAllByDeployId(Int32 deployId, Int32 co
// 实体缓存
if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.DeployId == deployId).OrderByDescending(e => e.Id).Take(count).ToList();

return FindAll(_.DeployId == deployId, _.Id.Desc(), null, 0, count);
return FindAll(_.DeployId == deployId & _.Enable == true, _.Id.Desc(), null, 0, count);
}
#endregion

Expand Down Expand Up @@ -125,5 +154,173 @@ public static IList<AppDeployVersion> Search(Int32 deployId, String version, Boo
#endregion

#region 业务操作
/// <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
}
15 changes: 15 additions & 0 deletions Stardust.Data/Deployment/部署版本.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public partial class AppDeployVersion
[BindColumn("Enable", "启用", "")]
public Boolean Enable { get => _Enable; set { if (OnPropertyChanging("Enable", value)) { _Enable = value; OnPropertyChanged("Enable"); } } }

private String _Strategy;
/// <summary>策略。升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version>=1.0;runtime/framework/os/oskind/arch/province/city</summary>
[DisplayName("策略")]
[Description("策略。升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version>=1.0;runtime/framework/os/oskind/arch/province/city")]
[DataObjectField(false, false, true, 500)]
[BindColumn("Strategy", "策略。升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version>=1.0;runtime/framework/os/oskind/arch/province/city", "varchar(500)")]
public String Strategy { get => _Strategy; set { if (OnPropertyChanging("Strategy", value)) { _Strategy = value; OnPropertyChanged("Strategy"); } } }

private String _Url;
/// <summary>资源地址。一般打包为Zip包,StarAgent下载后解压缩覆盖</summary>
[DisplayName("资源地址")]
Expand Down Expand Up @@ -206,6 +214,7 @@ public override Object this[String name]
"DeployId" => _DeployId,
"Version" => _Version,
"Enable" => _Enable,
"Strategy" => _Strategy,
"Url" => _Url,
"Overwrite" => _Overwrite,
"Size" => _Size,
Expand All @@ -232,6 +241,7 @@ public override Object this[String name]
case "DeployId": _DeployId = value.ToInt(); break;
case "Version": _Version = Convert.ToString(value); break;
case "Enable": _Enable = value.ToBoolean(); break;
case "Strategy": _Strategy = Convert.ToString(value); break;
case "Url": _Url = Convert.ToString(value); break;
case "Overwrite": _Overwrite = Convert.ToString(value); break;
case "Size": _Size = value.ToLong(); break;
Expand Down Expand Up @@ -281,6 +291,8 @@ public partial class _
/// <summary>启用</summary>
public static readonly Field Enable = FindByName("Enable");

/// <summary>策略。升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version>=1.0;runtime/framework/os/oskind/arch/province/city</summary>
public static readonly Field Strategy = FindByName("Strategy");
/// <summary>资源地址。一般打包为Zip包,StarAgent下载后解压缩覆盖</summary>
public static readonly Field Url = FindByName("Url");

Expand Down Expand Up @@ -347,6 +359,9 @@ public partial class __
/// <summary>启用</summary>
public const String Enable = "Enable";

/// <summary>策略。升级策略,版本特别支持大于等于和小于等于,node=*abcd*;version>=1.0;runtime/framework/os/oskind/arch/province/city</summary>
public const String Strategy = "Strategy";

/// <summary>资源地址。一般打包为Zip包,StarAgent下载后解压缩覆盖</summary>
public const String Url = "Url";

Expand Down
75 changes: 45 additions & 30 deletions Stardust.Server/Controllers/DeployController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,39 +46,54 @@ protected override Boolean OnAuthorize(String token)
/// <returns></returns>
public DeployInfo[] GetAll()
{
var list = AppDeployNode.FindAllByNodeId(_node.ID);

//指定节点上所有应用
var appsInNode = AppDeployNode.FindAllByNodeId(_node.ID);
var rs = new List<DeployInfo>();
foreach (var item in list)
{
// 不返回未启用的发布集,如果需要在客户端删除,则通过指令下发来实现
if (!item.Enable) continue;

var app = item.Deploy;
if (app == null || !app.Enable) continue;

// 消除缓存,解决版本更新后不能及时更新缓存的问题
app = AppDeploy.FindByKey(app.Id);
if (app == null || !app.Enable) continue;

var ver = AppDeployVersion.FindByDeployIdAndVersion(app.Id, app.Version);

var inf = new DeployInfo
foreach (var app in appsInNode)
{
// 不返回未启用的发布集
if (!app.Enable) continue;

var appDeploy = app.Deploy;
if (appDeploy == null || !appDeploy.Enable) continue;

//取所有, Id.Desc
var versions = AppDeployVersion.FindAllByDeployId(appDeploy.Id);
if (!versions.Any()) continue;

versions = versions.Where(ver => ver.Enable == true) //未启用的直接过滤
.GroupBy(ver => new { ver.DeployName, ver.Strategy }) //以 DeployName & Strategy 分组 Group by DeployName and Strategy combination
.SelectMany(group => group.OrderBy(ver => versions.IndexOf(ver)).Take(1)) //保留组内索引最小的一个,也就是最新的Select the first element of each group
.ToList();

foreach (var ver in versions)
{
Name = app.Name,
Version = app.Version,
Url = ver?.Url,
Hash = ver?.Hash,
Overwrite = ver?.Overwrite,

Service = item.ToService(app),
};
rs.Add(inf);

// 修正Url
if (inf.Url.StartsWithIgnoreCase("/cube/file/")) inf.Url = inf.Url.Replace("/cube/file/", "/cube/file?id=");

WriteHistory(app.Id, nameof(GetAll), true, inf.ToJson());
//应用策略
if (!ver.Match(_node))
{
WriteHistory(appDeploy.Id, nameof(GetAll), false, ver.MatchResult(_node));
continue;
}

var inf = new DeployInfo
{
Name = appDeploy.Name,
Version = ver.Version,
Url = ver?.Url,
Hash = ver?.Hash,
Overwrite = ver?.Overwrite,

Service = app.ToService(appDeploy),
};

rs.Add(inf);

// 修正Url
if (inf.Url.StartsWithIgnoreCase("/cube/file/")) inf.Url = inf.Url.Replace("/cube/file/", "/cube/file?id=");

WriteHistory(appDeploy.Id, nameof(GetAll), true, inf.ToJson());
}
}

return rs.ToArray();
Expand Down
Loading
Loading