Merge remote-tracking branch 'origin/dev' into cody/netwrok_SDK

# Conflicts:
#	apps/im_app/lib/features/chat/presentation/chat_db_test_view_model.dart
#	apps/im_app/lib/features/login/presentation/login_view_model.dart

修复逻辑漏洞,性能优化
This commit is contained in:
Cody
2026-03-08 20:47:28 +08:00
88 changed files with 5695 additions and 593 deletions

View File

@@ -4,17 +4,50 @@ import 'package:networks_sdk/src/annotations/api_request.dart';
import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart';
/// @JsonKey 检测器(用于读取字段的 JSON 键名映射)
const _jsonKeyChecker = TypeChecker.fromUrl(
'package:json_annotation/src/json_key.dart#JsonKey',
);
/// @ApiRequest 代码生成器
///
/// 为标注了 `@ApiRequest` 的类自动生成 mixin提供
/// - `path`, `method`, `requestType`, `includeToken` 协议实现
/// - 自动注册响应类型的 `fromJson`(在 `parameters` getter 中触发
/// 保证首次请求前完成注册,无需手动调用 `registerApiResponses()`
/// - `toJson()` — 从类的声明字段自动生成,只序列化自身字段
/// 不含 ApiRequestable 的继承属性,避免递归
/// - 自动注册响应类型的 `fromJson`(在 `parameters` getter 中触发)
///
/// 生成的 mixin 命名规则:`_$<ClassName>Api`
/// 支持 `@JsonKey(name: '...')` 字段重命名。
/// 如有 `@JsonKey(includeToJson: false)` 则跳过该字段。
///
/// ## 使用模式
///
/// Request 类只需 `@ApiRequest` 注解,无需 `@JsonSerializable`
///
/// ```dart
/// @ApiRequest(
/// path: ApiPaths.authLogin,
/// method: HttpMethod.post,
/// responseType: LoginData,
/// requestType: ApiRequestType.login,
/// )
/// class LoginRequest extends ApiRequestable<LoginData>
/// with _$LoginRequestApi {
/// final String email;
/// final String password;
///
/// LoginRequest({required this.email, required this.password});
/// // 完毕toJson / path / method 全部由 mixin 自动生成
/// // Response 的 fromJson 在 parameters getter 中自动注册
/// }
/// ```
///
/// ## mixin 命名规则
///
/// `_$<ClassName>Api`
///
/// ## 生成示例
///
/// 示例输出:
/// ```dart
/// mixin _$LoginRequestApi on ApiRequestable<LoginData> {
/// @override String get path => '/auth/login';
@@ -22,17 +55,29 @@ import 'package:build/build.dart';
/// @override ApiRequestType get requestType => ApiRequestType.login;
/// @override bool get includeToken => false;
/// @override
/// Map<String, dynamic> toJson() => <String, dynamic>{
/// 'email': (this as LoginRequest).email,
/// 'password': (this as LoginRequest).password,
/// };
/// @override
/// Map<String, dynamic>? get parameters {
/// registerResponse<LoginData>(LoginData.fromJson);
/// return super.parameters;
/// }
/// }
/// ```
class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest>
{
///
/// ## Upload 等特殊请求
///
/// 如需自定义 toJson如 upload 返回空 map在类中 override 即可,
/// 类的 override 优先于 mixin。
class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest> {
@override
String generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep,)
{
String generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
if (element is! ClassElement) {
throw InvalidGenerationSourceError(
'@ApiRequest can only be applied to classes.',
@@ -40,7 +85,7 @@ class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest>
);
}
final className = element.name;
final className = element.name!;
final path = annotation.read('path').stringValue;
// 读取 HttpMethod 枚举值
@@ -68,6 +113,9 @@ class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest>
includeToken = requestTypeName != 'login';
}
// 从类的声明字段生成 toJson()
final toJsonBody = _buildToJsonBody(element, className);
return '''
/// Generated by @ApiRequest for [$className]
mixin _\$${className}Api on ApiRequestable<$responseTypeName> {
@@ -80,6 +128,8 @@ mixin _\$${className}Api on ApiRequestable<$responseTypeName> {
@override
bool get includeToken => $includeToken;
@override
Map<String, dynamic> toJson() => $toJsonBody;
@override
Map<String, dynamic>? get parameters {
registerResponse<$responseTypeName>($responseTypeName.fromJson);
return super.parameters;
@@ -88,6 +138,46 @@ mixin _\$${className}Api on ApiRequestable<$responseTypeName> {
''';
}
/// 从类的声明字段构建 toJson() 方法体
///
/// 只读取类自身声明的实例字段(非 static、非 synthetic
/// 不含继承自 ApiRequestable 的属性,避免递归。
/// 支持 @JsonKey(name: '...') 字段重命名,
/// 以及 @JsonKey(includeToJson: false) 跳过字段。
String _buildToJsonBody(ClassElement element, String className) {
final fields = element.fields
.where((f) => !f.isStatic && !f.isSynthetic)
.toList();
if (fields.isEmpty) {
return '<String, dynamic>{}';
}
final entries = <String>[];
for (final field in fields) {
// 检查 @JsonKey 注解
final jsonKeyAnnotation = _jsonKeyChecker.firstAnnotationOfExact(field);
// @JsonKey(includeToJson: false) → 跳过
final includeToJson = jsonKeyAnnotation
?.getField('includeToJson')
?.toBoolValue();
if (includeToJson == false) continue;
// JSON 键名:@JsonKey(name: '...') 或字段名
final jsonName =
jsonKeyAnnotation?.getField('name')?.toStringValue() ?? field.name;
entries.add("'$jsonName': (this as $className).${field.name}");
}
if (entries.isEmpty) {
return '<String, dynamic>{}';
}
return '<String, dynamic>{${entries.join(', ')}}';
}
/// 从 DartObject 提取枚举常量名称
String _readEnumName(dynamic dartObject, String defaultValue) {
final index = dartObject.getField('index')?.toIntValue();
@@ -105,4 +195,4 @@ mixin _\$${className}Api on ApiRequestable<$responseTypeName> {
return defaultValue;
}
}
}