-
Notifications
You must be signed in to change notification settings - Fork 86
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 1 commit
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.Client.Request; | ||
|
Check failure on line 10 in PCL.Core/App/Essentials/Announcement/AnnouncementService.cs
|
||
| 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.OrderBy(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( | ||
| detail.Details, $"{detail.Title} ({detail.ReleaseDate})", _GetSelectTheme(detail.Level), | ||
| false, | ||
| detail.Buttons.Select(operation => new MsgBoxButtonInfo(operation.ButtonText, | ||
|
Comment on lines
+65
to
+68
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; } | ||
|
sourcery-ai[bot] marked this conversation as resolved.
Outdated
|
||
| [JsonPropertyName("notAfter")] | ||
| public string? NotAfter { get; init; } | ||
| [JsonPropertyName("notBefore")] | ||
| public string? NotBefore { get; init; } | ||
|
|
||
| } | ||
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.
issue (bug_risk): 版本解析在
MinVersion/MaxVersion包含无效或空值时可能会抛出异常。new Version(a.SkipOn.MaxVersion ?? "999.999.999")(以及MinVersion的对应代码)如果服务器返回空字符串或格式错误的版本字符串,就会抛出异常。建议使用Version.TryParse并提供默认值,或者在反序列化阶段把空字符串规范化为null,然后再应用这些默认值,以避免公告处理因为一个条目出错而中断。Original comment in English
issue (bug_risk): Version parsing can throw if
MinVersion/MaxVersioncontain invalid or empty values.new Version(a.SkipOn.MaxVersion ?? "999.999.999")(and theMinVersionequivalent) will throw if the server sends an empty or malformed version string. Consider usingVersion.TryParsewith a default value, or normalizing empty strings tonullduring deserialization before applying these defaults, to avoid aborting announcement processing.