XFEExtension.NetCore.ServerInteractive
[DLL] Server interaction extension, including user identity verification and querying in conjunction with AutoConfig
关注
0
Fork
0
Star
0
返回提交历史
Modified
XFEExtension.NetCore.ServerInteractive.SourceGenerator/EntryPointGenerator.cs
+210
-28
Modified
XFEExtension.NetCore.ServerInteractive.TServer/Services/TestCoreService.cs
+6
-4
Modified
XFEExtension.NetCore.ServerInteractive/Utilities/Extensions/XFEServerCoreBuilderExtensions.cs
+8
-5
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
代码差异
3 个文件
+224
-37
@@ -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
}
@@ -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
}
@@ -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标准服务器核心