Files
customer-im-client-dev/apps/im_app/lib/data/remote/upload_file_request.dart
2026-03-09 19:05:55 +08:00

157 lines
5.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:typed_data';
import 'package:json_annotation/json_annotation.dart';
import 'package:networks_sdk/networks_sdk.dart';
import 'package:im_app/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
// ─────────────────────────────────────────────
/// 文件上传接口的业务响应数据(对应服务端 `data` 字段)。纯 Dart 类,无需任何注解。
class UploadResult {
final String url;
@JsonKey(name: 'file_id')
final String fileId;
const UploadResult({required this.url, required this.fileId});
}
// ═════════════════════════════════════════════
// 模式 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);
}
}