// // TravelAlbumOTGUploader.swift // suixinkan // import Foundation /// 旅拍相册 OTG 上传服务协议。 @MainActor protocol TravelAlbumOTGUploading { /// 上传本地照片并登记为旅拍相册素材。 func upload( record: TravelAlbumOTGPhotoRecord, scenicId: Int, progress: @escaping (Int) -> Void ) async throws -> TravelAlbumMaterial } /// 旅拍相册 OTG 上传服务,负责 OSS 上传与 `upload-material` 素材登记。 @MainActor final class TravelAlbumOTGUploader: TravelAlbumOTGUploading { private let uploader: OSSUploadService private let api: any TravelAlbumServing /// 初始化旅拍相册 OTG 上传服务。 init( uploader: OSSUploadService? = nil, api: (any TravelAlbumServing)? = nil ) { self.uploader = uploader ?? NetworkServices.shared.ossUploadService self.api = api ?? NetworkServices.shared.travelAlbumAPI } /// 上传本地照片并登记为旅拍相册素材。 func upload( record: TravelAlbumOTGPhotoRecord, scenicId: Int, progress: @escaping (Int) -> Void ) async throws -> TravelAlbumMaterial { guard record.albumId > 0 else { throw TravelAlbumOTGUploadError.invalidAlbum } guard !record.localPath.isEmpty, FileManager.default.fileExists(atPath: record.localPath) else { throw TravelAlbumOTGUploadError.localFileMissing } let data = try Data(contentsOf: URL(fileURLWithPath: record.localPath)) let remoteURL = try await uploader.uploadTravelAlbumMaterial( data: data, fileName: record.fileName, scenicId: scenicId, onProgress: progress ) return try await api.uploadMaterial( TravelAlbumUploadMaterialRequest( userEquityTravelId: record.albumId, fileName: record.fileName, fileUrl: remoteURL ) ) } } /// 旅拍相册 OTG 上传错误。 enum TravelAlbumOTGUploadError: LocalizedError, Equatable { case invalidAlbum case localFileMissing var errorDescription: String? { switch self { case .invalidAlbum: return "请先选择有效的相册" case .localFileMissing: return "本地文件不存在,无法上传" } } }