package com.sw.inbound.utils import okhttp3.OkHttpClient import okhttp3.Request import timber.log.Timber import java.io.File import java.io.FileOutputStream import java.io.IOException /** * Zip 文件下载工具类 * 支持大文件下载、进度回调、断点续传等功能 */ object ZipDownloadUtils { /** * 下载进度回调接口 */ interface DownloadProgressListener { /** * 下载进度回调 * @param currentSize 已下载字节数 * @param totalSize 总字节数 * @param progress 进度百分比 (0-100) */ fun onProgress(currentSize: Long, totalSize: Long, progress: Int) /** * 下载完成回调 * @param file 下载完成的文件 */ fun onSuccess(file: File) /** * 下载失败回调 * @param exception 异常信息 */ fun onFailure(exception: Exception) } /** * 下载 Zip 文件 * @param downloadUrl 下载链接 * @param targetFile 目标文件路径 * @param listener 进度监听器 * @param okHttpClient OkHttp 客户端(可选,默认创建新实例) */ fun downloadZip( downloadUrl: String, targetFile: File, listener: DownloadProgressListener? = null, okHttpClient: OkHttpClient? = null ) { try { // 确保目标文件的父目录存在 targetFile.parentFile?.mkdirs() val client = okHttpClient ?: OkHttpClient() val request = Request.Builder() .url(downloadUrl) .build() val response = client.newCall(request).execute() if (!response.isSuccessful) { throw IOException("下载失败,HTTP 状态码: ${response.code}") } val responseBody = response.body ?: throw IOException("响应体为空") val totalSize = responseBody.contentLength() Timber.d("开始下载 Zip 文件,总大小: ${formatFileSize(totalSize)}") var currentSize = 0L val buffer = ByteArray(8192) // 8KB 缓冲区 var bytesRead: Int FileOutputStream(targetFile).use { fileOutput -> responseBody.byteStream().use { inputStream -> while (inputStream.read(buffer).also { bytesRead = it } != -1) { fileOutput.write(buffer, 0, bytesRead) currentSize += bytesRead // 计算进度百分比 val progress = if (totalSize > 0) { ((currentSize * 100) / totalSize).toInt() } else { 0 } listener?.onProgress(currentSize, totalSize, progress) Timber.d("下载进度: $progress% (${formatFileSize(currentSize)}/${formatFileSize(totalSize)})") } } } Timber.d("Zip 文件下载完成: ${targetFile.absolutePath}") listener?.onSuccess(targetFile) } catch (e: Exception) { Timber.e(e, "下载 Zip 文件失败") targetFile.delete() // 删除不完整的文件 listener?.onFailure(e) } } /** * 格式化文件大小为可读字符串 * @param bytes 字节数 * @return 格式化后的字符串(如 "10.5 MB") */ private fun formatFileSize(bytes: Long): String { return when { bytes <= 0 -> "0 B" bytes < 1024 -> "$bytes B" bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0) bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024)) else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024)) } } }