Initial project

This commit is contained in:
Cody
2026-03-06 14:56:17 +08:00
parent 977b627b15
commit bf9e099747
1180 changed files with 50973 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:networks_sdk/networks_sdk.dart';
import '../../../core/foundation/api_paths.dart';
import '../../../domain/entities/user.dart';
part 'get_profile_request.g.dart';
/// # /user/profile — 获取用户资料GET 请求示例)
///
/// 演示GET 请求 + 无 body 参数的模式。
/// GET 请求的 toJson() 结果会自动作为 URL query parameters 发送。
///
/// ## 数据流位置
///
/// ```
/// UserRepositoryImpl.getProfile()
/// → _client.executeRequest( ★ GetProfileRequest ★ ) ← 你在这里
/// → 服务端 GET /user/profile
/// → 响应 JSON → ★ ProfileData ★ ← 也在这里
/// → ProfileData.toEntity() → User
/// ```
// ─────────────────────────────────────────────
// Response DTO
// ─────────────────────────────────────────────
/// 用户资料响应 DTO只需反序列化禁止生成无用的 toJson
@JsonSerializable(createToJson: false)
class ProfileData {
@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);
/// DTO → Domain Entity
User toEntity() {
return User(
id: userId,
email: email,
nickname: nickname,
avatar: avatar,
);
}
}
// ─────────────────────────────────────────────
// Request
// ─────────────────────────────────────────────
/// 获取用户资料请求GET无参数
///
/// GET 请求无 bodytoJson() 返回空 map。
/// 如需 query 参数(如分页),添加字段即可,
/// toJson() 会自动将字段序列化为 URL query string。
@ApiRequest(
path: ApiPaths.userProfile,
method: HttpMethod.get,
responseType: ProfileData,
)
@JsonSerializable()
class GetProfileRequest extends ApiRequestable<ProfileData>
with _$GetProfileRequestApi {
GetProfileRequest();
factory GetProfileRequest.fromJson(Map<String, dynamic> json) =>
_$GetProfileRequestFromJson(json);
@override
Map<String, dynamic> toJson() => _$GetProfileRequestToJson(this);
}

View File

@@ -0,0 +1,90 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:networks_sdk/networks_sdk.dart';
import '../../../core/foundation/api_paths.dart';
import '../../../domain/entities/user.dart';
part 'login_request.g.dart';
/// # /auth/login — 登录接口
///
/// 一个端点 = 一个文件Response DTO + Request 放在同一文件中。
///
/// ## 数据流位置
///
/// ```
/// AuthRepositoryImpl.login(email, password)
/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里
/// → 服务端 POST /auth/login
/// → 响应 JSON → ★ LoginData ★ ← 也在这里
/// → LoginData.toEntity() → User
/// ```
// ─────────────────────────────────────────────
// Response DTO
// ─────────────────────────────────────────────
/// 登录响应 DTO
///
/// 服务端返回的登录数据,包含 token 和用户信息。
/// 通过 [toEntity] 转换为 Domain Entity [User]。
@JsonSerializable()
class LoginData {
final String token;
@JsonKey(name: 'user_id')
final String userId;
final String email;
final String? nickname;
final String? avatar;
const LoginData({
required this.token,
required this.userId,
required this.email,
this.nickname,
this.avatar,
});
factory LoginData.fromJson(Map<String, dynamic> json) =>
_$LoginDataFromJson(json);
Map<String, dynamic> toJson() => _$LoginDataToJson(this);
/// DTO → Domain Entity
User toEntity() {
return User(
id: userId,
email: email,
nickname: nickname,
avatar: avatar,
);
}
}
// ─────────────────────────────────────────────
// Request
// ─────────────────────────────────────────────
/// 登录请求
///
/// `@ApiRequest` 自动生成 `_$LoginRequestApi` mixin
/// 提供 path / method / requestType / includeToken / fromJson 自动注册。
@ApiRequest(
path: ApiPaths.authLogin,
method: HttpMethod.post,
responseType: LoginData,
requestType: ApiRequestType.login,
)
@JsonSerializable()
class LoginRequest extends ApiRequestable<LoginData> with _$LoginRequestApi {
final String email;
final String password;
LoginRequest({required this.email, required this.password});
factory LoginRequest.fromJson(Map<String, dynamic> json) =>
_$LoginRequestFromJson(json);
@override
Map<String, dynamic> toJson() => _$LoginRequestToJson(this);
}

View File

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

View File

@@ -0,0 +1,160 @@
import 'dart:typed_data';
import 'package:json_annotation/json_annotation.dart';
import 'package:networks_sdk/networks_sdk.dart';
import '../../../core/foundation/api_paths.dart';
part 'upload_file_request.g.dart';
/// # /upload/file — 文件上传Upload 请求示例)
///
/// 演示两种上传模式:
///
/// ## 模式 A: FormData 上传到自有后端
/// 适用于后端直接接收文件的场景。
/// 使用 [UploadFileRequest] — path 为相对路径SDK 自动拼 baseURL。
///
/// ## 模式 B: 二进制上传到 S3 presigned URL
/// 适用于先向后端获取 presigned URL再直接上传到 S3 的场景。
/// 使用 [S3UploadRequest] — path 为完整 URLoverride decodeResponse。
///
/// ## Upload 与普通请求的区别
///
/// | 普通请求 | Upload 请求 |
/// |---------|-----------|
/// | `toJson()` → JSON body | `uploadData` → FormData / Uint8List |
/// | `requestType: request` | `requestType: upload` |
/// | `parameters` 有值 | `parameters` 返回 null |
/// | 标准 `{ code, msg, data }` 响应 | 可能需要 override `decodeResponse` |
// ─────────────────────────────────────────────
// Response DTO
// ─────────────────────────────────────────────
/// 文件上传响应 DTO只需反序列化禁止生成无用的 toJson
@JsonSerializable(createToJson: false)
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);
}
// ═════════════════════════════════════════════
// 模式 A: FormData 上传到自有后端
// ═════════════════════════════════════════════
/// FormData 上传请求
///
/// 上传到自有后端 `/upload/file`,响应为标准 `{ code, message, data }` 信封。
/// 无需 override `decodeResponse`。
@ApiRequest(
path: ApiPaths.uploadFile,
method: HttpMethod.post,
responseType: UploadResult,
requestType: ApiRequestType.upload,
)
class UploadFileRequest extends ApiRequestable<UploadResult>
with _$UploadFileRequestApi {
final String filePath;
final String? fileName;
UploadFileRequest({required this.filePath, this.fileName});
@override
Map<String, dynamic> toJson() => {};
/// FormData — SDK 通过 uploadData 获取上传数据
@override
Object? get uploadData {
return FormData.fromMap({
'file': MultipartFile.fromFileSync(filePath, filename: fileName),
});
}
}
// ═════════════════════════════════════════════
// 模式 B: 二进制上传到 S3 presigned URL
// ═════════════════════════════════════════════
/// S3 presigned URL 上传响应
class S3UploadResponse {
final bool success;
final String? message;
const S3UploadResponse({this.success = true, this.message});
}
/// S3 presigned URL 上传请求
///
/// 特点:
/// - path 为完整的 presigned URLSDK 检测到 http 开头不拼 baseURL
/// - uploadData 为 Uint8List 二进制数据
/// - 自定义 headersContent-Type: application/octet-stream
/// - override decodeResponse — S3 返回 204 No Content 或 XML不是标准信封
class S3UploadRequest extends ApiRequestable<S3UploadResponse> {
final Uint8List data;
final String presignedURL;
S3UploadRequest({required this.data, required this.presignedURL});
@override
String get path => presignedURL;
@override
HttpMethod get method => HttpMethod.put;
@override
ApiRequestType get requestType => ApiRequestType.upload;
@override
Map<String, String>? get customHeaders => {
'Content-Type': 'application/octet-stream',
};
@override
Map<String, dynamic> toJson() => {};
/// 二进制数据
@override
Object? get uploadData => data;
/// S3 响应不走标准 { code, message, data } 信封,需要自定义解码
///
/// 可能的响应:
/// - 204 No Content空 body→ 成功
/// - 200 + XML body → 成功
/// - 200 + JSON body → 尝试解码
@override
S3UploadResponse? decodeResponse(Response response) {
// 空响应或 2xx 状态码 → 成功
if (response.data == null ||
(response.data is List && (response.data as List).isEmpty)) {
return const S3UploadResponse(success: true);
}
// JSON 响应 → 尝试解码
if (response.data is Map<String, dynamic>) {
final json = response.data as Map<String, dynamic>;
return S3UploadResponse(
success: true,
message: json['message'] as String?,
);
}
// 2xx 状态码 → 成功
if (response.statusCode != null &&
response.statusCode! >= 200 &&
response.statusCode! < 300) {
return const S3UploadResponse(success: true);
}
return const S3UploadResponse(success: true);
}
}