XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.ServerInteractive

[DLL] Server interaction extension, including user identity verification and querying in conjunction with AutoConfig

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEExtension.NetCore.ServerInteractive

Add source generator validation: partial class, method signature, return type, and path character checks; fix override vs new; fix route mismatches

Agent-Logs-Url: https://github.com/XFEstudio/XFEExtension.NetCore.ServerInteractive/sessions/96fe8623-55b8-4906-a548-49ea5dce0912 Co-authored-by: XFEstudio <132526994+XFEstudio@users.noreply.github.com>

6a0c234
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
提交于

代码差异

3 个文件 +224 -37
Modified XFEExtension.NetCore.ServerInteractive.SourceGenerator/EntryPointGenerator.cs +210 -28
@@ -16,6 +16,38 @@ namespace XFEExtension.NetCore.ServerInteractive.SourceGenerator;
16 16 [Generator]
17 17 public class EntryPointGenerator : IIncrementalGenerator
18 18 {
19 private static readonly DiagnosticDescriptor NonPartialClassRule = new(
20 id: "XFESI001",
21 title: "包含EntryPoint方法的类必须为partial",
22 messageFormat: "类'{0}'必须声明为partial以便增量生成器可以生成入口点代码",
23 category: "XFEServerInteractive",
24 defaultSeverity: DiagnosticSeverity.Error,
25 isEnabledByDefault: true);
26
27 private static readonly DiagnosticDescriptor MethodMustBeParameterlessRule = new(
28 id: "XFESI002",
29 title: "EntryPoint方法不能有参数",
30 messageFormat: "方法'{0}'标记了[EntryPoint]但包含参数,入口点方法必须是无参数的",
31 category: "XFEServerInteractive",
32 defaultSeverity: DiagnosticSeverity.Error,
33 isEnabledByDefault: true);
34
35 private static readonly DiagnosticDescriptor InvalidReturnTypeRule = new(
36 id: "XFESI003",
37 title: "EntryPoint方法返回类型无效",
38 messageFormat: "方法'{0}'的返回类型'{1}'无效,入口点方法必须返回void或Task",
39 category: "XFEServerInteractive",
40 defaultSeverity: DiagnosticSeverity.Error,
41 isEnabledByDefault: true);
42
43 private static readonly DiagnosticDescriptor InvalidPathCharactersRule = new(
44 id: "XFESI004",
45 title: "EntryPoint路径包含无效字符",
46 messageFormat: "入口点路径'{0}'包含无效字符(引号或反斜杠),这些字符不允许在路径中使用",
47 category: "XFEServerInteractive",
48 defaultSeverity: DiagnosticSeverity.Error,
49 isEnabledByDefault: true);
50
19 51 public void Initialize(IncrementalGeneratorInitializationContext context)
20 52 {
21 53 // 找到所有标记了EntryPointAttribute的方法
@@ -38,7 +70,7 @@ public class EntryPointGenerator : IIncrementalGenerator
38 70 return node is MethodDeclarationSyntax m && m.AttributeLists.Count > 0;
39 71 }
40 72
41 private static MethodInfo? GetMethodForGeneration(GeneratorSyntaxContext context)
73 private static MethodCandidate? GetMethodForGeneration(GeneratorSyntaxContext context)
42 74 {
43 75 var methodDeclaration = (MethodDeclarationSyntax)context.Node;
44 76 var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration);
@@ -58,36 +90,118 @@ public class EntryPointGenerator : IIncrementalGenerator
58 90 if (string.IsNullOrEmpty(path))
59 91 return null;
60 92
61 // 自动检查是否为异步方法
62 var isAsync = methodSymbol.IsAsync ||
63 (methodSymbol.ReturnType is INamedTypeSymbol returnType &&
64 returnType.Name == "Task");
93 // 检查返回类型:根据返回类型(而非async关键字)判断同步/异步
94 var returnType = methodSymbol.ReturnType;
95 var isVoid = returnType.SpecialType == SpecialType.System_Void;
96 var isTaskLike = returnType.Name == "Task" &&
97 returnType.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks";
98 var hasValidReturnType = isVoid || isTaskLike;
99 var isAsync = isTaskLike;
100
101 // 检查包含类型是否为partial
102 var classDeclaration = methodDeclaration.Parent as ClassDeclarationSyntax;
103 var isContainingTypePartial = classDeclaration?.Modifiers.Any(SyntaxKind.PartialKeyword) ?? false;
104
105 // 获取泛型类型参数和约束(从语法节点获取以保留原始文本)
106 var typeParameters = classDeclaration?.TypeParameterList?.ToString() ?? "";
107 var typeConstraints = classDeclaration?.ConstraintClauses.ToString() ?? "";
65 108
66 109 var containingType = methodSymbol.ContainingType;
67 110
68 return new MethodInfo(
111 // 获取位置信息(使用轻量结构避免在增量缓存中持有SyntaxTree引用)
112 var methodLocation = LocationInfo.From(methodDeclaration.GetLocation());
113 var classLocation = classDeclaration is not null
114 ? LocationInfo.From(classDeclaration.Identifier.GetLocation())
115 : methodLocation;
116
117 return new MethodCandidate(
69 118 containingType.ContainingNamespace.ToDisplayString(),
70 119 containingType.Name,
71 120 methodSymbol.Name,
72 121 path!,
73 isAsync
74 );
122 isAsync,
123 isContainingTypePartial,
124 methodSymbol.Parameters.Length,
125 hasValidReturnType,
126 returnType.ToDisplayString(),
127 methodLocation,
128 classLocation,
129 typeParameters,
130 typeConstraints);
75 131 }
76 132
77 private static void Execute(Compilation compilation, ImmutableArray<MethodInfo?> methods, SourceProductionContext context)
133 private static void Execute(Compilation compilation, ImmutableArray<MethodCandidate?> methods, SourceProductionContext context)
78 134 {
79 135 if (methods.IsDefaultOrEmpty)
80 136 return;
81 137
82 // 按类分组
83 var methodsByClass = methods
84 .Where(m => m is not null)
85 .GroupBy(m => (m!.Namespace, m.ClassName));
138 var validMethods = new List<MethodCandidate>();
139
140 // 校验并报告诊断信息
141 foreach (var method in methods)
142 {
143 if (method is null) continue;
144
145 var isValid = true;
146
147 // 校验:包含类型必须为partial
148 if (!method.IsContainingTypePartial)
149 {
150 context.ReportDiagnostic(Diagnostic.Create(
151 NonPartialClassRule,
152 method.ClassLocation.ToLocation(),
153 method.ClassName));
154 isValid = false;
155 }
156
157 // 校验:方法不能有参数
158 if (method.ParameterCount > 0)
159 {
160 context.ReportDiagnostic(Diagnostic.Create(
161 MethodMustBeParameterlessRule,
162 method.MethodLocation.ToLocation(),
163 method.MethodName));
164 isValid = false;
165 }
166
167 // 校验:返回类型必须为void或Task
168 if (!method.HasValidReturnType)
169 {
170 context.ReportDiagnostic(Diagnostic.Create(
171 InvalidReturnTypeRule,
172 method.MethodLocation.ToLocation(),
173 method.MethodName,
174 method.ReturnTypeName));
175 isValid = false;
176 }
177
178 // 校验:路径不能包含引号或反斜杠
179 if (method.Path.Contains("\"") || method.Path.Contains("\\"))
180 {
181 context.ReportDiagnostic(Diagnostic.Create(
182 InvalidPathCharactersRule,
183 method.MethodLocation.ToLocation(),
184 method.Path));
185 isValid = false;
186 }
187
188 if (isValid)
189 {
190 validMethods.Add(method);
191 }
192 }
193
194 if (validMethods.Count == 0)
195 return;
196
197 // 按类分组并生成代码
198 var methodsByClass = validMethods.GroupBy(m => (m.Namespace, m.ClassName, m.TypeParameters, m.TypeConstraints));
86 199
87 200 foreach (var group in methodsByClass)
88 201 {
89 var (namespaceName, className) = group.Key;
202 var (namespaceName, className, typeParameters, typeConstraints) = group.Key;
90 203 var methodInfos = group.ToList();
204 var constraintsSuffix = string.IsNullOrEmpty(typeConstraints) ? "" : $" {typeConstraints}";
91 205
92 206 var sourceBuilder = new StringBuilder();
93 207 sourceBuilder.AppendLine($@"// <auto-generated/>
@@ -102,7 +216,7 @@ namespace {namespaceName}
102 216 /// <summary>
103 217 /// {className}的自动生成入口点字典部分类
104 218 /// </summary>
105 public partial class {className}
219 public partial class {className}{typeParameters}{constraintsSuffix}
106 220 {{
107 221 /// <summary>
108 222 /// 静态构造函数,用于初始化EntryPointList
@@ -113,34 +227,42 @@ namespace {namespaceName}
113 227 // 添加所有入口点到静态列表
114 228 foreach (var method in methodInfos)
115 229 {
116 sourceBuilder.AppendLine($" EntryPointList.Add(\"{method!.Path}\");");
230 sourceBuilder.AppendLine($" EntryPointList.Add(\"{EscapeStringLiteral(method.Path)}\");");
117 231 }
118 232
119 233 sourceBuilder.AppendLine($@" }}
120 234
235 private Dictionary<string, Action>? _generatedSyncEntryPoints;
121 236 /// <inheritdoc/>
122 public new Dictionary<string, Action> SyncEntryPoints {{ get; }} = new()
123 {{");
237 public override Dictionary<string, Action> SyncEntryPoints
238 {{
239 get => _generatedSyncEntryPoints ??= new Dictionary<string, Action>()
240 {{");
124 241
125 242 // 添加同步入口点
126 foreach (var method in methodInfos.Where(m => !m!.IsAsync))
243 foreach (var method in methodInfos.Where(m => !m.IsAsync))
127 244 {
128 sourceBuilder.AppendLine($" {{ \"{method!.Path}\", {method.MethodName} }},");
245 sourceBuilder.AppendLine($" {{ \"{EscapeStringLiteral(method.Path)}\", {method.MethodName} }},");
129 246 }
130 247
131 sourceBuilder.AppendLine($@" }};
248 sourceBuilder.AppendLine($@" }};
249 }}
132 250
251 private Dictionary<string, Func<Task>>? _generatedAsyncEntryPoints;
133 252 /// <inheritdoc/>
134 public new Dictionary<string, Func<Task>> AsyncEntryPoints {{ get; }} = new()
135 {{");
253 public override Dictionary<string, Func<Task>> AsyncEntryPoints
254 {{
255 get => _generatedAsyncEntryPoints ??= new Dictionary<string, Func<Task>>()
256 {{");
136 257
137 258 // 添加异步入口点
138 foreach (var method in methodInfos.Where(m => m!.IsAsync))
259 foreach (var method in methodInfos.Where(m => m.IsAsync))
139 260 {
140 sourceBuilder.AppendLine($" {{ \"{method!.Path}\", {method.MethodName} }},");
261 sourceBuilder.AppendLine($" {{ \"{EscapeStringLiteral(method.Path)}\", {method.MethodName} }},");
141 262 }
142 263
143 sourceBuilder.AppendLine(@" };
264 sourceBuilder.AppendLine(@" };
265 }
144 266 }
145 267 }");
146 268
@@ -148,21 +270,81 @@ namespace {namespaceName}
148 270 }
149 271 }
150 272
151 private class MethodInfo
273 /// <summary>
274 /// 转义字符串字面量中的特殊字符
275 /// </summary>
276 private static string EscapeStringLiteral(string value)
277 {
278 return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
279 }
280
281 /// <summary>
282 /// 轻量级位置信息,避免在增量生成器缓存中持有SyntaxTree引用
283 /// </summary>
284 private readonly struct LocationInfo
285 {
286 public string FilePath { get; }
287 public TextSpan TextSpan { get; }
288 public LinePositionSpan LineSpan { get; }
289
290 private LocationInfo(string filePath, TextSpan textSpan, LinePositionSpan lineSpan)
291 {
292 FilePath = filePath;
293 TextSpan = textSpan;
294 LineSpan = lineSpan;
295 }
296
297 public static LocationInfo From(Location location)
298 {
299 var mappedSpan = location.GetMappedLineSpan();
300 return new LocationInfo(
301 mappedSpan.Path ?? "",
302 location.SourceSpan,
303 mappedSpan.Span);
304 }
305
306 public Location ToLocation()
307 {
308 return Location.Create(FilePath, TextSpan, LineSpan);
309 }
310 }
311
312 /// <summary>
313 /// 方法候选信息,包含验证所需的所有数据
314 /// </summary>
315 private class MethodCandidate
152 316 {
153 317 public string Namespace { get; }
154 318 public string ClassName { get; }
155 319 public string MethodName { get; }
156 320 public string Path { get; }
157 321 public bool IsAsync { get; }
322 public bool IsContainingTypePartial { get; }
323 public int ParameterCount { get; }
324 public bool HasValidReturnType { get; }
325 public string ReturnTypeName { get; }
326 public LocationInfo MethodLocation { get; }
327 public LocationInfo ClassLocation { get; }
328 public string TypeParameters { get; }
329 public string TypeConstraints { get; }
158 330
159 public MethodInfo(string namespaceName, string className, string methodName, string path, bool isAsync)
331 public MethodCandidate(string namespaceName, string className, string methodName, string path, bool isAsync,
332 bool isContainingTypePartial, int parameterCount, bool hasValidReturnType, string returnTypeName,
333 LocationInfo methodLocation, LocationInfo classLocation, string typeParameters, string typeConstraints)
160 334 {
161 335 Namespace = namespaceName;
162 336 ClassName = className;
163 337 MethodName = methodName;
164 338 Path = path;
165 339 IsAsync = isAsync;
340 IsContainingTypePartial = isContainingTypePartial;
341 ParameterCount = parameterCount;
342 HasValidReturnType = hasValidReturnType;
343 ReturnTypeName = returnTypeName;
344 MethodLocation = methodLocation;
345 ClassLocation = classLocation;
346 TypeParameters = typeParameters;
347 TypeConstraints = typeConstraints;
166 348 }
167 349 }
168 350 }
Modified XFEExtension.NetCore.ServerInteractive.TServer/Services/TestCoreService.cs +6 -4
@@ -1,12 +1,14 @@
1 using XFEExtension.NetCore.ServerInteractive.Implements.CoreService;
1 using XFEExtension.NetCore.ServerInteractive.Attributes;
2 using XFEExtension.NetCore.ServerInteractive.Implements.CoreService;
2 3
3 4 namespace XFEExtension.NetCore.ServerInteractive.TServer.Services;
4 5
5 public class TestCoreService : ServerCoreStandardServiceBase
6 public partial class TestCoreService : ServerCoreStandardServiceBase
6 7 {
7 public override async Task RequestReceiveAsync()
8 [EntryPoint("test")]
9 public async Task TestEntryPoint()
8 10 {
9 Console.Write($"收到方法{Execute}");
11 Console.Write($"收到测试请求");
10 12 await Close("完成");
11 13 }
12 14 }
Modified XFEExtension.NetCore.ServerInteractive/Utilities/Extensions/XFEServerCoreBuilderExtensions.cs +8 -5
@@ -63,15 +63,17 @@ public static class XFEServerCoreBuilderExtensions
63 63 /// </summary>
64 64 /// <typeparam name="T">登录返回用户接口类型</typeparam>
65 65 /// <returns></returns>
66 public XFEServerCoreBuilder AddStandardLoginService<T>() where T : class => xFEServerCoreBuilder.AddStandardService<UserLoginService<T>>("login")
67 .AddStandardService<UserReloginService<T>>("relogin")
66 public XFEServerCoreBuilder AddStandardLoginService<T>() where T : class => xFEServerCoreBuilder.AddStandardService<UserLoginService<T>>("user/login")
67 .AddStandardService<UserReloginService<T>>("user/relogin")
68 68 .AddService<UserLoginAutoCleanService>();
69 69
70 70 /// <summary>
71 71 /// 添加IP封禁服务
72 72 /// </summary>
73 73 /// <returns></returns>
74 public XFEServerCoreBuilder AddIPBannerService() => xFEServerCoreBuilder.AddStandardService<IPBannerService>(["get_bannedIPList", "add_bannedIP", "remove_bannedIP"]);
74 public XFEServerCoreBuilder AddIPBannerService() => xFEServerCoreBuilder.AddStandardService<IPBannerService>("ip/banned/get")
75 .AddStandardService<IPBannerService>("ip/banned/add")
76 .AddStandardService<IPBannerService>("ip/banned/remove");
75 77
76 78 /// <summary>
77 79 /// 添加日期统计服务
@@ -89,7 +91,7 @@ public static class XFEServerCoreBuilderExtensions
89 91 /// 添加连接检查服务
90 92 /// </summary>
91 93 /// <returns></returns>
92 public XFEServerCoreBuilder AddConnectService() => xFEServerCoreBuilder.AddStandardService<ConnectService>("check_connect");
94 public XFEServerCoreBuilder AddConnectService() => xFEServerCoreBuilder.AddStandardService<ConnectService>("connect");
93 95
94 96 /// <summary>
95 97 /// 添加服务器入口点校验
@@ -101,7 +103,8 @@ public static class XFEServerCoreBuilderExtensions
101 103 /// 添加服务器日志请求
102 104 /// </summary>
103 105 /// <returns></returns>
104 public XFEServerCoreBuilder AddServerLogService() => xFEServerCoreBuilder.AddStandardService<CoreLogService>(["get_log", "clear_log"]);
106 public XFEServerCoreBuilder AddServerLogService() => xFEServerCoreBuilder.AddStandardService<CoreLogService>("log/get")
107 .AddStandardService<CoreLogService>("log/clear");
105 108
106 109 /// <summary>
107 110 /// 使用XFE标准服务器核心