diff --git a/Doc/IM_App_架构设计.html b/Doc/IM_App_架构设计.html index 12edacb..698760e 100644 --- a/Doc/IM_App_架构设计.html +++ b/Doc/IM_App_架构设计.html @@ -886,8 +886,9 @@ flowchart TD │ ├── annotations/ │ │ └── api_request.dart # @ApiRequest 注解定义 │ ├── generator/ -│ │ ├── api_request_generator.dart # build_runner 代码生成器实现 -│ │ └── builder.dart # SharedPartBuilder 入口 +│ │ ├── api_request_generator.dart # Request mixin 生成器(toJson / path / method / fromJson 注册) +│ │ ├── api_response_generator.dart # Response fromJson 生成器(从 @ApiRequest(responseType) 自动推导,递归嵌套类型) +│ │ └── builder.dart # SharedPartBuilder 入口(两个生成器合并到同一 .g.dart) │ ├── data/ │ │ ├── datasources/ │ │ │ ├── http/ @@ -2286,23 +2287,21 @@ extension APIRequestableExtension<T> on APIRequestable<T> {

一个端点 = 一个文件(data/remote/login_request.dart),Response DTO + Request 放在同一文件中。

-
import 'package:json_annotation/json_annotation.dart';
-import 'package:networks_sdk/networks_sdk.dart';
+
import 'package:networks_sdk/networks_sdk.dart';
 
 part 'login_request.g.dart';
 
-// ── Response DTO ──
+// ── Response DTO(纯 Dart 类,零注解,零样板)──
+// _$LoginResponseFromJson 由 ApiResponseGenerator 从 @ApiRequest(responseType: T) 自动推导生成
 
-@JsonSerializable()
-class LoginData {
+class LoginResponse {
   final String token;
-  @JsonKey(name: 'user_id')
+  @JsonKey(name: 'user_id')    // @JsonKey 由生成器读取,Response 类不需要 @JsonSerializable
   final String userId;
   final String email;
 
-  const LoginData({required this.token, required this.userId, required this.email});
-  factory LoginData.fromJson(Map<String, dynamic> json) => _$LoginDataFromJson(json);
-  Map<String, dynamic> toJson() => _$LoginDataToJson(this);
+  const LoginResponse({required this.token, required this.userId, required this.email});
+  // 无 factory fromJson — 生成器在 .g.dart 中提供私有 _$LoginResponseFromJson
 
   User toEntity() => User(id: userId, email: email); // DTO → Domain Entity
 }
@@ -2313,23 +2312,23 @@ class LoginData {
 @ApiRequest(
   path: ApiPaths.authLogin,                // 路径统一在 core/foundation/api_paths.dart 管理
   method: HttpMethod.post,
-  responseType: LoginData,
+  responseType: LoginResponse,
   requestType: ApiRequestType.login,
 )
-class LoginRequest extends ApiRequestable<LoginData> with _$LoginRequestApi {
+class LoginRequest extends ApiRequestable<LoginResponse> with _$LoginRequestApi {
   final String email;
   final String password;
 
   LoginRequest({required this.email, required this.password});
-  // 完毕!toJson 由 mixin 从类字段自动生成,fromJson 不需要(Request 永远手动构造)
+  // 完毕!toJson 由 mixin 从类字段自动生成,fromJson 注册也全部自动处理
 }
 
// 使用 - 超级简单!
-final loginData = await apiClient.executeRequest(
+final loginResponse = await apiClient.executeRequest(
   LoginRequest(email: 'user@example.com', password: '123456'),
 );
-final user = loginData?.toEntity(); // DTO → Domain Entity
+final user = loginResponse?.toEntity(); // DTO → Domain Entity
 
@@ -2359,8 +2358,8 @@ final user = loginData?.toEntity(); // DTO → Domain Entity @ApiRequest(当前方案) -字段 + 构造函数 + @ApiRequest -path / method / requestType / includeToken / toJson / fromJson 注册 +字段 + 构造函数 + @ApiRequest
(Response DTO:字段 + 构造函数,零注解) +Request: path / method / requestType / includeToken / toJson / fromJson 注册
Response: _$XFromJson 私有反序列化函数(按需递归生成嵌套类型) 极低 @@ -2368,10 +2367,10 @@ final user = loginData?.toEntity(); // DTO → Domain Entity

核心优势

    -
  • 注解驱动@ApiRequest 一个注解自动生成 mixin(含 toJson),无需 @JsonSerializable
  • +
  • Request 零样板@ApiRequest 一个注解生成 mixin(含 toJson),无需 @JsonSerializable
  • +
  • Response 零注解:Response DTO 不需要 @JsonSerializable,不需要 factory fromJson_$XFromJson 由生成器从 @ApiRequest(responseType: T) 自动推导;嵌套自定义类型递归生成,依赖关系自动处理
  • 自动注册:fromJson 在首次请求时自动注册到全局注册表,无需手动 registerApiResponses()
  • 一个端点 = 一个文件:Response DTO + Request 放在同一文件,打开即看全貌
  • -
  • 傻瓜式使用:使用者只需关注业务字段和注解配置
  • 类型安全ApiRequestable<T> 泛型 + responseType 编译期检查
@@ -2387,12 +2386,12 @@ final user = loginData?.toEntity(); // DTO → Domain Entity Swift -struct LoginRequest: APIRequestable { typealias Response = LoginData ... } +struct LoginRequest: APIRequestable { typealias Response = LoginResponse ... } 协议直接实现,最简洁 Dart -@ApiRequest(...) class LoginRequest extends ApiRequestable<LoginData> with _$LoginRequestApi { ... } +@ApiRequest(...) class LoginRequest extends ApiRequestable<LoginResponse> with _$LoginRequestApi { ... } 注解 + 代码生成,接近 Swift 体验 @@ -2530,26 +2529,23 @@ melos run gen
4.5 更多使用示例
-

所有示例遵循同一模式:@ApiRequest + extends ApiRequestable<T> with _$XxxApi。Request 类无需 @JsonSerializable

+

所有示例遵循同一模式:@ApiRequest + extends ApiRequestable<T> with _$XxxApi。Request 和 Response 均无需 @JsonSerializable,Response DTO 连 factory fromJson 也不需要。

发送消息请求(POST + @JsonKey 字段重命名):

// data/remote/send_message_request.dart
 
-// ── Response DTO(仍用 @JsonSerializable)──
-@JsonSerializable()
-class SendMessageData {
+// ── Response DTO(纯 Dart 类,零注解,_$SendMessageResponseFromJson 由生成器自动提供)──
+class SendMessageResponse {
   @JsonKey(name: 'message_id')
   final String messageId;
   final int timestamp;
 
-  const SendMessageData({required this.messageId, required this.timestamp});
-  factory SendMessageData.fromJson(Map<String, dynamic> json) =>
-      _$SendMessageDataFromJson(json);
+  const SendMessageResponse({required this.messageId, required this.timestamp});
 }
 
 // ── Request(零样板)──
-@ApiRequest(path: ApiPaths.chatSendMessage, responseType: SendMessageData)
-class SendMessageRequest extends ApiRequestable<SendMessageData>
+@ApiRequest(path: ApiPaths.chatSendMessage, responseType: SendMessageResponse)
+class SendMessageRequest extends ApiRequestable<SendMessageResponse>
     with _$SendMessageRequestApi {
   @JsonKey(name: 'chat_id')  // 生成器会读取,JSON 键名为 'chat_id'
   final String chatId;
@@ -2563,23 +2559,21 @@ class SendMessageRequest extends ApiRequestable<SendMessageData>
 

获取用户资料(GET,靠 token 标识当前用户,无需传参):

// data/remote/get_profile_request.dart
 
-@JsonSerializable(createToJson: false)  // 只需反序列化
-class ProfileData {
+// ── Response DTO(纯 Dart 类,零注解,_$ProfileResponseFromJson 由生成器自动提供)──
+class ProfileResponse {
   @JsonKey(name: 'user_id')
   final String userId;
   final String email;
   final String? nickname;
   final String? avatar;
 
-  const ProfileData({required this.userId, required this.email, this.nickname, this.avatar});
-  factory ProfileData.fromJson(Map<String, dynamic> json) =>
-      _$ProfileDataFromJson(json);
+  const ProfileResponse({required this.userId, required this.email, this.nickname, this.avatar});
 
   User toEntity() => User(id: userId, email: email, nickname: nickname, avatar: avatar);
 }
 
-@ApiRequest(path: ApiPaths.userProfile, method: HttpMethod.get, responseType: ProfileData)
-class GetProfileRequest extends ApiRequestable<ProfileData>
+@ApiRequest(path: ApiPaths.userProfile, method: HttpMethod.get, responseType: ProfileResponse)
+class GetProfileRequest extends ApiRequestable<ProfileResponse>
     with _$GetProfileRequestApi {
   GetProfileRequest();  // 无参数 — toJson 自动生成空 map
 }
@@ -2588,14 +2582,12 @@ class GetProfileRequest extends ApiRequestable<ProfileData>
 

上传文件请求(FormData multipart):

// data/remote/upload_file_request.dart
 
-@JsonSerializable()
+// ── Response DTO(纯 Dart 类,零注解,_$UploadResultFromJson 由生成器自动提供)──
 class UploadResult {
   final String url;
   @JsonKey(name: 'file_id')
   final String fileId;
   const UploadResult({required this.url, required this.fileId});
-  factory UploadResult.fromJson(Map<String, dynamic> json) =>
-      _$UploadResultFromJson(json);
 }
 
 @ApiRequest(
@@ -2624,7 +2616,8 @@ class UploadFileRequest extends ApiRequestable<UploadResult>
 

核心价值

    -
  • 极简使用:字段 + 构造函数 + @ApiRequest(Request 无需 @JsonSerializable、无需 fromJson、无需手写 toJson
  • +
  • Request 极简:字段 + 构造函数 + @ApiRequest(无需 @JsonSerializable、无需手写 toJson
  • +
  • Response 零注解:Response DTO 是纯 Dart 类,无需 @JsonSerializable,无需 factory fromJson_$XFromJson 由生成器从 responseType 自动推导
  • 零维护:path / method / requestType / includeToken / toJson / fromJson 注册 全部自动生成
  • 类型安全:泛型 ApiRequestable<T> + responseType 编译期检查
  • 一个端点 = 一个文件:Response DTO + Request 放在同一文件,打开即看全貌
  • @@ -2721,11 +2714,11 @@ class LoginViewModel extends _$LoginViewModel { → LoggingInterceptor ← 请求/响应日志 ← request.decodeResponse(response) ← 自动解码 ← ApiResponseWrapper.fromJson ← 拆 { code, msg, data } - ← fromJsonRegistry[LoginData] ← 查注册表 - ← LoginData.fromJson(data) ← 反序列化 - ← LoginData(DTO) + ← fromJsonRegistry[LoginResponse] ← 查注册表 + ← _$LoginResponseFromJson(data) ← 反序列化(生成的私有函数) + ← LoginResponse(DTO) → onTokenUpdate(token) ← 回调写入 Token(内存 + 持久化) - ← loginData.toEntity() → User ← DTO → Domain Entity + ← loginResponse.toEntity() → User ← DTO → Domain Entity ← User ← state.copyWith(user: user) ← 更新状态 View: ref.watch → 自动 rebuild ← UI 刷新 @@ -5715,7 +5708,7 @@ class MessageLocalDataSource { class MessageRepositoryImpl implements MessageRepository { final NetworksSdkApi _client; - Future<SendMessageData?> sendMessage({ + Future<SendMessageResponse?> sendMessage({ required String chatId, required String content, }) { @@ -5978,7 +5971,7 @@ flowchart LR 层级文件命名类命名示例 -接口定义{action}_request.dartRequest: {Action}Request
    Response DTO: {Action}Datalogin_request.dartLoginRequest + LoginData +接口定义{action}_request.dartRequest: {Action}Request
    Response DTO: {Action}Responselogin_request.dartLoginRequest + LoginResponse 持久化 DTOdata/models/{entity}_dto.dart{Entity}Dtouser_dto.dartUserDto Repository 接口domain/repositories/{module}_repository.dart{Module}Repositoryauth_repository.dartAuthRepository Repository 实现data/repositories/{module}_repository_impl.dart{Module}RepositoryImplauth_repository_impl.dartAuthRepositoryImpl @@ -5990,8 +5983,8 @@ flowchart LR

    关键规则

    • 一个端点 = 一个 Request 文件:Response DTO + Request 类放在同一文件中
    • -
    • Response DTO 必须有 toEntity():统一 DTO → Domain Entity 的转换入口
    • -
    • 持久化 DTO 和 Response DTO 分开:Response DTO(XxxData)在 request 文件中,持久化 DTO(XxxDto)在 data/models/
    • +
    • Response DTO 是纯 Dart 类:零注解、零 factory fromJson;只需字段 + 构造函数,toEntity() 按需添加
    • +
    • 持久化 DTO 和 Response DTO 分开:Response DTO(XxxResponse)在 request 文件中,持久化 DTO(XxxDto)在 data/models/
    • 禁止跳层:ViewModel → Repository(→ UseCase 按需)→ NetworksSdkApi,每层职责明确
    @@ -6041,8 +6034,10 @@ part 'login_request.g.dart'; // 这行必须写!指向即将自动生成的
    // ── Response DTO ──
     
     /// 服务端返回的登录数据
    -@JsonSerializable()                       // ← 这个注解让 build_runner 自动生成 fromJson / toJson
    -class LoginData {
    +/// 纯 Dart 类,零注解,零样板。
    +/// _$LoginResponseFromJson 由 ApiResponseGenerator 从 @ApiRequest(responseType: LoginResponse) 自动推导生成,
    +/// 无需手动添加任何注解或 factory fromJson。
    +class LoginResponse {
       final String token;                      // 服务端返回的字段
       @JsonKey(name: 'user_id')               // 服务端字段名是 user_id,Dart 字段名是 userId
       final String userId;
    @@ -6050,18 +6045,15 @@ class LoginData {
       final String? nickname;                  // 可选字段用 String?
       final String? avatar;
     
    -  const LoginData({                        // 构造函数,参数和字段一一对应
    +  const LoginResponse({                    // 构造函数,参数和字段一一对应
         required this.token,
         required this.userId,
         required this.email,
         this.nickname,
         this.avatar,
       });
    -
    -  // ↓ 这两行是固定写法,照抄就行,把类名替换掉
    -  factory LoginData.fromJson(Map<String, dynamic> json) =>
    -      _$LoginDataFromJson(json);           // ← 短暂报红,watch 模式下保存后几秒自动消失
    -  Map<String, dynamic> toJson() => _$LoginDataToJson(this);
    +  // 不需要 factory fromJson,不需要 toJson,不需要任何注解
    +  // 生成器自动在 login_request.g.dart 中生成 _$LoginResponseFromJson 私有函数
     
       /// DTO → Domain Entity(把网络层数据转为业务层数据)
       User toEntity() {
    @@ -6103,19 +6095,16 @@ class LoginData {
     @ApiRequest(                               // ← 这个注解让 build_runner 自动生成 path / method 等
       path: ApiPaths.authLogin,                //    路径常量,定义在 core/foundation/api_paths.dart
       method: HttpMethod.post,                 //    HTTP 方法,从接口文档抄
    -  responseType: LoginData,                 //    响应类型,就是上面写的 LoginData
    +  responseType: LoginResponse,             //    响应类型,就是上面写的 LoginResponse
       requestType: ApiRequestType.login,       //    login 类型不携带 Token(登录前还没有 Token)
     )
    -@JsonSerializable()                        // ← 自动生成 toJson(把请求参数序列化为 JSON)
    -class LoginRequest extends ApiRequestable<LoginData>  // ← 固定写法:extends ApiRequestable<响应类型>
    -    with _$LoginRequestApi {                           // ← 固定写法:with _$类名Api(短暂报红,保存后自动消失)
    +class LoginRequest extends ApiRequestable<LoginResponse>  // ← 固定写法:extends ApiRequestable<响应类型>
    +    with _$LoginRequestApi {                              // ← 固定写法:with _$类名Api(短暂报红,保存后自动消失)
       final String email;                      // 请求参数:要发给服务端的字段
       final String password;
     
       LoginRequest({required this.email, required this.password});
    -
    -  @override
    -  Map<String, dynamic> toJson() => _$LoginRequestToJson(this);  // ← 固定写法,短暂报红,保存后自动消失
    +  // 完毕!toJson 由 _$LoginRequestApi mixin 从类字段自动生成,不需要 @JsonSerializable,不需要手写 toJson
     }
     
    @@ -6124,14 +6113,14 @@ class LoginRequest extends ApiRequestable<LoginData> // ← 固定写法

    命名规则速查(写之前就能确定引用名)

    - + - - - + + +
    你写的类名fromJsontoJsonApi mixin
    你写的类名fromJson(私有函数)toJsonApi mixin来源
    LoginData_$LoginDataFromJson_$LoginDataToJson-
    LoginRequest_$LoginRequestFromJson_$LoginRequestToJson_$LoginRequestApi
    SendMessageRequest_$SendMessageRequestFromJson_$SendMessageRequestToJson_$SendMessageRequestApi
    LoginResponse_$LoginResponseFromJson-(不需要)-ApiResponseGenerator 自动推导
    LoginRequest-(不需要)由 mixin 自动生成_$LoginRequestApiApiRequestGenerator 生成 mixin
    SendMessageRequest-(不需要)由 mixin 自动生成_$SendMessageRequestApiApiRequestGenerator 生成 mixin
    -

    规则:_$ + 类名 + FromJson / ToJson / Api。固定前缀,直接拼。

    +

    规则:Response DTO 类名拼 _$ + 类名 + FromJson(私有,只在 .g.dart 内部使用);Request 类名拼 _$ + 类名 + Api(mixin)。

    @@ -6162,19 +6151,19 @@ class AuthRepositoryImpl implements AuthRepository { required String password, }) async { // 1. 调 NetworksSdkApi,构造请求 → 发 HTTP → 自动解码 → 返回 DTO - final LoginData? loginData = await _client.executeRequest( + final LoginResponse? loginResponse = await _client.executeRequest( LoginRequest(email: email, password: password), ); - if (loginData == null) { + if (loginResponse == null) { throw Exception('Login failed: empty response'); } // 2. 回调写入 Token(内存 + 持久化由 Provider 层组合) - _onTokenUpdate(loginData.token); + _onTokenUpdate(loginResponse.token); // 3. DTO → Domain Entity,返回给上层 - return loginData.toEntity(); + return loginResponse.toEntity(); } }
@@ -6321,10 +6310,10 @@ class LoginViewModel extends _$LoginViewModel { → AuthRepositoryImpl.login() // data/repositories/ → _client.executeRequest(LoginRequest) // 调 NetworksSdkApi → Auth → Encryption → Dio.request → Retry → Logging // 拦截器链自动处理 - ← request.decodeResponse → LoginData.fromJson // 自动解码 - ← LoginData(DTO) + ← request.decodeResponse → _$LoginResponseFromJson // 自动解码(生成的私有函数) + ← LoginResponse(DTO) → onTokenUpdate(token) // 回调:内存写入 + 持久化 - ← loginData.toEntity() → User(Domain Entity) + ← loginResponse.toEntity() → User(Domain Entity) ← User ← state = state.copyWith(user: user) // 更新状态 ← View: ref.watch → 自动 rebuild → UI 显示用户信息 // 自动刷新 @@ -6345,22 +6334,18 @@ class LoginViewModel extends _$LoginViewModel {

你只需创建一个文件lib/data/remote/send_message_request.dart,然后在 Repository 中调用即可。

-
import 'package:json_annotation/json_annotation.dart';
-import 'package:networks_sdk/networks_sdk.dart';
+
import 'package:networks_sdk/networks_sdk.dart';
 
 part 'send_message_request.g.dart';
 
-// ── Response DTO ──
+// ── Response DTO(纯 Dart 类,零注解,_$SendMessageResponseFromJson 由生成器自动提供)──
 
-@JsonSerializable()
-class SendMessageData {
+class SendMessageResponse {
   @JsonKey(name: 'message_id')
   final String messageId;
   final int timestamp;
 
-  const SendMessageData({required this.messageId, required this.timestamp});
-  factory SendMessageData.fromJson(Map<String, dynamic> json) =>
-      _$SendMessageDataFromJson(json);
+  const SendMessageResponse({required this.messageId, required this.timestamp});
 }
 
 // ── Request ──
@@ -6368,26 +6353,24 @@ class SendMessageData {
 @ApiRequest(
   path: ApiPaths.chatSendMessage,           // 路径常量,定义在 api_paths.dart
   method: HttpMethod.post,                 // HTTP 方法
-  responseType: SendMessageData,           // 响应类型
+  responseType: SendMessageResponse,       // 响应类型
   // requestType 不写,默认 ApiRequestType.request(会携带 Token)
 )
-@JsonSerializable()
-class SendMessageRequest extends ApiRequestable<SendMessageData>
+class SendMessageRequest extends ApiRequestable<SendMessageResponse>
     with _$SendMessageRequestApi {
   @JsonKey(name: 'chat_id')               // JSON 字段名和 Dart 字段名不一样时用 @JsonKey
   final String chatId;
   final String content;
 
   SendMessageRequest({required this.chatId, required this.content});
-  @override
-  Map<String, dynamic> toJson() => _$SendMessageRequestToJson(this);
+  // toJson 自动生成:{'chat_id': chatId, 'content': content}
 }
 

保存 → 自动生成 → 然后在 Repository 中调 NetworksSdkApi 就完了:

// 在 MessageRepositoryImpl 中添加
-Future<SendMessageData?> sendMessage({
+Future<SendMessageResponse?> sendMessage({
   required String chatId,
   required String content,
 }) {
@@ -6407,22 +6390,19 @@ Future<SendMessageData?> sendMessage({
 
 
// lib/data/remote/get_profile_request.dart
 
-import 'package:json_annotation/json_annotation.dart';
 import 'package:networks_sdk/networks_sdk.dart';
 
 part 'get_profile_request.g.dart';
 
-@JsonSerializable()
-class ProfileData {
+// ── Response DTO(纯 Dart 类,零注解,_$ProfileResponseFromJson 由生成器自动提供)──
+class ProfileResponse {
   @JsonKey(name: 'user_id')
   final String userId;
   final String email;
   final String? nickname;
   final String? avatar;
 
-  const ProfileData({required this.userId, required this.email, this.nickname, this.avatar});
-  factory ProfileData.fromJson(Map<String, dynamic> json) =>
-      _$ProfileDataFromJson(json);
+  const ProfileResponse({required this.userId, required this.email, this.nickname, this.avatar});
 
   User toEntity() => User(id: userId, email: email, nickname: nickname, avatar: avatar);
 }
@@ -6430,15 +6410,12 @@ class ProfileData {
 @ApiRequest(
   path: ApiPaths.userProfile,
   method: HttpMethod.get,                  // ← GET 请求,toJson() 结果作为 query string
-  responseType: ProfileData,
+  responseType: ProfileResponse,
 )
-@JsonSerializable()
-class GetProfileRequest extends ApiRequestable<ProfileData>
+class GetProfileRequest extends ApiRequestable<ProfileResponse>
     with _$GetProfileRequestApi {
   GetProfileRequest();  // 无参数 — token 标识当前用户,无需显式传 user_id
-
-  @override
-  Map<String, dynamic> toJson() => _$GetProfileRequestToJson(this);
+  // toJson 自动生成空 map {}
 }
 
@@ -6685,41 +6662,33 @@ melos run gen:watch
// data/remote/login_request.dart
 
-import 'package:json_annotation/json_annotation.dart';
 import 'package:networks_sdk/networks_sdk.dart';
 
 part 'login_request.g.dart';  // ← 必须写,指向即将生成的文件
 
-// ── Response DTO ──
-@JsonSerializable()
-class LoginData {
+// ── Response DTO(纯 Dart 类,零注解,零样板)──
+// _$LoginResponseFromJson 由 ApiResponseGenerator 从 @ApiRequest(responseType: LoginResponse) 自动推导生成
+class LoginResponse {
   final String token;
   final String email;
-  const LoginData({required this.token, required this.email});
-
-  // ↓ 此时 _$LoginDataFromJson 还不存在,IDE 会报红,正常!
-  factory LoginData.fromJson(Map<String, dynamic> json) =>
-      _$LoginDataFromJson(json);
-  Map<String, dynamic> toJson() => _$LoginDataToJson(this);
+  const LoginResponse({required this.token, required this.email});
+  // 不需要 factory fromJson,不需要任何注解
 }
 
 // ── Request ──
 @ApiRequest(
   path: ApiPaths.authLogin,
   method: HttpMethod.post,
-  responseType: LoginData,
+  responseType: LoginResponse,
   requestType: ApiRequestType.login,
 )
-@JsonSerializable()
-class LoginRequest extends ApiRequestable<LoginData>
+class LoginRequest extends ApiRequestable<LoginResponse>
     with _$LoginRequestApi {                // ← 短暂报红,保存后自动消失
   final String email;
   final String password;
 
   LoginRequest({required this.email, required this.password});
-
-  @override
-  Map<String, dynamic> toJson() => _$LoginRequestToJson(this);  // ← 短暂报红,保存后自动消失
+  // toJson 由 mixin 自动生成,保存后红线自动消失
 }
 
@@ -6734,11 +6703,10 @@ class LoginRequest extends ApiRequestable<LoginData>

命名规则(写之前就能确定引用名)

- + - - - + +
注解生成的符号示例
来源生成的符号示例
@JsonSerializable()_$类名FromJson()_$LoginDataFromJson(json)
@JsonSerializable()_$类名ToJson()_$LoginDataToJson(this)
@ApiRequest(...)_$类名Api(mixin)_$LoginRequestApi
@ApiRequest(responseType: T)
(ApiResponseGenerator 推导)
_$类名FromJson(私有函数,.g.dart 内部使用)_$LoginResponseFromJson(json)
@ApiRequest(...) on Request 类
(ApiRequestGenerator 生成 mixin)
_$类名Api(mixin)_$LoginRequestApi
diff --git a/apps/im_app/lib/data/remote/get_profile_request.dart b/apps/im_app/lib/data/remote/get_profile_request.dart index c51ed81..5030fec 100644 --- a/apps/im_app/lib/data/remote/get_profile_request.dart +++ b/apps/im_app/lib/data/remote/get_profile_request.dart @@ -6,28 +6,30 @@ import '../../../domain/entities/user.dart'; part 'get_profile_request.g.dart'; -/// # /user/profile — 获取用户资料(GET 请求示例) +/// # /user/profile — 获取用户资料(GET 请求) /// -/// 演示:GET 请求 + 无 body 参数的模式。 -/// GET 请求的 toJson() 结果会自动作为 URL query parameters 发送。 +/// GET 请求无 body,`toJson()` 结果自动作为 URL query parameters 发送。 +/// 如需 query 参数(如分页),直接在类中添加字段,生成器自动序列化。 /// /// ## 数据流位置 /// /// ``` /// UserRepositoryImpl.getProfile() -/// → _client.executeRequest( ★ GetProfileRequest ★ ) ← 你在这里 +/// → _client.executeRequest( ★ GetProfileRequest ★ ) ← 你在这里 /// → 服务端 GET /user/profile -/// → 响应 JSON → ★ ProfileData ★ ← 也在这里 -/// → ProfileData.toEntity() → User +/// → SDK 内部 ApiResponseWrapper 拆包 { code, message, data } +/// → ★ ProfileResponse ★ = data 字段 ← 也在这里 +/// → ProfileResponse.toEntity() → User /// ``` // ───────────────────────────────────────────── // Response DTO // ───────────────────────────────────────────── -/// 用户资料响应 DTO(只需反序列化,禁止生成无用的 toJson) -@JsonSerializable(createToJson: false) -class ProfileData { +/// 用户资料接口的业务响应数据(对应服务端 `data` 字段)。 +/// +/// `{ code, message }` 由 SDK 内部的 `ApiResponseWrapper` 统一处理。纯 Dart 类,无需任何注解。 +class ProfileResponse { final int uid; final String uuid; @JsonKey(name: 'last_online') @@ -54,7 +56,7 @@ class ProfileData { final int channelGroupId; final String hint; - const ProfileData({ + const ProfileResponse({ required this.uid, required this.uuid, required this.lastOnline, @@ -74,10 +76,6 @@ class ProfileData { required this.hint, }); - factory ProfileData.fromJson(Map json) => - _$ProfileDataFromJson(json); - - /// DTO → Domain Entity User toEntity() => User( uid: uid, uuid: uuid, @@ -104,16 +102,12 @@ class ProfileData { // ───────────────────────────────────────────── /// 获取用户资料请求(GET,无参数) -/// -/// GET 请求无 body,mixin 自动生成 toJson() → 空 map。 -/// 如需 query 参数(如分页),添加字段即可, -/// toJson() 会自动将字段序列化为 URL query string。 @ApiRequest( path: ApiPaths.userProfile, method: HttpMethod.get, - responseType: ProfileData, + responseType: ProfileResponse, ) -class GetProfileRequest extends ApiRequestable +class GetProfileRequest extends ApiRequestable with _$GetProfileRequestApi { GetProfileRequest(); } diff --git a/apps/im_app/lib/data/remote/login_request.dart b/apps/im_app/lib/data/remote/login_request.dart index 9d1c976..b8b18a7 100644 --- a/apps/im_app/lib/data/remote/login_request.dart +++ b/apps/im_app/lib/data/remote/login_request.dart @@ -12,17 +12,20 @@ part 'login_request.g.dart'; /// /// ``` /// AuthRepositoryImpl.login(email, password) -/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里 +/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里 /// → 服务端 POST /auth/login -/// → 响应 JSON → ★ LoginResponse ★ ← 也在这里 -/// → LoginResponse.toEntity() → User +/// → SDK 内部 ApiResponseWrapper 拆包 { code, message, data } +/// → ★ LoginResponse ★ = data 字段,T in APIResponseWrapper ← 也在这里 +/// → LoginResponse.toEntity() → User /// ``` // ───────────────────────────────────────────── // Response DTO // ───────────────────────────────────────────── -@JsonSerializable(createToJson: false) +/// 登录响应中的用户档案,嵌套在 [LoginResponse.profile] 中。 +/// +/// 纯 Dart 类,无需任何注解。`_$LoginProfileFromJson` 由生成器从 `@ApiRequest` 声明中自动推导生成。 class LoginProfile { final int uid; final String uuid; @@ -70,9 +73,6 @@ class LoginProfile { required this.hint, }); - factory LoginProfile.fromJson(Map json) => - _$LoginProfileFromJson(json); - User toEntity() => User( uid: uid, uuid: uuid, @@ -94,8 +94,11 @@ class LoginProfile { ); } -@JsonSerializable(createToJson: false, explicitToJson: true) -class LoginData { +/// 登录接口的业务响应数据(对应服务端 `data` 字段,即 T in `APIResponseWrapper`)。 +/// +/// `{ code, message }` 由 SDK 内部的 `ApiResponseWrapper` 统一处理, +/// App 层只接触此类,不感知 envelope 结构。纯 Dart 类,无需任何注解。 +class LoginResponse { @JsonKey(name: 'account_id') final String accountId; final LoginProfile profile; @@ -109,7 +112,7 @@ class LoginData { @JsonKey(name: 'login_data') final String loginData; - const LoginData({ + const LoginResponse({ required this.accountId, required this.profile, required this.nonce, @@ -119,31 +122,9 @@ class LoginData { required this.loginData, }); - factory LoginData.fromJson(Map json) => - _$LoginDataFromJson(json); - User toEntity() => profile.toEntity(); } -/// Top-level envelope: { "code": 0, "message": "OK", "data": { ... } } -@JsonSerializable(createToJson: false, explicitToJson: true) -class LoginResponse { - final int code; - final String message; - final LoginData data; - - const LoginResponse({ - required this.code, - required this.message, - required this.data, - }); - - factory LoginResponse.fromJson(Map json) => - _$LoginResponseFromJson(json); - - User toEntity() => data.toEntity(); -} - // ───────────────────────────────────────────── // Request // ───────────────────────────────────────────── @@ -152,9 +133,7 @@ class LoginResponse { /// /// `@ApiRequest` 一个注解搞定一切: /// - mixin 自动生成 path / method / requestType / includeToken / toJson -/// - toJson 只序列化类自身字段(email, password),不含继承属性 -/// - Response 的 fromJson 在 parameters getter 中自动注册 -/// - 无需 @JsonSerializable,无需手写 fromJson / toJson +/// - parameters getter 自动注册 `_$LoginResponseFromJson` 到 SDK 全局注册表 @ApiRequest( path: ApiPaths.authLogin, method: HttpMethod.post, diff --git a/apps/im_app/lib/data/remote/logout_request.dart b/apps/im_app/lib/data/remote/logout_request.dart index a4a908d..651f940 100644 --- a/apps/im_app/lib/data/remote/logout_request.dart +++ b/apps/im_app/lib/data/remote/logout_request.dart @@ -2,14 +2,14 @@ import 'package:networks_sdk/networks_sdk.dart'; import '../../../core/foundation/api_paths.dart'; -/// # /auth/logout — 登出接口(无响应数据示例) +part 'logout_request.g.dart'; + +/// # /auth/logout — 登出接口(无响应数据) /// -/// 演示:POST 请求 + 无 Response DTO 的模式。 /// 服务端返回 `{"code": 0, "message": "ok"}` 无 data 字段, /// `executeRequest` 返回 null,调用方直接 await 即可。 /// -/// 此接口不使用 @ApiRequest 注解,直接实现 ApiRequestable, -/// 演示手动实现方式(适用于不需要代码生成器的简单接口)。 +/// `responseType` 省略 → 生成器跳过 `fromJson` 注册,mixin 泛型为 `void`。 /// /// ## 数据流位置 /// @@ -17,16 +17,9 @@ import '../../../core/foundation/api_paths.dart'; /// AuthRepositoryImpl.logout() /// → _client.executeRequest( ★ LogoutRequest ★ ) ← 你在这里 /// → 服务端 POST /auth/logout -/// → 响应 {"code": 0, "message": "ok"} → null +/// → 响应 {"code": 0, "message": "ok"} → null(无 data) /// ``` -class LogoutRequest extends ApiRequestable { - @override - String get path => ApiPaths.authLogout; - - @override - HttpMethod get method => HttpMethod.post; - - /// 登出不需要请求体参数 - @override - Map toJson() => {}; +@ApiRequest(path: ApiPaths.authLogout, method: HttpMethod.post) +class LogoutRequest extends ApiRequestable with _$LogoutRequestApi { + LogoutRequest(); } diff --git a/apps/im_app/lib/data/remote/upload_file_request.dart b/apps/im_app/lib/data/remote/upload_file_request.dart index abb4440..7917ca4 100644 --- a/apps/im_app/lib/data/remote/upload_file_request.dart +++ b/apps/im_app/lib/data/remote/upload_file_request.dart @@ -32,8 +32,7 @@ part 'upload_file_request.g.dart'; // Response DTO // ───────────────────────────────────────────── -/// 文件上传响应 DTO(只需反序列化,禁止生成无用的 toJson) -@JsonSerializable(createToJson: false) +/// 文件上传接口的业务响应数据(对应服务端 `data` 字段)。纯 Dart 类,无需任何注解。 class UploadResult { final String url; @@ -41,9 +40,6 @@ class UploadResult { final String fileId; const UploadResult({required this.url, required this.fileId}); - - factory UploadResult.fromJson(Map json) => - _$UploadResultFromJson(json); } // ═════════════════════════════════════════════ diff --git a/apps/im_app/lib/data/repositories/auth_repository_impl.dart b/apps/im_app/lib/data/repositories/auth_repository_impl.dart index 0c167cb..afdec66 100644 --- a/apps/im_app/lib/data/repositories/auth_repository_impl.dart +++ b/apps/im_app/lib/data/repositories/auth_repository_impl.dart @@ -8,7 +8,7 @@ import '../remote/logout_request.dart'; /// 认证 Repository 实现 /// /// implements [AuthRepository] 接口(domain/repositories/ 中定义)。 -/// 直接使用 [ApiClient] 发送请求,将 DTO 转为 Domain Entity。 +/// 直接使用 [NetworksSdkApi] 发送请求,将 DTO 转为 Domain Entity。 /// 后续可加 Local DataSource 实现离线缓存。 /// /// ## 数据流位置 @@ -16,11 +16,11 @@ import '../remote/logout_request.dart'; /// ``` /// LoginUseCase.execute(email, password) /// → ★ AuthRepositoryImpl.login() ★ ← 你在这里 -/// → ApiClient.executeRequest(LoginRequest) +/// → NetworksSdkApi.executeRequest(LoginRequest) /// → 服务端 POST /auth/login -/// ← LoginData(Response DTO) -/// → onTokenUpdate(token) ← 回调写入 Token -/// ← LoginData.toEntity() → User ← DTO → Entity 转换在这里 +/// ← LoginResponse(SDK 已拆包 { code, message, data } envelope) +/// → _onTokenUpdate(accessToken) ← 回调写入 Token +/// ← LoginResponse.toEntity() → User ← DTO → Entity 转换在这里 /// ← User(Domain Entity) /// ``` class AuthRepositoryImpl implements AuthRepository { @@ -43,7 +43,7 @@ class AuthRepositoryImpl implements AuthRepository { throw Exception('Login failed: empty response'); } - _onTokenUpdate(loginResponse.data.accessToken); + _onTokenUpdate(loginResponse.accessToken); return loginResponse.toEntity(); } diff --git a/apps/im_app/lib/features/login/presentation/login_view_model.dart b/apps/im_app/lib/features/login/presentation/login_view_model.dart index 7552100..2cf2b45 100644 --- a/apps/im_app/lib/features/login/presentation/login_view_model.dart +++ b/apps/im_app/lib/features/login/presentation/login_view_model.dart @@ -1,10 +1,10 @@ import 'dart:convert'; import 'package:flutter/services.dart'; -import 'package:im_app/data/models/user_dto.dart'; -import 'package:im_app/data/remote/login_request.dart'; -import 'package:networks_sdk/networks_sdk.dart'; import 'package:im_app/app/di/db_provider.dart'; +import 'package:im_app/data/models/user_dto.dart'; +import 'package:im_app/domain/entities/user.dart'; +import 'package:networks_sdk/networks_sdk.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:storage_sdk/storage_sdk.dart'; @@ -44,7 +44,7 @@ part 'login_view_model.g.dart'; /// → LoginUseCase.execute() ← 格式校验 + 调 Repository /// → AuthRepository.login() /// → _client.executeRequest(LoginRequest) -/// ← LoginData → User +/// ← LoginResponse → User /// ← User /// → state = state.copyWith(user: user) ← 更新状态 /// View: ref.watch → 自动 rebuild ← UI 刷新 @@ -67,11 +67,33 @@ class LoginViewModel extends _$LoginViewModel { final storageApi = ref.read(storageSdkProvider); final storageLifeCycle = storageApi as StorageSdkLifecycle; - // 读取 mock 数据 + // 读取 mock 数据(loginData.json 结构: { code, message, data: {...} }) + // 手动拆包 data 字段,对应 SDK 内部 ApiResponseWrapper 的行为 final raw = await rootBundle.loadString('assets/loginData.json'); final json = jsonDecode(raw) as Map; - final loginResponse = LoginResponse.fromJson(json); - final user = loginResponse.data.toEntity(); + final data = json['data'] as Map; + final profile = data['profile'] as Map; + // 生成器生成的 _$XFromJson 是 library 私有函数,外部不可调用。 + // Demo 场景直接从 JSON 字段构建 User,不依赖生成的 fromJson。 + final user = User( + uid: profile['uid'] as int, + uuid: profile['uuid'] as String, + lastOnline: profile['last_online'] as int, + profilePic: profile['profile_pic'] as String, + profilePicGaussian: profile['profile_pic_gaussian'] as String, + nickname: profile['nickname'] as String, + contact: profile['contact'] as String, + countryCode: profile['country_code'] as String, + email: profile['email'] as String, + recoveryEmail: profile['recovery_email'] as String, + username: profile['username'] as String, + bio: profile['bio'] as String, + relationship: profile['relationship'] as int, + userAlias: profile['user_alias'] as String?, + channelId: profile['channel_id'] as int, + channelGroupId: profile['channel_group_id'] as int, + hint: profile['hint'] as String, + ); // 先完成 DB 操作,再标记登录状态(失败时不会误标为已登录) await storageLifeCycle.openDatabase(user.uid); diff --git a/apps/im_app/lib/features/login/usecases/login_usecase.dart b/apps/im_app/lib/features/login/usecases/login_usecase.dart index 493ef42..68e76f5 100644 --- a/apps/im_app/lib/features/login/usecases/login_usecase.dart +++ b/apps/im_app/lib/features/login/usecases/login_usecase.dart @@ -28,9 +28,9 @@ import '../../../domain/repositories/auth_repository.dart'; /// → AuthRepository.login() /// → AuthRepositoryImpl.login() /// → _client.executeRequest(LoginRequest) -/// ← LoginData(DTO) -/// → _onTokenUpdate(token) ← 回调写入 Token(内存 + 持久化,由 Provider 层组合) -/// ← LoginData.toEntity() → User +/// ← LoginResponse(SDK 已拆包 envelope) +/// → _onTokenUpdate(accessToken) ← 回调写入 Token(内存 + 持久化,由 Provider 层组合) +/// ← LoginResponse.toEntity() → User /// → SocketManager.connect(token) ← 登录后连接 WebSocket /// → StorageSdkApi.openDatabase(user.id) ← 按用户 id 打开本地库 /// ← User @@ -41,17 +41,18 @@ class LoginUseCase { final ApiConfig _apiConfig; final StorageSdkApi _storageApi; - StorageSdkLifecycle get _storageLifeCycle => _storageApi as StorageSdkLifecycle; + StorageSdkLifecycle get _storageLifeCycle => + _storageApi as StorageSdkLifecycle; LoginUseCase({ required AuthRepository authRepository, required SocketManager socketManager, required ApiConfig apiConfig, required StorageSdkApi storageApi, - }) : _authRepository = authRepository, - _socketManager = socketManager, - _apiConfig = apiConfig, - _storageApi = storageApi; + }) : _authRepository = authRepository, + _socketManager = socketManager, + _apiConfig = apiConfig, + _storageApi = storageApi; /// 执行登录 /// @@ -72,10 +73,7 @@ class LoginUseCase { _validatePassword(password); // ── 2. 登录 ── - final user = await _authRepository.login( - email: email, - password: password, - ); + final user = await _authRepository.login(email: email, password: password); // ── 3. 连接 WebSocket ── // token 在 Repository 的 _onTokenUpdate 回调中已写入 ApiConfig, diff --git a/packages/networks_sdk/lib/networks_sdk.dart b/packages/networks_sdk/lib/networks_sdk.dart index 5e2405a..b514681 100644 --- a/packages/networks_sdk/lib/networks_sdk.dart +++ b/packages/networks_sdk/lib/networks_sdk.dart @@ -29,3 +29,4 @@ export 'src/domain/entities/socket_error.dart'; // Annotations(代码生成) export 'src/annotations/api_request.dart'; +export 'src/annotations/api_response.dart'; diff --git a/packages/networks_sdk/lib/src/annotations/api_request.dart b/packages/networks_sdk/lib/src/annotations/api_request.dart index 50a3b23..1fe3204 100644 --- a/packages/networks_sdk/lib/src/annotations/api_request.dart +++ b/packages/networks_sdk/lib/src/annotations/api_request.dart @@ -1,49 +1,56 @@ import 'package:networks_sdk/src/domain/entities/api_request_type.dart'; import 'package:networks_sdk/src/domain/entities/http_method.dart'; - /// API 请求注解 — 标记一个类为 API 请求 /// /// 配合 `build_runner` 代码生成器,自动生成 `ApiRequestable` 协议实现, /// 使用侧只需定义字段 + 注解,path / method / requestType / includeToken /// 全部由生成器自动提供。 /// -/// ## 使用方式 +/// ## 有响应数据(指定 responseType) /// /// ```dart /// @ApiRequest( -/// path: '/auth/login', +/// path: ApiPaths.authLogin, /// method: HttpMethod.post, -/// responseType: LoginData, +/// responseType: LoginResponse, /// requestType: ApiRequestType.login, /// ) -/// @JsonSerializable() -/// class LoginRequest extends ApiRequestable +/// class LoginRequest extends ApiRequestable /// with _$LoginRequestApi { /// final String email; /// final String password; /// /// LoginRequest({required this.email, required this.password}); -/// -/// @override -/// Map toJson() => _$LoginRequestToJson(this); /// } /// ``` /// -/// 生成器自动生成 `_$LoginRequestApi` mixin,提供: -/// - `path` → `'/auth/login'` -/// - `method` → `HttpMethod.post` -/// - `requestType` → `ApiRequestType.login` -/// - `includeToken` → `false`(login 类型自动设为 false) +/// ## 无响应数据(省略 responseType) +/// +/// ```dart +/// @ApiRequest( +/// path: ApiPaths.authLogout, +/// method: HttpMethod.post, +/// ) +/// class LogoutRequest extends ApiRequestable +/// with _$LogoutRequestApi { +/// LogoutRequest(); +/// } +/// ``` +/// +/// 生成器自动生成 `_$XxxRequestApi` mixin,提供: +/// - `path` / `method` / `requestType` / `includeToken` +/// - `toJson()` — 从声明字段自动生成 +/// - `responseType` 存在时:`parameters` getter 自动注册 `fromJson` class ApiRequest { - /// API 路径(如 `'/auth/login'`) + /// API 路径(如 `ApiPaths.authLogin`) final String path; /// HTTP 方法(默认 POST) final HttpMethod method; - /// 响应类型(用于泛型绑定) - final Type responseType; + /// 响应数据类型(省略表示无响应数据,对应 `ApiRequestable`) + final Type? responseType; /// 请求类型(决定 header 处理方式,默认 request) final ApiRequestType requestType; @@ -57,7 +64,7 @@ class ApiRequest { const ApiRequest({ required this.path, this.method = HttpMethod.post, - required this.responseType, + this.responseType, this.requestType = ApiRequestType.request, this.includeToken, this.customHeaders, diff --git a/packages/networks_sdk/lib/src/annotations/api_response.dart b/packages/networks_sdk/lib/src/annotations/api_response.dart new file mode 100644 index 0000000..27f195c --- /dev/null +++ b/packages/networks_sdk/lib/src/annotations/api_response.dart @@ -0,0 +1,31 @@ +/// Response DTO 无需任何注解。 +/// +/// 生成器从 `@ApiRequest(responseType: T)` 声明出发,自动推导并生成 +/// `_$TFromJson` 反序列化函数,以及所有嵌套自定义类型的 `fromJson`。 +/// +/// ## 使用方式(只需标注 Request 类) +/// +/// ```dart +/// // Request:唯一需要注解的地方 +/// @ApiRequest(path: ApiPaths.authLogin, responseType: LoginResponse) +/// class LoginRequest extends ApiRequestable +/// with _$LoginRequestApi { +/// final String email; +/// final String password; +/// LoginRequest({required this.email, required this.password}); +/// } +/// +/// // Response:纯 Dart 类,零注解,零样板 +/// class LoginResponse { +/// @JsonKey(name: 'access_token') final String accessToken; +/// final LoginProfile profile; +/// const LoginResponse({required this.accessToken, required this.profile}); +/// // 生成器自动提供 _$LoginResponseFromJson +/// } +/// ``` +/// +/// 此文件保留作占位符,供未来扩展使用(如自定义序列化策略)。 +/// 当前版本 Response 类无需标注任何注解,所有 fromJson 由生成器自动推导。 +class ApiResponse { + const ApiResponse(); +} diff --git a/packages/networks_sdk/lib/src/generator/api_request_generator.dart b/packages/networks_sdk/lib/src/generator/api_request_generator.dart index d8592be..d6863ac 100644 --- a/packages/networks_sdk/lib/src/generator/api_request_generator.dart +++ b/packages/networks_sdk/lib/src/generator/api_request_generator.dart @@ -20,25 +20,36 @@ const _jsonKeyChecker = TypeChecker.fromUrl( /// 支持 `@JsonKey(name: '...')` 字段重命名。 /// 如有 `@JsonKey(includeToJson: false)` 则跳过该字段。 /// -/// ## 使用模式 -/// -/// Request 类只需 `@ApiRequest` 注解,无需 `@JsonSerializable`: +/// ## 使用模式(有响应数据) /// /// ```dart /// @ApiRequest( /// path: ApiPaths.authLogin, /// method: HttpMethod.post, -/// responseType: LoginData, +/// responseType: LoginResponse, /// requestType: ApiRequestType.login, /// ) -/// class LoginRequest extends ApiRequestable +/// class LoginRequest extends ApiRequestable /// with _$LoginRequestApi { /// final String email; /// final String password; /// /// LoginRequest({required this.email, required this.password}); -/// // 完毕!toJson / path / method 全部由 mixin 自动生成 -/// // Response 的 fromJson 在 parameters getter 中自动注册 +/// // 完毕!toJson / path / method / fromJson 注册全部由 mixin 自动生成 +/// } +/// ``` +/// +/// ## 使用模式(无响应数据) +/// +/// ```dart +/// @ApiRequest( +/// path: ApiPaths.authLogout, +/// method: HttpMethod.post, +/// ) +/// class LogoutRequest extends ApiRequestable +/// with _$LogoutRequestApi { +/// LogoutRequest(); +/// // responseType 省略 → mixin 跳过 fromJson 注册 /// } /// ``` /// @@ -46,10 +57,10 @@ const _jsonKeyChecker = TypeChecker.fromUrl( /// /// `_$Api` /// -/// ## 生成示例 +/// ## 生成示例(有响应数据) /// /// ```dart -/// mixin _$LoginRequestApi on ApiRequestable { +/// mixin _$LoginRequestApi on ApiRequestable { /// @override String get path => '/auth/login'; /// @override HttpMethod get method => HttpMethod.post; /// @override ApiRequestType get requestType => ApiRequestType.login; @@ -61,7 +72,7 @@ const _jsonKeyChecker = TypeChecker.fromUrl( /// }; /// @override /// Map? get parameters { -/// registerResponse(LoginData.fromJson); +/// registerResponse(_$LoginResponseFromJson); /// return super.parameters; /// } /// } @@ -94,9 +105,13 @@ class ApiRequestGenerator extends GeneratorForAnnotation { 'post', ); - // 读取 responseType(用于泛型绑定 + 自动注册 fromJson) - final responseType = annotation.read('responseType').typeValue; - final responseTypeName = responseType.getDisplayString(); + // 读取 responseType(可选:null 表示 void 响应,无需注册 fromJson) + final responseTypePeek = annotation.peek('responseType'); + final bool hasResponseType = + responseTypePeek != null && !responseTypePeek.isNull; + final String responseTypeName = hasResponseType + ? responseTypePeek.typeValue.getDisplayString() + : 'void'; // 读取 ApiRequestType 枚举值 final requestTypeName = _readEnumName( @@ -116,6 +131,14 @@ class ApiRequestGenerator extends GeneratorForAnnotation { // 从类的声明字段生成 toJson() final toJsonBody = _buildToJsonBody(element, className); + // 有响应类型:parameters getter 中注册 fromJson(使用生成的私有函数) + // ApiResponseGenerator 在同一 .g.dart 中生成 _$XFromJson,同 library 可访问 + // 无响应类型(void):跳过注册,直接返回 super.parameters + final parametersBody = hasResponseType + ? ''' registerResponse<$responseTypeName>(_\$${responseTypeName}FromJson); + return super.parameters;''' + : ' return super.parameters;'; + return ''' /// Generated by @ApiRequest for [$className] mixin _\$${className}Api on ApiRequestable<$responseTypeName> { @@ -131,8 +154,7 @@ mixin _\$${className}Api on ApiRequestable<$responseTypeName> { Map toJson() => $toJsonBody; @override Map? get parameters { - registerResponse<$responseTypeName>($responseTypeName.fromJson); - return super.parameters; +$parametersBody } } '''; diff --git a/packages/networks_sdk/lib/src/generator/api_response_generator.dart b/packages/networks_sdk/lib/src/generator/api_response_generator.dart new file mode 100644 index 0000000..946b920 --- /dev/null +++ b/packages/networks_sdk/lib/src/generator/api_response_generator.dart @@ -0,0 +1,205 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:build/build.dart'; + +/// @JsonKey 检测器(与 ApiRequestGenerator 共用相同策略) +const _jsonKeyChecker = TypeChecker.fromUrl( + 'package:json_annotation/src/json_key.dart#JsonKey', +); + +/// @ApiRequest 检测器(复用,用于找到所有 responseType 入口) +const _apiRequestChecker = TypeChecker.fromUrl( + 'package:networks_sdk/src/annotations/api_request.dart#ApiRequest', +); + +/// Response 反序列化代码生成器 +/// +/// 从同一 library 中的 `@ApiRequest(responseType: T)` 声明出发, +/// 自动生成 `_$TFromJson` 私有函数,无需在 Response 类上添加任何注解。 +/// +/// ## 设计原理 +/// +/// App 层只需标注 Request 类: +/// ```dart +/// @ApiRequest(path: '...', responseType: LoginResponse) +/// class LoginRequest extends ApiRequestable +/// with _$LoginRequestApi { ... } +/// ``` +/// +/// 生成器从 `responseType` 出发,递归找到所有嵌套自定义类型, +/// 在同一 `.g.dart` 中生成所有 `_$XFromJson` 私有函数。 +/// +/// Response 类体本身是纯 Dart 类,**无需任何注解,无需 factory fromJson**: +/// ```dart +/// class LoginResponse { +/// @JsonKey(name: 'access_token') +/// final String accessToken; +/// final LoginProfile profile; +/// // 完毕!fromJson 由生成器自动提供 +/// } +/// ``` +/// +/// ## 生成示例 +/// +/// ```dart +/// LoginProfile _$LoginProfileFromJson(Map json) { +/// return LoginProfile( +/// uid: json['uid'] as int, +/// uuid: json['uuid'] as String, +/// ); +/// } +/// +/// LoginResponse _$LoginResponseFromJson(Map json) { +/// return LoginResponse( +/// accessToken: json['access_token'] as String, +/// profile: _$LoginProfileFromJson(json['profile'] as Map), +/// ); +/// } +/// ``` +/// +/// ## 支持的字段类型 +/// +/// - 基础类型(String / int / bool / double / num) +/// - 可空类型(String? / int? / 自定义类?) +/// - 嵌套对象(自动递归,在同一 `.g.dart` 中生成被依赖类的函数) +/// - `@JsonKey(name: '...')` 字段重命名 +/// - `@JsonKey(includeFromJson: false)` 跳过字段 +class ApiResponseGenerator extends Generator { + @override + String generate(LibraryReader library, BuildStep buildStep) { + final buffer = StringBuffer(); + // 跟踪已生成的类,避免同一 library 中多个 Request 引用同一 Response 类时重复生成 + final generated = {}; + + for (final annotated in library.annotatedWith(_apiRequestChecker)) { + final annotation = annotated.annotation; + final responseTypePeek = annotation.peek('responseType'); + if (responseTypePeek == null || responseTypePeek.isNull) continue; + + final responseType = responseTypePeek.typeValue; + if (responseType is! InterfaceType) continue; + + final responseClass = responseType.element; + if (responseClass is! ClassElement) continue; + + // 只生成属于当前 library 的类(跨包类型跳过) + if (responseClass.library != library.element) continue; + + _generateRecursive(responseClass, library, generated, buffer); + } + + return buffer.toString(); + } + + /// 递归生成:先生成被依赖的嵌套类型,再生成当前类型(保证引用顺序) + void _generateRecursive( + ClassElement element, + LibraryReader library, + Set generated, + StringBuffer buffer, + ) { + final typeName = element.name!; + if (generated.contains(typeName)) return; + generated.add(typeName); + + // 先递归处理嵌套的自定义类型 + // InterfaceType 的 element 在 nullable 和非 nullable 情况下指向同一个 ClassElement + for (final field in element.fields.where( + (f) => !f.isStatic && !f.isSynthetic, + )) { + final fieldType = field.type; + if (fieldType is InterfaceType) { + final fieldClass = fieldType.element; + if (fieldClass is ClassElement && + fieldClass.library == library.element && + !_isPrimitive(fieldClass.name!)) { + _generateRecursive(fieldClass, library, generated, buffer); + } + } + } + + final params = _buildConstructorParams(element); + buffer.write(''' +/// Generated by ApiResponseGenerator for [$typeName] +$typeName _\$${typeName}FromJson(Map json) { + return $typeName( +$params ); +} +'''); + } + + /// 判断是否为不需要递归生成的基础类型 + bool _isPrimitive(String typeName) { + const primitives = { + 'String', + 'int', + 'double', + 'bool', + 'num', + 'dynamic', + 'Object', + 'List', + 'Map', + 'Set', + 'Iterable', + }; + return primitives.contains(typeName); + } + + /// 从类的字段生成构造函数参数列表 + String _buildConstructorParams(ClassElement element) { + final fields = element.fields + .where((f) => !f.isStatic && !f.isSynthetic) + .toList(); + + final lines = []; + for (final field in fields) { + final jsonKeyAnnotation = _jsonKeyChecker.firstAnnotationOfExact(field); + + // @JsonKey(includeFromJson: false) → 跳过 + final includeFromJson = jsonKeyAnnotation + ?.getField('includeFromJson') + ?.toBoolValue(); + if (includeFromJson == false) continue; + + final fieldName = field.name!; + final jsonName = + jsonKeyAnnotation?.getField('name')?.toStringValue() ?? fieldName; + + final type = field.type; + final isNullable = type.nullabilitySuffix == NullabilitySuffix.question; + final expr = _castExpression(type, jsonName, isNullable); + + lines.add(' $fieldName: $expr,'); + } + + return lines.join('\n'); + } + + /// 根据字段类型生成 JSON 取值表达式 + String _castExpression(DartType type, String jsonKey, bool isNullable) { + final q = isNullable ? '?' : ''; + final access = "json['$jsonKey']"; + + // 基础类型:直接 as 转换 + if (type.isDartCoreString) return '$access as String$q'; + if (type.isDartCoreInt) return '$access as int$q'; + if (type.isDartCoreBool) return '$access as bool$q'; + if (type.isDartCoreDouble) return '$access as double$q'; + if (type.isDartCoreNum) return '$access as num$q'; + + // 嵌套对象:调用同一 part 文件中生成的 _$TypeFromJson 私有函数 + if (type is InterfaceType) { + final typeName = type.element.name!; + if (isNullable) { + return '$access == null ? null : _\$${typeName}FromJson($access as Map)'; + } + return '_\$${typeName}FromJson($access as Map)'; + } + + // 兜底 + return '$access as dynamic'; + } +} diff --git a/packages/networks_sdk/lib/src/generator/builder.dart b/packages/networks_sdk/lib/src/generator/builder.dart index 0c91ae3..d3f64b4 100644 --- a/packages/networks_sdk/lib/src/generator/builder.dart +++ b/packages/networks_sdk/lib/src/generator/builder.dart @@ -2,11 +2,16 @@ import 'package:build/build.dart'; import 'package:source_gen/source_gen.dart'; import 'api_request_generator.dart'; +import 'api_response_generator.dart'; -/// @ApiRequest 代码生成器入口 +/// @ApiRequest / @ApiResponse 代码生成器入口 /// /// 在 `build.yaml` 中注册此 builder,配合 `build_runner` 使用。 -/// 生成的代码通过 `SharedPartBuilder` 合并到 `.g.dart` 文件中, -/// 与 `json_serializable` 等生成器共存。 -Builder apiRequestBuilder(BuilderOptions options) => - SharedPartBuilder([ApiRequestGenerator()], 'api_request'); +/// 生成的代码通过 `SharedPartBuilder` 合并到 `.g.dart` 文件中。 +/// +/// - `ApiRequestGenerator`:为 `@ApiRequest` 生成 Request mixin(toJson + path/method 等) +/// - `ApiResponseGenerator`:从 `@ApiRequest(responseType: T)` 推导,生成 Response 的所有 `_$XFromJson` 函数 +Builder apiRequestBuilder(BuilderOptions options) => SharedPartBuilder([ + ApiRequestGenerator(), + ApiResponseGenerator(), +], 'api_request');