极简接口定义和响应定义,支持更多的解析器

This commit is contained in:
Cody
2026-03-09 11:04:52 +08:00
parent a063ce178e
commit 03b89706a5
14 changed files with 482 additions and 261 deletions

View File

@@ -886,8 +886,9 @@ flowchart TD
│ ├── annotations/ │ ├── annotations/
│ │ └── api_request.dart # @ApiRequest 注解定义 │ │ └── api_request.dart # @ApiRequest 注解定义
│ ├── generator/ │ ├── generator/
│ │ ├── api_request_generator.dart # build_runner 代码生成器实现 │ │ ├── api_request_generator.dart # Request mixin 生成器toJson / path / method / fromJson 注册)
│ │ ── builder.dart # SharedPartBuilder 入口 │ │ ── api_response_generator.dart # Response fromJson 生成器(从 @ApiRequest(responseType) 自动推导,递归嵌套类型)
│ │ └── builder.dart # SharedPartBuilder 入口(两个生成器合并到同一 .g.dart
│ ├── data/ │ ├── data/
│ │ ├── datasources/ │ │ ├── datasources/
│ │ │ ├── http/ │ │ │ ├── http/
@@ -2286,23 +2287,21 @@ extension APIRequestableExtension<T> on APIRequestable<T> {
<p>一个端点 = 一个文件(<code>data/remote/login_request.dart</code>Response DTO + Request 放在同一文件中。</p> <p>一个端点 = 一个文件(<code>data/remote/login_request.dart</code>Response DTO + Request 放在同一文件中。</p>
<pre><code class="language-dart">import 'package:json_annotation/json_annotation.dart'; <pre><code class="language-dart">import 'package:networks_sdk/networks_sdk.dart';
import 'package:networks_sdk/networks_sdk.dart';
part 'login_request.g.dart'; part 'login_request.g.dart';
// ── Response DTO ── // ── Response DTO(纯 Dart 类,零注解,零样板)──
// _$LoginResponseFromJson 由 ApiResponseGenerator 从 @ApiRequest(responseType: T) 自动推导生成
@JsonSerializable() class LoginResponse {
class LoginData {
final String token; final String token;
@JsonKey(name: 'user_id') @JsonKey(name: 'user_id') // @JsonKey 由生成器读取Response 类不需要 @JsonSerializable
final String userId; final String userId;
final String email; final String email;
const LoginData({required this.token, required this.userId, required this.email}); const LoginResponse({required this.token, required this.userId, required this.email});
factory LoginData.fromJson(Map&lt;String, dynamic&gt; json) =&gt; _$LoginDataFromJson(json); // 无 factory fromJson — 生成器在 .g.dart 中提供私有 _$LoginResponseFromJson
Map&lt;String, dynamic&gt; toJson() =&gt; _$LoginDataToJson(this);
User toEntity() =&gt; User(id: userId, email: email); // DTO → Domain Entity User toEntity() =&gt; User(id: userId, email: email); // DTO → Domain Entity
} }
@@ -2313,23 +2312,23 @@ class LoginData {
@ApiRequest( @ApiRequest(
path: ApiPaths.authLogin, // 路径统一在 core/foundation/api_paths.dart 管理 path: ApiPaths.authLogin, // 路径统一在 core/foundation/api_paths.dart 管理
method: HttpMethod.post, method: HttpMethod.post,
responseType: LoginData, responseType: LoginResponse,
requestType: ApiRequestType.login, requestType: ApiRequestType.login,
) )
class LoginRequest extends ApiRequestable&lt;LoginData&gt; with _$LoginRequestApi { class LoginRequest extends ApiRequestable&lt;LoginResponse&gt; with _$LoginRequestApi {
final String email; final String email;
final String password; final String password;
LoginRequest({required this.email, required this.password}); LoginRequest({required this.email, required this.password});
// 完毕toJson 由 mixin 从类字段自动生成fromJson 不需要Request 永远手动构造) // 完毕toJson 由 mixin 从类字段自动生成fromJson 注册也全部自动处理
} }
</code></pre> </code></pre>
<pre><code class="language-dart">// 使用 - 超级简单! <pre><code class="language-dart">// 使用 - 超级简单!
final loginData = await apiClient.executeRequest( final loginResponse = await apiClient.executeRequest(
LoginRequest(email: 'user@example.com', password: '123456'), LoginRequest(email: 'user@example.com', password: '123456'),
); );
final user = loginData?.toEntity(); // DTO → Domain Entity final user = loginResponse?.toEntity(); // DTO → Domain Entity
</code></pre> </code></pre>
<div style="background: #e8f5e9; padding: 20px; border-radius: 8px; border-left: 4px solid #388e3c; margin: 20px 0;"> <div style="background: #e8f5e9; padding: 20px; border-radius: 8px; border-left: 4px solid #388e3c; margin: 20px 0;">
@@ -2359,8 +2358,8 @@ final user = loginData?.toEntity(); // DTO → Domain Entity
</tr> </tr>
<tr> <tr>
<td><strong>@ApiRequest当前方案</strong></td> <td><strong>@ApiRequest当前方案</strong></td>
<td>字段 + 构造函数 + @ApiRequest</td> <td>字段 + 构造函数 + @ApiRequest<br/><em>Response DTO字段 + 构造函数,零注解)</em></td>
<td>path / method / requestType / includeToken / toJson / fromJson 注册</td> <td>Request: path / method / requestType / includeToken / toJson / fromJson 注册<br/>Response: _$XFromJson 私有反序列化函数(按需递归生成嵌套类型)</td>
<td><strong>极低</strong></td> <td><strong>极低</strong></td>
</tr> </tr>
</tbody> </tbody>
@@ -2368,10 +2367,10 @@ final user = loginData?.toEntity(); // DTO → Domain Entity
<p><strong>核心优势</strong></p> <p><strong>核心优势</strong></p>
<ul> <ul>
<li><strong>注解驱动</strong><code>@ApiRequest</code> 一个注解自动生成 mixin含 toJson无需 <code>@JsonSerializable</code></li> <li><strong>Request 零样板</strong><code>@ApiRequest</code> 一个注解生成 mixin含 toJson无需 <code>@JsonSerializable</code></li>
<li><strong>Response 零注解</strong>Response DTO 不需要 <code>@JsonSerializable</code>,不需要 <code>factory fromJson</code><code>_$XFromJson</code> 由生成器从 <code>@ApiRequest(responseType: T)</code> 自动推导;嵌套自定义类型递归生成,依赖关系自动处理</li>
<li><strong>自动注册</strong>fromJson 在首次请求时自动注册到全局注册表,无需手动 <code>registerApiResponses()</code></li> <li><strong>自动注册</strong>fromJson 在首次请求时自动注册到全局注册表,无需手动 <code>registerApiResponses()</code></li>
<li><strong>一个端点 = 一个文件</strong>Response DTO + Request 放在同一文件,打开即看全貌</li> <li><strong>一个端点 = 一个文件</strong>Response DTO + Request 放在同一文件,打开即看全貌</li>
<li><strong>傻瓜式使用</strong>:使用者只需关注业务字段和注解配置</li>
<li><strong>类型安全</strong><code>ApiRequestable&lt;T&gt;</code> 泛型 + <code>responseType</code> 编译期检查</li> <li><strong>类型安全</strong><code>ApiRequestable&lt;T&gt;</code> 泛型 + <code>responseType</code> 编译期检查</li>
</ul> </ul>
@@ -2387,12 +2386,12 @@ final user = loginData?.toEntity(); // DTO → Domain Entity
<tbody> <tbody>
<tr> <tr>
<td><strong>Swift</strong></td> <td><strong>Swift</strong></td>
<td><code>struct LoginRequest: APIRequestable { typealias Response = LoginData ... }</code></td> <td><code>struct LoginRequest: APIRequestable { typealias Response = LoginResponse ... }</code></td>
<td>协议直接实现,最简洁</td> <td>协议直接实现,最简洁</td>
</tr> </tr>
<tr> <tr>
<td><strong>Dart</strong></td> <td><strong>Dart</strong></td>
<td><code>@ApiRequest(...) class LoginRequest extends ApiRequestable&lt;LoginData&gt; with _$LoginRequestApi { ... }</code></td> <td><code>@ApiRequest(...) class LoginRequest extends ApiRequestable&lt;LoginResponse&gt; with _$LoginRequestApi { ... }</code></td>
<td>注解 + 代码生成,接近 Swift 体验</td> <td>注解 + 代码生成,接近 Swift 体验</td>
</tr> </tr>
</tbody> </tbody>
@@ -2530,26 +2529,23 @@ melos run gen
<h6>4.5 更多使用示例</h6> <h6>4.5 更多使用示例</h6>
<p>所有示例遵循同一模式:<code>@ApiRequest</code> + <code>extends ApiRequestable&lt;T&gt; with _$XxxApi</code>。Request 无需 <code>@JsonSerializable</code></p> <p>所有示例遵循同一模式:<code>@ApiRequest</code> + <code>extends ApiRequestable&lt;T&gt; with _$XxxApi</code>。Request 和 Response 均无需 <code>@JsonSerializable</code>Response DTO 连 <code>factory fromJson</code> 也不需要</p>
<p><strong>发送消息请求POST + @JsonKey 字段重命名):</strong></p> <p><strong>发送消息请求POST + @JsonKey 字段重命名):</strong></p>
<pre><code class="language-dart">// data/remote/send_message_request.dart <pre><code class="language-dart">// data/remote/send_message_request.dart
// ── Response DTO仍用 @JsonSerializable)── // ── Response DTO纯 Dart 类零注解_$SendMessageResponseFromJson 由生成器自动提供)──
@JsonSerializable() class SendMessageResponse {
class SendMessageData {
@JsonKey(name: 'message_id') @JsonKey(name: 'message_id')
final String messageId; final String messageId;
final int timestamp; final int timestamp;
const SendMessageData({required this.messageId, required this.timestamp}); const SendMessageResponse({required this.messageId, required this.timestamp});
factory SendMessageData.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$SendMessageDataFromJson(json);
} }
// ── Request零样板── // ── Request零样板──
@ApiRequest(path: ApiPaths.chatSendMessage, responseType: SendMessageData) @ApiRequest(path: ApiPaths.chatSendMessage, responseType: SendMessageResponse)
class SendMessageRequest extends ApiRequestable&lt;SendMessageData&gt; class SendMessageRequest extends ApiRequestable&lt;SendMessageResponse&gt;
with _$SendMessageRequestApi { with _$SendMessageRequestApi {
@JsonKey(name: 'chat_id') // 生成器会读取JSON 键名为 'chat_id' @JsonKey(name: 'chat_id') // 生成器会读取JSON 键名为 'chat_id'
final String chatId; final String chatId;
@@ -2563,23 +2559,21 @@ class SendMessageRequest extends ApiRequestable&lt;SendMessageData&gt;
<p><strong>获取用户资料GET靠 token 标识当前用户,无需传参):</strong></p> <p><strong>获取用户资料GET靠 token 标识当前用户,无需传参):</strong></p>
<pre><code class="language-dart">// data/remote/get_profile_request.dart <pre><code class="language-dart">// data/remote/get_profile_request.dart
@JsonSerializable(createToJson: false) // 只需反序列化 // ── Response DTO纯 Dart 类零注解_$ProfileResponseFromJson 由生成器自动提供)──
class ProfileData { class ProfileResponse {
@JsonKey(name: 'user_id') @JsonKey(name: 'user_id')
final String userId; final String userId;
final String email; final String email;
final String? nickname; final String? nickname;
final String? avatar; final String? avatar;
const ProfileData({required this.userId, required this.email, this.nickname, this.avatar}); const ProfileResponse({required this.userId, required this.email, this.nickname, this.avatar});
factory ProfileData.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$ProfileDataFromJson(json);
User toEntity() =&gt; User(id: userId, email: email, nickname: nickname, avatar: avatar); User toEntity() =&gt; User(id: userId, email: email, nickname: nickname, avatar: avatar);
} }
@ApiRequest(path: ApiPaths.userProfile, method: HttpMethod.get, responseType: ProfileData) @ApiRequest(path: ApiPaths.userProfile, method: HttpMethod.get, responseType: ProfileResponse)
class GetProfileRequest extends ApiRequestable&lt;ProfileData&gt; class GetProfileRequest extends ApiRequestable&lt;ProfileResponse&gt;
with _$GetProfileRequestApi { with _$GetProfileRequestApi {
GetProfileRequest(); // 无参数 — toJson 自动生成空 map GetProfileRequest(); // 无参数 — toJson 自动生成空 map
} }
@@ -2588,14 +2582,12 @@ class GetProfileRequest extends ApiRequestable&lt;ProfileData&gt;
<p><strong>上传文件请求FormData multipart</strong></p> <p><strong>上传文件请求FormData multipart</strong></p>
<pre><code class="language-dart">// data/remote/upload_file_request.dart <pre><code class="language-dart">// data/remote/upload_file_request.dart
@JsonSerializable() // ── Response DTO纯 Dart 类零注解_$UploadResultFromJson 由生成器自动提供)──
class UploadResult { class UploadResult {
final String url; final String url;
@JsonKey(name: 'file_id') @JsonKey(name: 'file_id')
final String fileId; final String fileId;
const UploadResult({required this.url, required this.fileId}); const UploadResult({required this.url, required this.fileId});
factory UploadResult.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$UploadResultFromJson(json);
} }
@ApiRequest( @ApiRequest(
@@ -2624,7 +2616,8 @@ class UploadFileRequest extends ApiRequestable&lt;UploadResult&gt;
<div style="background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 20px 0;"> <div style="background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 20px 0;">
<p><strong>核心价值</strong></p> <p><strong>核心价值</strong></p>
<ul style="margin-bottom: 0;"> <ul style="margin-bottom: 0;">
<li><strong>极简使用</strong>:字段 + 构造函数 + <code>@ApiRequest</code>Request 无需 <code>@JsonSerializable</code>、无需 <code>fromJson</code>、无需手写 <code>toJson</code></li> <li><strong>Request 极简</strong>:字段 + 构造函数 + <code>@ApiRequest</code>(无需 <code>@JsonSerializable</code>、无需手写 <code>toJson</code></li>
<li><strong>Response 零注解</strong>Response DTO 是纯 Dart 类,无需 <code>@JsonSerializable</code>,无需 <code>factory fromJson</code><code>_$XFromJson</code> 由生成器从 <code>responseType</code> 自动推导</li>
<li><strong>零维护</strong>path / method / requestType / includeToken / toJson / fromJson 注册 全部自动生成</li> <li><strong>零维护</strong>path / method / requestType / includeToken / toJson / fromJson 注册 全部自动生成</li>
<li><strong>类型安全</strong>:泛型 <code>ApiRequestable&lt;T&gt;</code> + <code>responseType</code> 编译期检查</li> <li><strong>类型安全</strong>:泛型 <code>ApiRequestable&lt;T&gt;</code> + <code>responseType</code> 编译期检查</li>
<li><strong>一个端点 = 一个文件</strong>Response DTO + Request 放在同一文件,打开即看全貌</li> <li><strong>一个端点 = 一个文件</strong>Response DTO + Request 放在同一文件,打开即看全貌</li>
@@ -2721,11 +2714,11 @@ class LoginViewModel extends _$LoginViewModel {
→ LoggingInterceptor ← 请求/响应日志 → LoggingInterceptor ← 请求/响应日志
← request.decodeResponse(response) ← 自动解码 ← request.decodeResponse(response) ← 自动解码
← ApiResponseWrapper.fromJson ← 拆 { code, msg, data } ← ApiResponseWrapper.fromJson ← 拆 { code, msg, data }
← fromJsonRegistry[LoginData] ← 查注册表 ← fromJsonRegistry[LoginResponse] ← 查注册表
← LoginData.fromJson(data) ← 反序列化 _$LoginResponseFromJson(data) ← 反序列化(生成的私有函数)
← LoginDataDTO ← LoginResponseDTO
→ onTokenUpdate(token) ← 回调写入 Token内存 + 持久化) → onTokenUpdate(token) ← 回调写入 Token内存 + 持久化)
← loginData.toEntity() → User ← DTO → Domain Entity ← loginResponse.toEntity() → User ← DTO → Domain Entity
← User ← User
← state.copyWith(user: user) ← 更新状态 ← state.copyWith(user: user) ← 更新状态
View: ref.watch → 自动 rebuild ← UI 刷新 View: ref.watch → 自动 rebuild ← UI 刷新
@@ -5715,7 +5708,7 @@ class MessageLocalDataSource {
class MessageRepositoryImpl implements MessageRepository { class MessageRepositoryImpl implements MessageRepository {
final NetworksSdkApi _client; final NetworksSdkApi _client;
Future&lt;SendMessageData?&gt; sendMessage({ Future&lt;SendMessageResponse?&gt; sendMessage({
required String chatId, required String chatId,
required String content, required String content,
}) { }) {
@@ -5978,7 +5971,7 @@ flowchart LR
<tr><th>层级</th><th>文件命名</th><th>类命名</th><th>示例</th></tr> <tr><th>层级</th><th>文件命名</th><th>类命名</th><th>示例</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>接口定义</td><td><code>{action}_request.dart</code></td><td>Request: <code>{Action}Request</code><br/>Response DTO: <code>{Action}Data</code></td><td><code>login_request.dart</code><code>LoginRequest</code> + <code>LoginData</code></td></tr> <tr><td>接口定义</td><td><code>{action}_request.dart</code></td><td>Request: <code>{Action}Request</code><br/>Response DTO: <code>{Action}Response</code></td><td><code>login_request.dart</code><code>LoginRequest</code> + <code>LoginResponse</code></td></tr>
<tr><td>持久化 DTO</td><td><code>data/models/{entity}_dto.dart</code></td><td><code>{Entity}Dto</code></td><td><code>user_dto.dart</code><code>UserDto</code></td></tr> <tr><td>持久化 DTO</td><td><code>data/models/{entity}_dto.dart</code></td><td><code>{Entity}Dto</code></td><td><code>user_dto.dart</code><code>UserDto</code></td></tr>
<tr><td>Repository 接口</td><td><code>domain/repositories/{module}_repository.dart</code></td><td><code>{Module}Repository</code></td><td><code>auth_repository.dart</code><code>AuthRepository</code></td></tr> <tr><td>Repository 接口</td><td><code>domain/repositories/{module}_repository.dart</code></td><td><code>{Module}Repository</code></td><td><code>auth_repository.dart</code><code>AuthRepository</code></td></tr>
<tr><td>Repository 实现</td><td><code>data/repositories/{module}_repository_impl.dart</code></td><td><code>{Module}RepositoryImpl</code></td><td><code>auth_repository_impl.dart</code><code>AuthRepositoryImpl</code></td></tr> <tr><td>Repository 实现</td><td><code>data/repositories/{module}_repository_impl.dart</code></td><td><code>{Module}RepositoryImpl</code></td><td><code>auth_repository_impl.dart</code><code>AuthRepositoryImpl</code></td></tr>
@@ -5990,8 +5983,8 @@ flowchart LR
<p><strong>关键规则</strong></p> <p><strong>关键规则</strong></p>
<ul> <ul>
<li><strong>一个端点 = 一个 Request 文件</strong>Response DTO + Request 类放在同一文件中</li> <li><strong>一个端点 = 一个 Request 文件</strong>Response DTO + Request 类放在同一文件中</li>
<li><strong>Response DTO 必须有 <code>toEntity()</code></strong>:统一 DTO → Domain Entity 的转换入口</li> <li><strong>Response DTO 是纯 Dart 类</strong>:零注解、零 <code>factory fromJson</code>;只需字段 + 构造函数,<code>toEntity()</code> 按需添加</li>
<li><strong>持久化 DTO 和 Response DTO 分开</strong>Response DTO<code>XxxData</code>)在 request 文件中,持久化 DTO<code>XxxDto</code>)在 <code>data/models/</code></li> <li><strong>持久化 DTO 和 Response DTO 分开</strong>Response DTO<code>XxxResponse</code>)在 request 文件中,持久化 DTO<code>XxxDto</code>)在 <code>data/models/</code></li>
<li><strong>禁止跳层</strong>ViewModel → Repository→ UseCase 按需)→ NetworksSdkApi每层职责明确</li> <li><strong>禁止跳层</strong>ViewModel → Repository→ UseCase 按需)→ NetworksSdkApi每层职责明确</li>
</ul> </ul>
@@ -6041,8 +6034,10 @@ part 'login_request.g.dart'; // 这行必须写!指向即将自动生成的
<pre><code class="language-dart">// ── Response DTO ── <pre><code class="language-dart">// ── Response DTO ──
/// 服务端返回的登录数据 /// 服务端返回的登录数据
@JsonSerializable() // ← 这个注解让 build_runner 自动生成 fromJson / toJson /// 纯 Dart 类,零注解,零样板。
class LoginData { /// _$LoginResponseFromJson 由 ApiResponseGenerator 从 @ApiRequest(responseType: LoginResponse) 自动推导生成,
/// 无需手动添加任何注解或 factory fromJson。
class LoginResponse {
final String token; // 服务端返回的字段 final String token; // 服务端返回的字段
@JsonKey(name: 'user_id') // 服务端字段名是 user_idDart 字段名是 userId @JsonKey(name: 'user_id') // 服务端字段名是 user_idDart 字段名是 userId
final String userId; final String userId;
@@ -6050,18 +6045,15 @@ class LoginData {
final String? nickname; // 可选字段用 String? final String? nickname; // 可选字段用 String?
final String? avatar; final String? avatar;
const LoginData({ // 构造函数,参数和字段一一对应 const LoginResponse({ // 构造函数,参数和字段一一对应
required this.token, required this.token,
required this.userId, required this.userId,
required this.email, required this.email,
this.nickname, this.nickname,
this.avatar, this.avatar,
}); });
// 不需要 factory fromJson不需要 toJson不需要任何注解
// ↓ 这两行是固定写法,照抄就行,把类名替换掉 // 生成器自动在 login_request.g.dart 中生成 _$LoginResponseFromJson 私有函数
factory LoginData.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$LoginDataFromJson(json); // ← 短暂报红watch 模式下保存后几秒自动消失
Map&lt;String, dynamic&gt; toJson() =&gt; _$LoginDataToJson(this);
/// DTO → Domain Entity把网络层数据转为业务层数据 /// DTO → Domain Entity把网络层数据转为业务层数据
User toEntity() { User toEntity() {
@@ -6103,19 +6095,16 @@ class LoginData {
@ApiRequest( // ← 这个注解让 build_runner 自动生成 path / method 等 @ApiRequest( // ← 这个注解让 build_runner 自动生成 path / method 等
path: ApiPaths.authLogin, // 路径常量,定义在 core/foundation/api_paths.dart path: ApiPaths.authLogin, // 路径常量,定义在 core/foundation/api_paths.dart
method: HttpMethod.post, // HTTP 方法,从接口文档抄 method: HttpMethod.post, // HTTP 方法,从接口文档抄
responseType: LoginData, // 响应类型,就是上面写的 LoginData responseType: LoginResponse, // 响应类型,就是上面写的 LoginResponse
requestType: ApiRequestType.login, // login 类型不携带 Token登录前还没有 Token requestType: ApiRequestType.login, // login 类型不携带 Token登录前还没有 Token
) )
@JsonSerializable() // ← 自动生成 toJson把请求参数序列化为 JSON class LoginRequest extends ApiRequestable&lt;LoginResponse&gt; // ← 固定写法extends ApiRequestable&lt;响应类型&gt;
class LoginRequest extends ApiRequestable&lt;LoginData&gt; // ← 固定写法extends ApiRequestable&lt;响应类型&gt; with _$LoginRequestApi { // ← 固定写法with _$类名Api短暂报红保存后自动消失
with _$LoginRequestApi { // ← 固定写法with _$类名Api短暂报红保存后自动消失
final String email; // 请求参数:要发给服务端的字段 final String email; // 请求参数:要发给服务端的字段
final String password; final String password;
LoginRequest({required this.email, required this.password}); LoginRequest({required this.email, required this.password});
// 完毕toJson 由 _$LoginRequestApi mixin 从类字段自动生成,不需要 @JsonSerializable不需要手写 toJson
@override
Map&lt;String, dynamic&gt; toJson() =&gt; _$LoginRequestToJson(this); // ← 固定写法,短暂报红,保存后自动消失
} }
</code></pre> </code></pre>
@@ -6124,14 +6113,14 @@ class LoginRequest extends ApiRequestable&lt;LoginData&gt; // ← 固定写法
<div style="background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 15px 0;"> <div style="background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 15px 0;">
<p style="margin-top: 0; font-weight: 700; color: #f57f17;">命名规则速查(写之前就能确定引用名)</p> <p style="margin-top: 0; font-weight: 700; color: #f57f17;">命名规则速查(写之前就能确定引用名)</p>
<table> <table>
<thead><tr><th>你写的类名</th><th>fromJson</th><th>toJson</th><th>Api mixin</th></tr></thead> <thead><tr><th>你写的类名</th><th>fromJson(私有函数)</th><th>toJson</th><th>Api mixin</th><th>来源</th></tr></thead>
<tbody> <tbody>
<tr><td><code>LoginData</code></td><td><code>_$LoginDataFromJson</code></td><td><code>_$LoginDataToJson</code></td><td>-</td></tr> <tr><td><code>LoginResponse</code></td><td><code>_$LoginResponseFromJson</code></td><td>-(不需要)</td><td>-</td><td>ApiResponseGenerator 自动推导</td></tr>
<tr><td><code>LoginRequest</code></td><td><code>_$LoginRequestFromJson</code></td><td><code>_$LoginRequestToJson</code></td><td><code>_$LoginRequestApi</code></td></tr> <tr><td><code>LoginRequest</code></td><td>-(不需要)</td><td>由 mixin 自动生成</td><td><code>_$LoginRequestApi</code></td><td>ApiRequestGenerator 生成 mixin</td></tr>
<tr><td><code>SendMessageRequest</code></td><td><code>_$SendMessageRequestFromJson</code></td><td><code>_$SendMessageRequestToJson</code></td><td><code>_$SendMessageRequestApi</code></td></tr> <tr><td><code>SendMessageRequest</code></td><td>-(不需要)</td><td>由 mixin 自动生成</td><td><code>_$SendMessageRequestApi</code></td><td>ApiRequestGenerator 生成 mixin</td></tr>
</tbody> </tbody>
</table> </table>
<p style="margin-bottom: 0;">规则:<code>_$</code> + 类名 + <code>FromJson</code> / <code>ToJson</code> / <code>Api</code>。固定前缀,直接拼</p> <p style="margin-bottom: 0;">规则:Response DTO 类名拼 <code>_$</code> + 类名 + <code>FromJson</code>(私有,只在 .g.dart 内部使用Request 类名拼 <code>_$</code> + 类名 + <code>Api</code>mixin</p>
</div> </div>
<!-- ────────── 第 2 步 ────────── --> <!-- ────────── 第 2 步 ────────── -->
@@ -6162,19 +6151,19 @@ class AuthRepositoryImpl implements AuthRepository {
required String password, required String password,
}) async { }) async {
// 1. 调 NetworksSdkApi构造请求 → 发 HTTP → 自动解码 → 返回 DTO // 1. 调 NetworksSdkApi构造请求 → 发 HTTP → 自动解码 → 返回 DTO
final LoginData? loginData = await _client.executeRequest( final LoginResponse? loginResponse = await _client.executeRequest(
LoginRequest(email: email, password: password), LoginRequest(email: email, password: password),
); );
if (loginData == null) { if (loginResponse == null) {
throw Exception('Login failed: empty response'); throw Exception('Login failed: empty response');
} }
// 2. 回调写入 Token内存 + 持久化由 Provider 层组合) // 2. 回调写入 Token内存 + 持久化由 Provider 层组合)
_onTokenUpdate(loginData.token); _onTokenUpdate(loginResponse.token);
// 3. DTO → Domain Entity返回给上层 // 3. DTO → Domain Entity返回给上层
return loginData.toEntity(); return loginResponse.toEntity();
} }
} }
</code></pre> </code></pre>
@@ -6321,10 +6310,10 @@ class LoginViewModel extends _$LoginViewModel {
→ AuthRepositoryImpl.login() // data/repositories/ → AuthRepositoryImpl.login() // data/repositories/
→ _client.executeRequest(LoginRequest) // 调 NetworksSdkApi → _client.executeRequest(LoginRequest) // 调 NetworksSdkApi
→ Auth → Encryption → Dio.request → Retry → Logging // 拦截器链自动处理 → Auth → Encryption → Dio.request → Retry → Logging // 拦截器链自动处理
← request.decodeResponse → LoginData.fromJson // 自动解码 ← request.decodeResponse → _$LoginResponseFromJson // 自动解码(生成的私有函数)
← LoginDataDTO ← LoginResponseDTO
→ onTokenUpdate(token) // 回调:内存写入 + 持久化 → onTokenUpdate(token) // 回调:内存写入 + 持久化
← loginData.toEntity() → UserDomain Entity ← loginResponse.toEntity() → UserDomain Entity
← User ← User
← state = state.copyWith(user: user) // 更新状态 ← state = state.copyWith(user: user) // 更新状态
← View: ref.watch → 自动 rebuild → UI 显示用户信息 // 自动刷新 ← View: ref.watch → 自动 rebuild → UI 显示用户信息 // 自动刷新
@@ -6345,22 +6334,18 @@ class LoginViewModel extends _$LoginViewModel {
<p><strong>你只需创建一个文件</strong><code>lib/data/remote/send_message_request.dart</code>,然后在 Repository 中调用即可。</p> <p><strong>你只需创建一个文件</strong><code>lib/data/remote/send_message_request.dart</code>,然后在 Repository 中调用即可。</p>
<pre><code class="language-dart">import 'package:json_annotation/json_annotation.dart'; <pre><code class="language-dart">import 'package:networks_sdk/networks_sdk.dart';
import 'package:networks_sdk/networks_sdk.dart';
part 'send_message_request.g.dart'; part 'send_message_request.g.dart';
// ── Response DTO ── // ── Response DTO(纯 Dart 类零注解_$SendMessageResponseFromJson 由生成器自动提供)──
@JsonSerializable() class SendMessageResponse {
class SendMessageData {
@JsonKey(name: 'message_id') @JsonKey(name: 'message_id')
final String messageId; final String messageId;
final int timestamp; final int timestamp;
const SendMessageData({required this.messageId, required this.timestamp}); const SendMessageResponse({required this.messageId, required this.timestamp});
factory SendMessageData.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$SendMessageDataFromJson(json);
} }
// ── Request ── // ── Request ──
@@ -6368,26 +6353,24 @@ class SendMessageData {
@ApiRequest( @ApiRequest(
path: ApiPaths.chatSendMessage, // 路径常量,定义在 api_paths.dart path: ApiPaths.chatSendMessage, // 路径常量,定义在 api_paths.dart
method: HttpMethod.post, // HTTP 方法 method: HttpMethod.post, // HTTP 方法
responseType: SendMessageData, // 响应类型 responseType: SendMessageResponse, // 响应类型
// requestType 不写,默认 ApiRequestType.request会携带 Token // requestType 不写,默认 ApiRequestType.request会携带 Token
) )
@JsonSerializable() class SendMessageRequest extends ApiRequestable&lt;SendMessageResponse&gt;
class SendMessageRequest extends ApiRequestable&lt;SendMessageData&gt;
with _$SendMessageRequestApi { with _$SendMessageRequestApi {
@JsonKey(name: 'chat_id') // JSON 字段名和 Dart 字段名不一样时用 @JsonKey @JsonKey(name: 'chat_id') // JSON 字段名和 Dart 字段名不一样时用 @JsonKey
final String chatId; final String chatId;
final String content; final String content;
SendMessageRequest({required this.chatId, required this.content}); SendMessageRequest({required this.chatId, required this.content});
@override // toJson 自动生成:{'chat_id': chatId, 'content': content}
Map&lt;String, dynamic&gt; toJson() =&gt; _$SendMessageRequestToJson(this);
} }
</code></pre> </code></pre>
<p>保存 → 自动生成 → 然后在 Repository 中调 NetworksSdkApi 就完了:</p> <p>保存 → 自动生成 → 然后在 Repository 中调 NetworksSdkApi 就完了:</p>
<pre><code class="language-dart">// 在 MessageRepositoryImpl 中添加 <pre><code class="language-dart">// 在 MessageRepositoryImpl 中添加
Future&lt;SendMessageData?&gt; sendMessage({ Future&lt;SendMessageResponse?&gt; sendMessage({
required String chatId, required String chatId,
required String content, required String content,
}) { }) {
@@ -6407,22 +6390,19 @@ Future&lt;SendMessageData?&gt; sendMessage({
<pre><code class="language-dart">// lib/data/remote/get_profile_request.dart <pre><code class="language-dart">// lib/data/remote/get_profile_request.dart
import 'package:json_annotation/json_annotation.dart';
import 'package:networks_sdk/networks_sdk.dart'; import 'package:networks_sdk/networks_sdk.dart';
part 'get_profile_request.g.dart'; part 'get_profile_request.g.dart';
@JsonSerializable() // ── Response DTO纯 Dart 类零注解_$ProfileResponseFromJson 由生成器自动提供)──
class ProfileData { class ProfileResponse {
@JsonKey(name: 'user_id') @JsonKey(name: 'user_id')
final String userId; final String userId;
final String email; final String email;
final String? nickname; final String? nickname;
final String? avatar; final String? avatar;
const ProfileData({required this.userId, required this.email, this.nickname, this.avatar}); const ProfileResponse({required this.userId, required this.email, this.nickname, this.avatar});
factory ProfileData.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$ProfileDataFromJson(json);
User toEntity() =&gt; User(id: userId, email: email, nickname: nickname, avatar: avatar); User toEntity() =&gt; User(id: userId, email: email, nickname: nickname, avatar: avatar);
} }
@@ -6430,15 +6410,12 @@ class ProfileData {
@ApiRequest( @ApiRequest(
path: ApiPaths.userProfile, path: ApiPaths.userProfile,
method: HttpMethod.get, // ← GET 请求toJson() 结果作为 query string method: HttpMethod.get, // ← GET 请求toJson() 结果作为 query string
responseType: ProfileData, responseType: ProfileResponse,
) )
@JsonSerializable() class GetProfileRequest extends ApiRequestable&lt;ProfileResponse&gt;
class GetProfileRequest extends ApiRequestable&lt;ProfileData&gt;
with _$GetProfileRequestApi { with _$GetProfileRequestApi {
GetProfileRequest(); // 无参数 — token 标识当前用户,无需显式传 user_id GetProfileRequest(); // 无参数 — token 标识当前用户,无需显式传 user_id
// toJson 自动生成空 map {}
@override
Map&lt;String, dynamic&gt; toJson() =&gt; _$GetProfileRequestToJson(this);
} }
</code></pre> </code></pre>
@@ -6685,41 +6662,33 @@ melos run gen:watch
<pre><code class="language-dart">// data/remote/login_request.dart <pre><code class="language-dart">// data/remote/login_request.dart
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'; // ← 必须写,指向即将生成的文件 part 'login_request.g.dart'; // ← 必须写,指向即将生成的文件
// ── Response DTO ── // ── Response DTO(纯 Dart 类,零注解,零样板)──
@JsonSerializable() // _$LoginResponseFromJson 由 ApiResponseGenerator 从 @ApiRequest(responseType: LoginResponse) 自动推导生成
class LoginData { class LoginResponse {
final String token; final String token;
final String email; final String email;
const LoginData({required this.token, required this.email}); const LoginResponse({required this.token, required this.email});
// 不需要 factory fromJson不需要任何注解
// ↓ 此时 _$LoginDataFromJson 还不存在IDE 会报红,正常!
factory LoginData.fromJson(Map&lt;String, dynamic&gt; json) =&gt;
_$LoginDataFromJson(json);
Map&lt;String, dynamic&gt; toJson() =&gt; _$LoginDataToJson(this);
} }
// ── Request ── // ── Request ──
@ApiRequest( @ApiRequest(
path: ApiPaths.authLogin, path: ApiPaths.authLogin,
method: HttpMethod.post, method: HttpMethod.post,
responseType: LoginData, responseType: LoginResponse,
requestType: ApiRequestType.login, requestType: ApiRequestType.login,
) )
@JsonSerializable() class LoginRequest extends ApiRequestable&lt;LoginResponse&gt;
class LoginRequest extends ApiRequestable&lt;LoginData&gt;
with _$LoginRequestApi { // ← 短暂报红,保存后自动消失 with _$LoginRequestApi { // ← 短暂报红,保存后自动消失
final String email; final String email;
final String password; final String password;
LoginRequest({required this.email, required this.password}); LoginRequest({required this.email, required this.password});
// toJson 由 mixin 自动生成,保存后红线自动消失
@override
Map&lt;String, dynamic&gt; toJson() =&gt; _$LoginRequestToJson(this); // ← 短暂报红,保存后自动消失
} }
</code></pre> </code></pre>
@@ -6734,11 +6703,10 @@ class LoginRequest extends ApiRequestable&lt;LoginData&gt;
<p style="margin-top: 0; font-weight: 700; color: #c62828;">命名规则(写之前就能确定引用名)</p> <p style="margin-top: 0; font-weight: 700; color: #c62828;">命名规则(写之前就能确定引用名)</p>
<table> <table>
<thead><tr><th>注解</th><th>生成的符号</th><th>示例</th></tr></thead> <thead><tr><th>来源</th><th>生成的符号</th><th>示例</th></tr></thead>
<tbody> <tbody>
<tr><td><code>@JsonSerializable()</code></td><td><code>_$类名FromJson()</code></td><td><code>_$LoginDataFromJson(json)</code></td></tr> <tr><td><code>@ApiRequest(responseType: T)</code><br/>ApiResponseGenerator 推导)</td><td><code>_$类名FromJson</code>(私有函数,.g.dart 内部使用)</td><td><code>_$LoginResponseFromJson(json)</code></td></tr>
<tr><td><code>@JsonSerializable()</code></td><td><code>_$类名ToJson()</code></td><td><code>_$LoginDataToJson(this)</code></td></tr> <tr><td><code>@ApiRequest(...)</code> on Request 类<br/>ApiRequestGenerator 生成 mixin</td><td><code>_$类名Api</code>mixin</td><td><code>_$LoginRequestApi</code></td></tr>
<tr><td><code>@ApiRequest(...)</code></td><td><code>_$类名Api</code>mixin</td><td><code>_$LoginRequestApi</code></td></tr>
</tbody> </tbody>
</table> </table>

View File

@@ -6,28 +6,30 @@ import '../../../domain/entities/user.dart';
part 'get_profile_request.g.dart'; part 'get_profile_request.g.dart';
/// # /user/profile — 获取用户资料GET 请求示例 /// # /user/profile — 获取用户资料GET 请求)
/// ///
/// 演示:GET 请求 + 无 body 参数的模式 /// GET 请求无 body`toJson()` 结果自动作为 URL query parameters 发送
/// GET 请求的 toJson() 结果会自动作为 URL query parameters 发送 /// 如需 query 参数(如分页),直接在类中添加字段,生成器自动序列化
/// ///
/// ## 数据流位置 /// ## 数据流位置
/// ///
/// ``` /// ```
/// UserRepositoryImpl.getProfile() /// UserRepositoryImpl.getProfile()
/// → _client.executeRequest( ★ GetProfileRequest ★ ) ← 你在这里 /// → _client.executeRequest( ★ GetProfileRequest ★ ) ← 你在这里
/// → 服务端 GET /user/profile /// → 服务端 GET /user/profile
/// → 响应 JSON → ★ ProfileData ★ ← 也在这里 /// → SDK 内部 ApiResponseWrapper 拆包 { code, message, data }
/// → ProfileData.toEntity() → User /// → ProfileResponse ★ = data 字段 ← 也在这里
/// → ProfileResponse.toEntity() → User
/// ``` /// ```
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Response DTO // Response DTO
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
/// 用户资料响应 DTO只需反序列化禁止生成无用的 toJson /// 用户资料接口的业务响应数据(对应服务端 `data` 字段)。
@JsonSerializable(createToJson: false) ///
class ProfileData { /// `{ code, message }` 由 SDK 内部的 `ApiResponseWrapper` 统一处理。纯 Dart 类,无需任何注解。
class ProfileResponse {
final int uid; final int uid;
final String uuid; final String uuid;
@JsonKey(name: 'last_online') @JsonKey(name: 'last_online')
@@ -54,7 +56,7 @@ class ProfileData {
final int channelGroupId; final int channelGroupId;
final String hint; final String hint;
const ProfileData({ const ProfileResponse({
required this.uid, required this.uid,
required this.uuid, required this.uuid,
required this.lastOnline, required this.lastOnline,
@@ -74,10 +76,6 @@ class ProfileData {
required this.hint, required this.hint,
}); });
factory ProfileData.fromJson(Map<String, dynamic> json) =>
_$ProfileDataFromJson(json);
/// DTO → Domain Entity
User toEntity() => User( User toEntity() => User(
uid: uid, uid: uid,
uuid: uuid, uuid: uuid,
@@ -104,16 +102,12 @@ class ProfileData {
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
/// 获取用户资料请求GET无参数 /// 获取用户资料请求GET无参数
///
/// GET 请求无 bodymixin 自动生成 toJson() → 空 map。
/// 如需 query 参数(如分页),添加字段即可,
/// toJson() 会自动将字段序列化为 URL query string。
@ApiRequest( @ApiRequest(
path: ApiPaths.userProfile, path: ApiPaths.userProfile,
method: HttpMethod.get, method: HttpMethod.get,
responseType: ProfileData, responseType: ProfileResponse,
) )
class GetProfileRequest extends ApiRequestable<ProfileData> class GetProfileRequest extends ApiRequestable<ProfileResponse>
with _$GetProfileRequestApi { with _$GetProfileRequestApi {
GetProfileRequest(); GetProfileRequest();
} }

View File

@@ -12,17 +12,20 @@ part 'login_request.g.dart';
/// ///
/// ``` /// ```
/// AuthRepositoryImpl.login(email, password) /// AuthRepositoryImpl.login(email, password)
/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里 /// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里
/// → 服务端 POST /auth/login /// → 服务端 POST /auth/login
/// → 响应 JSON → ★ LoginResponse ★ ← 也在这里 /// → SDK 内部 ApiResponseWrapper 拆包 { code, message, data }
/// → LoginResponse.toEntity() → User /// → LoginResponse ★ = data 字段T in APIResponseWrapper<T> ← 也在这里
/// → LoginResponse.toEntity() → User
/// ``` /// ```
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Response DTO // Response DTO
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@JsonSerializable(createToJson: false) /// 登录响应中的用户档案,嵌套在 [LoginResponse.profile] 中。
///
/// 纯 Dart 类,无需任何注解。`_$LoginProfileFromJson` 由生成器从 `@ApiRequest` 声明中自动推导生成。
class LoginProfile { class LoginProfile {
final int uid; final int uid;
final String uuid; final String uuid;
@@ -70,9 +73,6 @@ class LoginProfile {
required this.hint, required this.hint,
}); });
factory LoginProfile.fromJson(Map<String, dynamic> json) =>
_$LoginProfileFromJson(json);
User toEntity() => User( User toEntity() => User(
uid: uid, uid: uid,
uuid: uuid, uuid: uuid,
@@ -94,8 +94,11 @@ class LoginProfile {
); );
} }
@JsonSerializable(createToJson: false, explicitToJson: true) /// 登录接口的业务响应数据(对应服务端 `data` 字段,即 T in `APIResponseWrapper<T>`)。
class LoginData { ///
/// `{ code, message }` 由 SDK 内部的 `ApiResponseWrapper` 统一处理,
/// App 层只接触此类,不感知 envelope 结构。纯 Dart 类,无需任何注解。
class LoginResponse {
@JsonKey(name: 'account_id') @JsonKey(name: 'account_id')
final String accountId; final String accountId;
final LoginProfile profile; final LoginProfile profile;
@@ -109,7 +112,7 @@ class LoginData {
@JsonKey(name: 'login_data') @JsonKey(name: 'login_data')
final String loginData; final String loginData;
const LoginData({ const LoginResponse({
required this.accountId, required this.accountId,
required this.profile, required this.profile,
required this.nonce, required this.nonce,
@@ -119,31 +122,9 @@ class LoginData {
required this.loginData, required this.loginData,
}); });
factory LoginData.fromJson(Map<String, dynamic> json) =>
_$LoginDataFromJson(json);
User toEntity() => profile.toEntity(); 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<String, dynamic> json) =>
_$LoginResponseFromJson(json);
User toEntity() => data.toEntity();
}
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Request // Request
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@@ -152,9 +133,7 @@ class LoginResponse {
/// ///
/// `@ApiRequest` 一个注解搞定一切: /// `@ApiRequest` 一个注解搞定一切:
/// - mixin 自动生成 path / method / requestType / includeToken / toJson /// - mixin 自动生成 path / method / requestType / includeToken / toJson
/// - toJson 只序列化类自身字段email, password不含继承属性 /// - parameters getter 自动注册 `_$LoginResponseFromJson` 到 SDK 全局注册表
/// - Response 的 fromJson 在 parameters getter 中自动注册
/// - 无需 @JsonSerializable无需手写 fromJson / toJson
@ApiRequest( @ApiRequest(
path: ApiPaths.authLogin, path: ApiPaths.authLogin,
method: HttpMethod.post, method: HttpMethod.post,

View File

@@ -2,14 +2,14 @@ import 'package:networks_sdk/networks_sdk.dart';
import '../../../core/foundation/api_paths.dart'; import '../../../core/foundation/api_paths.dart';
/// # /auth/logout — 登出接口(无响应数据示例) part 'logout_request.g.dart';
/// # /auth/logout — 登出接口(无响应数据)
/// ///
/// 演示POST 请求 + 无 Response DTO 的模式。
/// 服务端返回 `{"code": 0, "message": "ok"}` 无 data 字段, /// 服务端返回 `{"code": 0, "message": "ok"}` 无 data 字段,
/// `executeRequest` 返回 null调用方直接 await 即可。 /// `executeRequest` 返回 null调用方直接 await 即可。
/// ///
/// 此接口不使用 @ApiRequest 注解,直接实现 ApiRequestable /// `responseType` 省略 → 生成器跳过 `fromJson` 注册mixin 泛型为 `void`。
/// 演示手动实现方式(适用于不需要代码生成器的简单接口)。
/// ///
/// ## 数据流位置 /// ## 数据流位置
/// ///
@@ -17,16 +17,9 @@ import '../../../core/foundation/api_paths.dart';
/// AuthRepositoryImpl.logout() /// AuthRepositoryImpl.logout()
/// → _client.executeRequest( ★ LogoutRequest ★ ) ← 你在这里 /// → _client.executeRequest( ★ LogoutRequest ★ ) ← 你在这里
/// → 服务端 POST /auth/logout /// → 服务端 POST /auth/logout
/// → 响应 {"code": 0, "message": "ok"} → null /// → 响应 {"code": 0, "message": "ok"} → null(无 data
/// ``` /// ```
class LogoutRequest extends ApiRequestable<void> { @ApiRequest(path: ApiPaths.authLogout, method: HttpMethod.post)
@override class LogoutRequest extends ApiRequestable<void> with _$LogoutRequestApi {
String get path => ApiPaths.authLogout; LogoutRequest();
@override
HttpMethod get method => HttpMethod.post;
/// 登出不需要请求体参数
@override
Map<String, dynamic> toJson() => {};
} }

View File

@@ -32,8 +32,7 @@ part 'upload_file_request.g.dart';
// Response DTO // Response DTO
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
/// 文件上传响应 DTO只需反序列化禁止生成无用的 toJson /// 文件上传接口的业务响应数据(对应服务端 `data` 字段)。纯 Dart 类,无需任何注解。
@JsonSerializable(createToJson: false)
class UploadResult { class UploadResult {
final String url; final String url;
@@ -41,9 +40,6 @@ class UploadResult {
final String fileId; final String fileId;
const UploadResult({required this.url, required this.fileId}); const UploadResult({required this.url, required this.fileId});
factory UploadResult.fromJson(Map<String, dynamic> json) =>
_$UploadResultFromJson(json);
} }
// ═════════════════════════════════════════════ // ═════════════════════════════════════════════

View File

@@ -8,7 +8,7 @@ import '../remote/logout_request.dart';
/// 认证 Repository 实现 /// 认证 Repository 实现
/// ///
/// implements [AuthRepository] 接口domain/repositories/ 中定义)。 /// implements [AuthRepository] 接口domain/repositories/ 中定义)。
/// 直接使用 [ApiClient] 发送请求,将 DTO 转为 Domain Entity。 /// 直接使用 [NetworksSdkApi] 发送请求,将 DTO 转为 Domain Entity。
/// 后续可加 Local DataSource 实现离线缓存。 /// 后续可加 Local DataSource 实现离线缓存。
/// ///
/// ## 数据流位置 /// ## 数据流位置
@@ -16,11 +16,11 @@ import '../remote/logout_request.dart';
/// ``` /// ```
/// LoginUseCase.execute(email, password) /// LoginUseCase.execute(email, password)
/// → ★ AuthRepositoryImpl.login() ★ ← 你在这里 /// → ★ AuthRepositoryImpl.login() ★ ← 你在这里
/// → ApiClient.executeRequest(LoginRequest) /// → NetworksSdkApi.executeRequest(LoginRequest)
/// → 服务端 POST /auth/login /// → 服务端 POST /auth/login
/// ← LoginDataResponse DTO /// ← LoginResponseSDK 已拆包 { code, message, data } envelope
/// → onTokenUpdate(token) ← 回调写入 Token /// → _onTokenUpdate(accessToken) ← 回调写入 Token
/// ← LoginData.toEntity() → User ← DTO → Entity 转换在这里 /// ← LoginResponse.toEntity() → User ← DTO → Entity 转换在这里
/// ← UserDomain Entity /// ← UserDomain Entity
/// ``` /// ```
class AuthRepositoryImpl implements AuthRepository { class AuthRepositoryImpl implements AuthRepository {
@@ -43,7 +43,7 @@ class AuthRepositoryImpl implements AuthRepository {
throw Exception('Login failed: empty response'); throw Exception('Login failed: empty response');
} }
_onTokenUpdate(loginResponse.data.accessToken); _onTokenUpdate(loginResponse.accessToken);
return loginResponse.toEntity(); return loginResponse.toEntity();
} }

View File

@@ -1,10 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart'; 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/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:riverpod_annotation/riverpod_annotation.dart';
import 'package:storage_sdk/storage_sdk.dart'; import 'package:storage_sdk/storage_sdk.dart';
@@ -44,7 +44,7 @@ part 'login_view_model.g.dart';
/// → LoginUseCase.execute() ← 格式校验 + 调 Repository /// → LoginUseCase.execute() ← 格式校验 + 调 Repository
/// → AuthRepository.login() /// → AuthRepository.login()
/// → _client.executeRequest(LoginRequest) /// → _client.executeRequest(LoginRequest)
/// ← LoginData → User /// ← LoginResponse → User
/// ← User /// ← User
/// → state = state.copyWith(user: user) ← 更新状态 /// → state = state.copyWith(user: user) ← 更新状态
/// View: ref.watch → 自动 rebuild ← UI 刷新 /// View: ref.watch → 自动 rebuild ← UI 刷新
@@ -67,11 +67,33 @@ class LoginViewModel extends _$LoginViewModel {
final storageApi = ref.read(storageSdkProvider); final storageApi = ref.read(storageSdkProvider);
final storageLifeCycle = storageApi as StorageSdkLifecycle; final storageLifeCycle = storageApi as StorageSdkLifecycle;
// 读取 mock 数据 // 读取 mock 数据loginData.json 结构: { code, message, data: {...} }
// 手动拆包 data 字段,对应 SDK 内部 ApiResponseWrapper 的行为
final raw = await rootBundle.loadString('assets/loginData.json'); final raw = await rootBundle.loadString('assets/loginData.json');
final json = jsonDecode(raw) as Map<String, dynamic>; final json = jsonDecode(raw) as Map<String, dynamic>;
final loginResponse = LoginResponse.fromJson(json); final data = json['data'] as Map<String, dynamic>;
final user = loginResponse.data.toEntity(); final profile = data['profile'] as Map<String, dynamic>;
// 生成器生成的 _$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 操作,再标记登录状态(失败时不会误标为已登录) // 先完成 DB 操作,再标记登录状态(失败时不会误标为已登录)
await storageLifeCycle.openDatabase(user.uid); await storageLifeCycle.openDatabase(user.uid);

View File

@@ -28,9 +28,9 @@ import '../../../domain/repositories/auth_repository.dart';
/// → AuthRepository.login() /// → AuthRepository.login()
/// → AuthRepositoryImpl.login() /// → AuthRepositoryImpl.login()
/// → _client.executeRequest(LoginRequest) /// → _client.executeRequest(LoginRequest)
/// ← LoginDataDTO /// ← LoginResponseSDK 已拆包 envelope
/// → _onTokenUpdate(token) ← 回调写入 Token内存 + 持久化,由 Provider 层组合) /// → _onTokenUpdate(accessToken) ← 回调写入 Token内存 + 持久化,由 Provider 层组合)
/// ← LoginData.toEntity() → User /// ← LoginResponse.toEntity() → User
/// → SocketManager.connect(token) ← 登录后连接 WebSocket /// → SocketManager.connect(token) ← 登录后连接 WebSocket
/// → StorageSdkApi.openDatabase(user.id) ← 按用户 id 打开本地库 /// → StorageSdkApi.openDatabase(user.id) ← 按用户 id 打开本地库
/// ← User /// ← User
@@ -41,17 +41,18 @@ class LoginUseCase {
final ApiConfig _apiConfig; final ApiConfig _apiConfig;
final StorageSdkApi _storageApi; final StorageSdkApi _storageApi;
StorageSdkLifecycle get _storageLifeCycle => _storageApi as StorageSdkLifecycle; StorageSdkLifecycle get _storageLifeCycle =>
_storageApi as StorageSdkLifecycle;
LoginUseCase({ LoginUseCase({
required AuthRepository authRepository, required AuthRepository authRepository,
required SocketManager socketManager, required SocketManager socketManager,
required ApiConfig apiConfig, required ApiConfig apiConfig,
required StorageSdkApi storageApi, required StorageSdkApi storageApi,
}) : _authRepository = authRepository, }) : _authRepository = authRepository,
_socketManager = socketManager, _socketManager = socketManager,
_apiConfig = apiConfig, _apiConfig = apiConfig,
_storageApi = storageApi; _storageApi = storageApi;
/// 执行登录 /// 执行登录
/// ///
@@ -72,10 +73,7 @@ class LoginUseCase {
_validatePassword(password); _validatePassword(password);
// ── 2. 登录 ── // ── 2. 登录 ──
final user = await _authRepository.login( final user = await _authRepository.login(email: email, password: password);
email: email,
password: password,
);
// ── 3. 连接 WebSocket ── // ── 3. 连接 WebSocket ──
// token 在 Repository 的 _onTokenUpdate 回调中已写入 ApiConfig // token 在 Repository 的 _onTokenUpdate 回调中已写入 ApiConfig

View File

@@ -29,3 +29,4 @@ export 'src/domain/entities/socket_error.dart';
// Annotations代码生成 // Annotations代码生成
export 'src/annotations/api_request.dart'; export 'src/annotations/api_request.dart';
export 'src/annotations/api_response.dart';

View File

@@ -1,49 +1,56 @@
import 'package:networks_sdk/src/domain/entities/api_request_type.dart'; import 'package:networks_sdk/src/domain/entities/api_request_type.dart';
import 'package:networks_sdk/src/domain/entities/http_method.dart'; import 'package:networks_sdk/src/domain/entities/http_method.dart';
/// API 请求注解 — 标记一个类为 API 请求 /// API 请求注解 — 标记一个类为 API 请求
/// ///
/// 配合 `build_runner` 代码生成器,自动生成 `ApiRequestable<T>` 协议实现, /// 配合 `build_runner` 代码生成器,自动生成 `ApiRequestable<T>` 协议实现,
/// 使用侧只需定义字段 + 注解path / method / requestType / includeToken /// 使用侧只需定义字段 + 注解path / method / requestType / includeToken
/// 全部由生成器自动提供。 /// 全部由生成器自动提供。
/// ///
/// ## 使用方式 /// ## 有响应数据(指定 responseType
/// ///
/// ```dart /// ```dart
/// @ApiRequest( /// @ApiRequest(
/// path: '/auth/login', /// path: ApiPaths.authLogin,
/// method: HttpMethod.post, /// method: HttpMethod.post,
/// responseType: LoginData, /// responseType: LoginResponse,
/// requestType: ApiRequestType.login, /// requestType: ApiRequestType.login,
/// ) /// )
/// @JsonSerializable() /// class LoginRequest extends ApiRequestable<LoginResponse>
/// class LoginRequest extends ApiRequestable<LoginData>
/// with _$LoginRequestApi { /// with _$LoginRequestApi {
/// final String email; /// final String email;
/// final String password; /// final String password;
/// ///
/// LoginRequest({required this.email, required this.password}); /// LoginRequest({required this.email, required this.password});
///
/// @override
/// Map<String, dynamic> toJson() => _$LoginRequestToJson(this);
/// } /// }
/// ``` /// ```
/// ///
/// 生成器自动生成 `_$LoginRequestApi` mixin提供 /// ## 无响应数据(省略 responseType
/// - `path` → `'/auth/login'` ///
/// - `method` → `HttpMethod.post` /// ```dart
/// - `requestType` → `ApiRequestType.login` /// @ApiRequest(
/// - `includeToken` → `false`login 类型自动设为 false /// path: ApiPaths.authLogout,
/// method: HttpMethod.post,
/// )
/// class LogoutRequest extends ApiRequestable<void>
/// with _$LogoutRequestApi {
/// LogoutRequest();
/// }
/// ```
///
/// 生成器自动生成 `_$XxxRequestApi` mixin提供
/// - `path` / `method` / `requestType` / `includeToken`
/// - `toJson()` — 从声明字段自动生成
/// - `responseType` 存在时:`parameters` getter 自动注册 `fromJson`
class ApiRequest { class ApiRequest {
/// API 路径(如 `'/auth/login'` /// API 路径(如 `ApiPaths.authLogin`
final String path; final String path;
/// HTTP 方法(默认 POST /// HTTP 方法(默认 POST
final HttpMethod method; final HttpMethod method;
/// 响应类型(用于泛型绑定 /// 响应数据类型(省略表示无响应数据,对应 `ApiRequestable<void>`
final Type responseType; final Type? responseType;
/// 请求类型(决定 header 处理方式,默认 request /// 请求类型(决定 header 处理方式,默认 request
final ApiRequestType requestType; final ApiRequestType requestType;
@@ -57,7 +64,7 @@ class ApiRequest {
const ApiRequest({ const ApiRequest({
required this.path, required this.path,
this.method = HttpMethod.post, this.method = HttpMethod.post,
required this.responseType, this.responseType,
this.requestType = ApiRequestType.request, this.requestType = ApiRequestType.request,
this.includeToken, this.includeToken,
this.customHeaders, this.customHeaders,

View File

@@ -0,0 +1,31 @@
/// Response DTO 无需任何注解。
///
/// 生成器从 `@ApiRequest(responseType: T)` 声明出发,自动推导并生成
/// `_$TFromJson` 反序列化函数,以及所有嵌套自定义类型的 `fromJson`。
///
/// ## 使用方式(只需标注 Request 类)
///
/// ```dart
/// // Request唯一需要注解的地方
/// @ApiRequest(path: ApiPaths.authLogin, responseType: LoginResponse)
/// class LoginRequest extends ApiRequestable<LoginResponse>
/// 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();
}

View File

@@ -20,25 +20,36 @@ const _jsonKeyChecker = TypeChecker.fromUrl(
/// 支持 `@JsonKey(name: '...')` 字段重命名。 /// 支持 `@JsonKey(name: '...')` 字段重命名。
/// 如有 `@JsonKey(includeToJson: false)` 则跳过该字段。 /// 如有 `@JsonKey(includeToJson: false)` 则跳过该字段。
/// ///
/// ## 使用模式 /// ## 使用模式(有响应数据)
///
/// Request 类只需 `@ApiRequest` 注解,无需 `@JsonSerializable`
/// ///
/// ```dart /// ```dart
/// @ApiRequest( /// @ApiRequest(
/// path: ApiPaths.authLogin, /// path: ApiPaths.authLogin,
/// method: HttpMethod.post, /// method: HttpMethod.post,
/// responseType: LoginData, /// responseType: LoginResponse,
/// requestType: ApiRequestType.login, /// requestType: ApiRequestType.login,
/// ) /// )
/// class LoginRequest extends ApiRequestable<LoginData> /// class LoginRequest extends ApiRequestable<LoginResponse>
/// with _$LoginRequestApi { /// with _$LoginRequestApi {
/// final String email; /// final String email;
/// final String password; /// final String password;
/// ///
/// LoginRequest({required this.email, required this.password}); /// LoginRequest({required this.email, required this.password});
/// // 完毕toJson / path / method 全部由 mixin 自动生成 /// // 完毕toJson / path / method / fromJson 注册全部由 mixin 自动生成
/// // Response 的 fromJson 在 parameters getter 中自动注册 /// }
/// ```
///
/// ## 使用模式(无响应数据)
///
/// ```dart
/// @ApiRequest(
/// path: ApiPaths.authLogout,
/// method: HttpMethod.post,
/// )
/// class LogoutRequest extends ApiRequestable<void>
/// with _$LogoutRequestApi {
/// LogoutRequest();
/// // responseType 省略 → mixin 跳过 fromJson 注册
/// } /// }
/// ``` /// ```
/// ///
@@ -46,10 +57,10 @@ const _jsonKeyChecker = TypeChecker.fromUrl(
/// ///
/// `_$<ClassName>Api` /// `_$<ClassName>Api`
/// ///
/// ## 生成示例 /// ## 生成示例(有响应数据)
/// ///
/// ```dart /// ```dart
/// mixin _$LoginRequestApi on ApiRequestable<LoginData> { /// mixin _$LoginRequestApi on ApiRequestable<LoginResponse> {
/// @override String get path => '/auth/login'; /// @override String get path => '/auth/login';
/// @override HttpMethod get method => HttpMethod.post; /// @override HttpMethod get method => HttpMethod.post;
/// @override ApiRequestType get requestType => ApiRequestType.login; /// @override ApiRequestType get requestType => ApiRequestType.login;
@@ -61,7 +72,7 @@ const _jsonKeyChecker = TypeChecker.fromUrl(
/// }; /// };
/// @override /// @override
/// Map<String, dynamic>? get parameters { /// Map<String, dynamic>? get parameters {
/// registerResponse<LoginData>(LoginData.fromJson); /// registerResponse<LoginResponse>(_$LoginResponseFromJson);
/// return super.parameters; /// return super.parameters;
/// } /// }
/// } /// }
@@ -94,9 +105,13 @@ class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest> {
'post', 'post',
); );
// 读取 responseType用于泛型绑定 + 自动注册 fromJson // 读取 responseType可选null 表示 void 响应,无需注册 fromJson
final responseType = annotation.read('responseType').typeValue; final responseTypePeek = annotation.peek('responseType');
final responseTypeName = responseType.getDisplayString(); final bool hasResponseType =
responseTypePeek != null && !responseTypePeek.isNull;
final String responseTypeName = hasResponseType
? responseTypePeek.typeValue.getDisplayString()
: 'void';
// 读取 ApiRequestType 枚举值 // 读取 ApiRequestType 枚举值
final requestTypeName = _readEnumName( final requestTypeName = _readEnumName(
@@ -116,6 +131,14 @@ class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest> {
// 从类的声明字段生成 toJson() // 从类的声明字段生成 toJson()
final toJsonBody = _buildToJsonBody(element, className); 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 ''' return '''
/// Generated by @ApiRequest for [$className] /// Generated by @ApiRequest for [$className]
mixin _\$${className}Api on ApiRequestable<$responseTypeName> { mixin _\$${className}Api on ApiRequestable<$responseTypeName> {
@@ -131,8 +154,7 @@ mixin _\$${className}Api on ApiRequestable<$responseTypeName> {
Map<String, dynamic> toJson() => $toJsonBody; Map<String, dynamic> toJson() => $toJsonBody;
@override @override
Map<String, dynamic>? get parameters { Map<String, dynamic>? get parameters {
registerResponse<$responseTypeName>($responseTypeName.fromJson); $parametersBody
return super.parameters;
} }
} }
'''; ''';

View File

@@ -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<LoginResponse>
/// 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<String, dynamic> json) {
/// return LoginProfile(
/// uid: json['uid'] as int,
/// uuid: json['uuid'] as String,
/// );
/// }
///
/// LoginResponse _$LoginResponseFromJson(Map<String, dynamic> json) {
/// return LoginResponse(
/// accessToken: json['access_token'] as String,
/// profile: _$LoginProfileFromJson(json['profile'] as Map<String, dynamic>),
/// );
/// }
/// ```
///
/// ## 支持的字段类型
///
/// - 基础类型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 = <String>{};
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<String> 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<String, dynamic> 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 = <String>[];
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<String, dynamic>)';
}
return '_\$${typeName}FromJson($access as Map<String, dynamic>)';
}
// 兜底
return '$access as dynamic';
}
}

View File

@@ -2,11 +2,16 @@ import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart'; import 'package:source_gen/source_gen.dart';
import 'api_request_generator.dart'; import 'api_request_generator.dart';
import 'api_response_generator.dart';
/// @ApiRequest 代码生成器入口 /// @ApiRequest / @ApiResponse 代码生成器入口
/// ///
/// 在 `build.yaml` 中注册此 builder配合 `build_runner` 使用。 /// 在 `build.yaml` 中注册此 builder配合 `build_runner` 使用。
/// 生成的代码通过 `SharedPartBuilder` 合并到 `.g.dart` 文件中 /// 生成的代码通过 `SharedPartBuilder` 合并到 `.g.dart` 文件中
/// 与 `json_serializable` 等生成器共存。 ///
Builder apiRequestBuilder(BuilderOptions options) => /// - `ApiRequestGenerator`:为 `@ApiRequest` 生成 Request mixintoJson + path/method 等)
SharedPartBuilder([ApiRequestGenerator()], 'api_request'); /// - `ApiResponseGenerator`:从 `@ApiRequest(responseType: T)` 推导,生成 Response 的所有 `_$XFromJson` 函数
Builder apiRequestBuilder(BuilderOptions options) => SharedPartBuilder([
ApiRequestGenerator(),
ApiResponseGenerator(),
], 'api_request');