极简接口定义和响应定义,支持更多的解析器
This commit is contained in:
@@ -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> {
|
||||
|
||||
<p>一个端点 = 一个文件(<code>data/remote/login_request.dart</code>),Response DTO + Request 放在同一文件中。</p>
|
||||
|
||||
<pre><code class="language-dart">import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
<pre><code class="language-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 注册也全部自动处理
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<pre><code class="language-dart">// 使用 - 超级简单!
|
||||
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
|
||||
</code></pre>
|
||||
|
||||
<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>
|
||||
<td><strong>@ApiRequest(当前方案)</strong></td>
|
||||
<td>字段 + 构造函数 + @ApiRequest</td>
|
||||
<td>path / method / requestType / includeToken / toJson / fromJson 注册</td>
|
||||
<td>字段 + 构造函数 + @ApiRequest<br/><em>(Response DTO:字段 + 构造函数,零注解)</em></td>
|
||||
<td>Request: path / method / requestType / includeToken / toJson / fromJson 注册<br/>Response: _$XFromJson 私有反序列化函数(按需递归生成嵌套类型)</td>
|
||||
<td><strong>极低</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -2368,10 +2367,10 @@ final user = loginData?.toEntity(); // DTO → Domain Entity
|
||||
|
||||
<p><strong>核心优势</strong>:</p>
|
||||
<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>:Response DTO + Request 放在同一文件,打开即看全貌</li>
|
||||
<li><strong>傻瓜式使用</strong>:使用者只需关注业务字段和注解配置</li>
|
||||
<li><strong>类型安全</strong>:<code>ApiRequestable<T></code> 泛型 + <code>responseType</code> 编译期检查</li>
|
||||
</ul>
|
||||
|
||||
@@ -2387,12 +2386,12 @@ final user = loginData?.toEntity(); // DTO → Domain Entity
|
||||
<tbody>
|
||||
<tr>
|
||||
<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>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Dart</strong></td>
|
||||
<td><code>@ApiRequest(...) class LoginRequest extends ApiRequestable<LoginData> with _$LoginRequestApi { ... }</code></td>
|
||||
<td><code>@ApiRequest(...) class LoginRequest extends ApiRequestable<LoginResponse> with _$LoginRequestApi { ... }</code></td>
|
||||
<td>注解 + 代码生成,接近 Swift 体验</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -2530,26 +2529,23 @@ melos run gen
|
||||
|
||||
<h6>4.5 更多使用示例</h6>
|
||||
|
||||
<p>所有示例遵循同一模式:<code>@ApiRequest</code> + <code>extends ApiRequestable<T> with _$XxxApi</code>。Request 类无需 <code>@JsonSerializable</code>。</p>
|
||||
<p>所有示例遵循同一模式:<code>@ApiRequest</code> + <code>extends ApiRequestable<T> with _$XxxApi</code>。Request 和 Response 均无需 <code>@JsonSerializable</code>,Response DTO 连 <code>factory fromJson</code> 也不需要。</p>
|
||||
|
||||
<p><strong>发送消息请求(POST + @JsonKey 字段重命名):</strong></p>
|
||||
<pre><code class="language-dart">// 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>
|
||||
<p><strong>获取用户资料(GET,靠 token 标识当前用户,无需传参):</strong></p>
|
||||
<pre><code class="language-dart">// 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>
|
||||
<p><strong>上传文件请求(FormData multipart):</strong></p>
|
||||
<pre><code class="language-dart">// 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>
|
||||
<div style="background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 20px 0;">
|
||||
<p><strong>核心价值</strong></p>
|
||||
<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>:泛型 <code>ApiRequestable<T></code> + <code>responseType</code> 编译期检查</li>
|
||||
<li><strong>一个端点 = 一个文件</strong>:Response DTO + Request 放在同一文件,打开即看全貌</li>
|
||||
@@ -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
|
||||
<tr><th>层级</th><th>文件命名</th><th>类命名</th><th>示例</th></tr>
|
||||
</thead>
|
||||
<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>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>
|
||||
@@ -5990,8 +5983,8 @@ flowchart LR
|
||||
<p><strong>关键规则</strong>:</p>
|
||||
<ul>
|
||||
<li><strong>一个端点 = 一个 Request 文件</strong>:Response DTO + Request 类放在同一文件中</li>
|
||||
<li><strong>Response DTO 必须有 <code>toEntity()</code></strong>:统一 DTO → Domain Entity 的转换入口</li>
|
||||
<li><strong>持久化 DTO 和 Response DTO 分开</strong>:Response DTO(<code>XxxData</code>)在 request 文件中,持久化 DTO(<code>XxxDto</code>)在 <code>data/models/</code></li>
|
||||
<li><strong>Response DTO 是纯 Dart 类</strong>:零注解、零 <code>factory fromJson</code>;只需字段 + 构造函数,<code>toEntity()</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>
|
||||
</ul>
|
||||
|
||||
@@ -6041,8 +6034,10 @@ part 'login_request.g.dart'; // 这行必须写!指向即将自动生成的
|
||||
<pre><code class="language-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
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -6124,14 +6113,14 @@ class LoginRequest extends ApiRequestable<LoginData> // ← 固定写法
|
||||
<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>
|
||||
<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>
|
||||
<tr><td><code>LoginData</code></td><td><code>_$LoginDataFromJson</code></td><td><code>_$LoginDataToJson</code></td><td>-</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>SendMessageRequest</code></td><td><code>_$SendMessageRequestFromJson</code></td><td><code>_$SendMessageRequestToJson</code></td><td><code>_$SendMessageRequestApi</code></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>-(不需要)</td><td>由 mixin 自动生成</td><td><code>_$LoginRequestApi</code></td><td>ApiRequestGenerator 生成 mixin</td></tr>
|
||||
<tr><td><code>SendMessageRequest</code></td><td>-(不需要)</td><td>由 mixin 自动生成</td><td><code>_$SendMessageRequestApi</code></td><td>ApiRequestGenerator 生成 mixin</td></tr>
|
||||
</tbody>
|
||||
</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>
|
||||
|
||||
<!-- ────────── 第 2 步 ────────── -->
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
@@ -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 {
|
||||
|
||||
<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';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
<pre><code class="language-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}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>保存 → 自动生成 → 然后在 Repository 中调 NetworksSdkApi 就完了:</p>
|
||||
|
||||
<pre><code class="language-dart">// 在 MessageRepositoryImpl 中添加
|
||||
Future<SendMessageData?> sendMessage({
|
||||
Future<SendMessageResponse?> sendMessage({
|
||||
required String chatId,
|
||||
required String content,
|
||||
}) {
|
||||
@@ -6407,22 +6390,19 @@ Future<SendMessageData?> sendMessage({
|
||||
|
||||
<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';
|
||||
|
||||
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 {}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -6685,41 +6662,33 @@ melos run gen:watch
|
||||
|
||||
<pre><code class="language-dart">// 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 自动生成,保存后红线自动消失
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -6734,11 +6703,10 @@ class LoginRequest extends ApiRequestable<LoginData>
|
||||
<p style="margin-top: 0; font-weight: 700; color: #c62828;">命名规则(写之前就能确定引用名)</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>注解</th><th>生成的符号</th><th>示例</th></tr></thead>
|
||||
<thead><tr><th>来源</th><th>生成的符号</th><th>示例</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>@JsonSerializable()</code></td><td><code>_$类名FromJson()</code></td><td><code>_$LoginDataFromJson(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></td><td><code>_$类名Api</code>(mixin)</td><td><code>_$LoginRequestApi</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>@ApiRequest(...)</code> on Request 类<br/>(ApiRequestGenerator 生成 mixin)</td><td><code>_$类名Api</code>(mixin)</td><td><code>_$LoginRequestApi</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -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<String, dynamic> 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<ProfileData>
|
||||
class GetProfileRequest extends ApiRequestable<ProfileResponse>
|
||||
with _$GetProfileRequestApi {
|
||||
GetProfileRequest();
|
||||
}
|
||||
|
||||
@@ -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<T> ← 也在这里
|
||||
/// → 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<String, dynamic> 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<T>`)。
|
||||
///
|
||||
/// `{ 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<String, dynamic> 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<String, dynamic> 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,
|
||||
|
||||
@@ -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<void> {
|
||||
@override
|
||||
String get path => ApiPaths.authLogout;
|
||||
|
||||
@override
|
||||
HttpMethod get method => HttpMethod.post;
|
||||
|
||||
/// 登出不需要请求体参数
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {};
|
||||
@ApiRequest(path: ApiPaths.authLogout, method: HttpMethod.post)
|
||||
class LogoutRequest extends ApiRequestable<void> with _$LogoutRequestApi {
|
||||
LogoutRequest();
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic> json) =>
|
||||
_$UploadResultFromJson(json);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic>;
|
||||
final loginResponse = LoginResponse.fromJson(json);
|
||||
final user = loginResponse.data.toEntity();
|
||||
final data = json['data'] as Map<String, dynamic>;
|
||||
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 操作,再标记登录状态(失败时不会误标为已登录)
|
||||
await storageLifeCycle.openDatabase(user.uid);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -29,3 +29,4 @@ export 'src/domain/entities/socket_error.dart';
|
||||
|
||||
// Annotations(代码生成)
|
||||
export 'src/annotations/api_request.dart';
|
||||
export 'src/annotations/api_response.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<T>` 协议实现,
|
||||
/// 使用侧只需定义字段 + 注解,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<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);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 生成器自动生成 `_$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<void>
|
||||
/// 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<void>`)
|
||||
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,
|
||||
|
||||
31
packages/networks_sdk/lib/src/annotations/api_response.dart
Normal file
31
packages/networks_sdk/lib/src/annotations/api_response.dart
Normal 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();
|
||||
}
|
||||
@@ -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<LoginData>
|
||||
/// class LoginRequest extends ApiRequestable<LoginResponse>
|
||||
/// 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<void>
|
||||
/// with _$LogoutRequestApi {
|
||||
/// LogoutRequest();
|
||||
/// // responseType 省略 → mixin 跳过 fromJson 注册
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
@@ -46,10 +57,10 @@ const _jsonKeyChecker = TypeChecker.fromUrl(
|
||||
///
|
||||
/// `_$<ClassName>Api`
|
||||
///
|
||||
/// ## 生成示例
|
||||
/// ## 生成示例(有响应数据)
|
||||
///
|
||||
/// ```dart
|
||||
/// mixin _$LoginRequestApi on ApiRequestable<LoginData> {
|
||||
/// mixin _$LoginRequestApi on ApiRequestable<LoginResponse> {
|
||||
/// @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<String, dynamic>? get parameters {
|
||||
/// registerResponse<LoginData>(LoginData.fromJson);
|
||||
/// registerResponse<LoginResponse>(_$LoginResponseFromJson);
|
||||
/// return super.parameters;
|
||||
/// }
|
||||
/// }
|
||||
@@ -94,9 +105,13 @@ class ApiRequestGenerator extends GeneratorForAnnotation<ApiRequest> {
|
||||
'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<ApiRequest> {
|
||||
// 从类的声明字段生成 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<String, dynamic> toJson() => $toJsonBody;
|
||||
@override
|
||||
Map<String, dynamic>? get parameters {
|
||||
registerResponse<$responseTypeName>($responseTypeName.fromJson);
|
||||
return super.parameters;
|
||||
$parametersBody
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user