-
Notifications
You must be signed in to change notification settings - Fork 113
feat(app): 全新的公告系统 #2786
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
base: dev
Are you sure you want to change the base?
feat(app): 全新的公告系统 #2786
Changes from all commits
5cdb34b
c32f753
a726ff0
8388b31
56762ff
3ebd0cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using System.Net.Http; | ||
| using System.Text.Json; | ||
| using System.Threading.Tasks; | ||
| using PCL.Core.App.Essentials.Announcement.Models; | ||
| using PCL.Core.App.IoC; | ||
| using PCL.Core.IO.Net.Http; | ||
| using PCL.Core.Logging; | ||
| using PCL.Core.UI; | ||
|
|
||
| namespace PCL.Core.App.Essentials.Announcement; | ||
|
|
||
| [LifecycleScope("announcement","公告")] | ||
| [LifecycleService(LifecycleState.Running)] | ||
| public partial class AnnouncementService | ||
| { | ||
|
|
||
| private static readonly string[] _AllowScheme = ["http", "https", "minecraft" ]; | ||
| private static readonly string[] _AnnouncementServerList = Secrets.AnnouncementServerList; | ||
|
|
||
| private static List<string> _ignored = | ||
| JsonSerializer.Deserialize<string[]>(Config.System.HiddenAnnouncement)?.ToList() ?? []; | ||
|
|
||
| [LifecycleStart] | ||
| private static async Task _Start() | ||
| { | ||
| // 可能会出现公告服务比配置服务晚关闭的情况 | ||
| Lifecycle.StateChanged += state => | ||
| { | ||
| if (state == LifecycleState.Closing) Config.System.HiddenAnnouncement = JsonSerializer.Serialize(_ignored); | ||
| }; | ||
| try | ||
| { | ||
| foreach (var source in _AnnouncementServerList) | ||
| { | ||
| var response = await HttpRequest.GetJsonAsync<List<AnnouncementDetails>>(source) | ||
| .ConfigureAwait(false); | ||
| if (response is null) continue; | ||
|
|
||
| // 对忽略的公告进行检查1,以确保仍然处于公告列表内 | ||
|
|
||
| var invalid = _ignored.Except(response.Select(a => a.Id)).ToList(); | ||
| _ignored.RemoveAll(invalid.Contains); | ||
|
|
||
| var announcements = response.OrderByDescending(a => a.Priority).Where(a => | ||
| { | ||
| var isNotAfterValid = DateTimeOffset.TryParse(a.SkipOn.NotAfter, out var notAfter); | ||
| var isNotBeforeValid = DateTimeOffset.TryParse(a.SkipOn.NotBefore, out var notBefore); | ||
| var localTime = DateTimeOffset.Now; | ||
| if (isNotAfterValid && localTime > notAfter) return false; | ||
| if (isNotBeforeValid && localTime < notBefore) return false; | ||
| var currentVersion = new Version(Basics.VersionName.Split("-")[0]); | ||
| var max = new Version(a.SkipOn.MaxVersion ?? "999.999.999"); | ||
| var min = new Version(a.SkipOn.MinVersion ?? "0.0.0"); | ||
|
|
||
| // [min,max] | ||
| return currentVersion >= min && currentVersion <= max; | ||
|
|
||
| }); | ||
| foreach (var detail in announcements) | ||
| { | ||
| Context.Debug(MsgBoxWrapper.ShowWithCustomButtons( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| detail.Details, $"{detail.Title} ({detail.ReleaseDate})", _GetSelectTheme(detail.Level), | ||
| false, | ||
| detail.Buttons.Select(operation => new MsgBoxButtonInfo(operation.ButtonText, | ||
|
Comment on lines
+65
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: 某一个公告服务器失败会中止后续服务器的处理。 因为 建议实现方式: foreach (var detail in announcements)
{
Context.Debug(MsgBoxWrapper.ShowWithCustomButtons(
detail.Details, $"{detail.Title} ({detail.ReleaseDate})", _GetSelectTheme(detail.Level),
false,
detail.Buttons.Select(operation => new MsgBoxButtonInfo(operation.ButtonText,
OnClick: _GetSelectCallback(operation.Operation, operation.Argument))).ToArray()).ToString());
}
}
}
}
private static Action _GetSelectCallback(string operation, string arguments) => operation switch要完整实现你在审查意见中建议的“按服务器分别处理错误”,可以:
Original comment in Englishsuggestion: A failure on one announcement server aborts processing of subsequent servers. Because the Suggested implementation: foreach (var detail in announcements)
{
Context.Debug(MsgBoxWrapper.ShowWithCustomButtons(
detail.Details, $"{detail.Title} ({detail.ReleaseDate})", _GetSelectTheme(detail.Level),
false,
detail.Buttons.Select(operation => new MsgBoxButtonInfo(operation.ButtonText,
OnClick: _GetSelectCallback(operation.Operation, operation.Argument))).ToArray()).ToString());
}
}
}
}
private static Action _GetSelectCallback(string operation, string arguments) => operation switchTo fully implement per-server error handling as suggested in your review comment, you should:
|
||
| OnClick: _GetSelectCallback(operation.Operation, operation.Argument))).ToArray()).ToString()); | ||
| } | ||
| } | ||
| } | ||
| catch (HttpRequestException ex) | ||
| { | ||
| Context.Error("加载公告失败", ex, ActionLevel.HintErr); | ||
| } | ||
| } | ||
|
|
||
| private static Action _GetSelectCallback(string operation, string arguments) => operation switch | ||
| { | ||
| "OpenWebSite" => () => | ||
| { | ||
| if (arguments.Length == 0) throw new ArgumentException("Uri is missing"); | ||
| if (_AllowScheme.All(s => new Uri(arguments).Scheme != s)) | ||
| throw new InvalidOperationException("This uri contains a unsupported scheme."); | ||
| Process.Start(new ProcessStartInfo(arguments){ UseShellExecute = true }); | ||
|
|
||
| }, | ||
| "StopShow" => () => | ||
| { | ||
| _ignored.Add(arguments); | ||
| }, | ||
| _ => static () => { } | ||
| }; | ||
|
|
||
| private static MsgBoxTheme _GetSelectTheme(AnnouncementLevel level) => level switch | ||
| { | ||
| AnnouncementLevel.Medium => MsgBoxTheme.Warning, | ||
| AnnouncementLevel.Highest => MsgBoxTheme.Error, | ||
| _ => MsgBoxTheme.Info | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace PCL.Core.App.Essentials.Announcement.Models; | ||
|
|
||
| public record AnnouncementDetails | ||
| { | ||
| /// <summary> | ||
| /// 公告标题 | ||
| /// </summary> | ||
| [JsonPropertyName("title")] | ||
| public required string Title { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 公告内容 | ||
| /// </summary> | ||
| [JsonPropertyName("details")] | ||
| public required string Details { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 该公告的优先级,值越高优先级越高 | ||
| /// </summary> | ||
| [JsonPropertyName("priority")] | ||
| public int Priority { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 公告 ID | ||
| /// </summary> | ||
| [JsonPropertyName("id")] | ||
| public required string Id { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 该公告的等级,决定弹窗应该用什么样式 | ||
| /// </summary> | ||
| [JsonPropertyName("level")] | ||
| public required AnnouncementLevel Level { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 该公告的发布日期 | ||
| /// </summary> | ||
| [JsonPropertyName("date")] | ||
| public required string ReleaseDate { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 显示条件 | ||
| /// </summary> | ||
| [JsonPropertyName("skip")] | ||
| public required AnnouncementSkipCondition SkipOn { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 弹窗按钮信息 | ||
| /// </summary> | ||
| [JsonPropertyName("buttons")] | ||
| public required IEnumerable<AnnouncementOperation> Buttons { get; init; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| namespace PCL.Core.App.Essentials.Announcement.Models; | ||
|
|
||
| public enum AnnouncementLevel | ||
| { | ||
| /// <summary> | ||
| /// 最低的等级,属于可看可不看的那种 | ||
| /// </summary> | ||
| Lowest, | ||
| /// <summary> | ||
| /// 用户应该稍微有点了解的公告 | ||
| /// </summary> | ||
| Medium, | ||
| /// <summary> | ||
| /// 必须让用户知道并理解的公告内容 | ||
| /// </summary> | ||
| Highest | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| using System.Collections.Generic; | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace PCL.Core.App.Essentials.Announcement.Models; | ||
|
|
||
| public record AnnouncementOperation | ||
| { | ||
| /// <summary> | ||
| /// 按钮文本 | ||
| /// </summary> | ||
| [JsonPropertyName("text")] | ||
| public required string ButtonText { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 按下后的操作 | ||
| /// </summary> | ||
| [JsonPropertyName("exec")] | ||
| public required string Operation { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// 参数列表 | ||
| /// </summary> | ||
| [JsonPropertyName("argument")] | ||
| public required string Argument { get; init; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace PCL.Core.App.Essentials.Announcement.Models; | ||
|
|
||
| public class AnnouncementSkipCondition | ||
| { | ||
| [JsonPropertyName("min")] | ||
| public string? MinVersion { get; init; } | ||
| [JsonPropertyName("max")] | ||
| public string? MaxVersion { get; init; } | ||
| [JsonPropertyName("notAfter")] | ||
| public string? NotAfter { get; init; } | ||
| [JsonPropertyName("notBefore")] | ||
| public string? NotBefore { get; init; } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,4 +40,10 @@ public static class Secrets | |
| /// 当前版本的 Git 提交 SHA | ||
| /// </summary> | ||
| public static string CommitHash { get; } = EnvironmentInterop.GetSecret("GITHUB_SHA", readEnvDebugOnly: true).ReplaceNullOrEmpty(); | ||
|
|
||
| /// <summary> | ||
| /// 公告服务器地址 | ||
| /// </summary> | ||
| public static string[] AnnouncementServerList { get; } = EnvironmentInterop | ||
| .GetSecret("ANNOUNCEMENT_SERVER", readEnvDebugOnly: true).ReplaceNullOrEmpty().Split("|"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In release builds Useful? React with 👍 / 👎. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a user clicks a
StopShowbutton, the callback appends that ID to_ignoredand the Closing handler persists it, but this filtered sequence only checks the time/version window and never excludes IDs already in_ignored. On the next launch the supposedly hidden announcement is still included and shown again, so the permanent-hide action does not work.Useful? React with 👍 / 👎.