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}Data login_request.dart → LoginRequest + LoginData
+接口定义 {action}_request.dartRequest: {Action}Request
Response DTO: {Action}Response login_request.dart → LoginRequest + LoginResponse
持久化 DTO data/models/{entity}_dto.dart{Entity}Dtouser_dto.dart → UserDto
Repository 接口 domain/repositories/{module}_repository.dart{Module}Repositoryauth_repository.dart → AuthRepository
Repository 实现 data/repositories/{module}_repository_impl.dart{Module}RepositoryImplauth_repository_impl.dart → AuthRepositoryImpl
@@ -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> // ← 固定写法
命名规则速查(写之前就能确定引用名)
-你写的类名 fromJson toJson Api mixin
+你写的类名 fromJson(私有函数) toJson Api 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');