初始化
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../config/address.dart';
|
||||
import '../../extension/extensions.dart';
|
||||
import '../debug_log.dart';
|
||||
import '../image/image_data_handle/image_crypto.dart';
|
||||
import '../net/load_apk/dio_cli.dart';
|
||||
|
||||
class ImageCacheManager extends CacheManager {
|
||||
static const image_key = "customCache";
|
||||
|
||||
// 工厂模式
|
||||
factory ImageCacheManager() => _getInstance();
|
||||
|
||||
static ImageCacheManager get instance => _getInstance();
|
||||
static ImageCacheManager? _instance;
|
||||
|
||||
static ImageCacheManager _getInstance() {
|
||||
_instance ??= ImageCacheManager._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
ImageCacheManager._internal()
|
||||
: super(Config(image_key, maxNrOfCacheObjects: 400, stalePeriod: const Duration(days: 7), fileService: CustomFileRespons()));
|
||||
|
||||
Future<String> getFilePath() async {
|
||||
var directory = await getTemporaryDirectory();
|
||||
return path.join(directory.path, image_key);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomFileRespons extends HttpFileService {
|
||||
DioCli client = DioCli(
|
||||
options: BaseOptions(
|
||||
connectTimeout: const Duration(milliseconds: 30000),
|
||||
receiveTimeout: const Duration(milliseconds: 30000),
|
||||
sendTimeout: const Duration(milliseconds: 30000),
|
||||
validateStatus: (int? status) {
|
||||
if (status != null) return status < 600;
|
||||
return false;
|
||||
}));
|
||||
|
||||
@override
|
||||
Future<FileServiceResponse> get(String url, {Map<String, String>? headers = const {}}) async {
|
||||
if (!url.startsWith("http") && !url.startsWith("https")) {
|
||||
url = path.join(Address.baseImagePath ?? '', url);
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
debugLog("image_request", "get()...begin...ulr:$url...");
|
||||
}
|
||||
|
||||
headers?['cache-control'] = 'max-age=31104000';
|
||||
|
||||
final resp = await client.getBytes(url, headers: headers);
|
||||
final statusCode = resp.data?.statusCode ?? HttpStatus.badRequest;
|
||||
|
||||
// 失败 / 非 2xx:直接抛。避免错误响应(空内容、CDN 错误页)被落盘缓存成坏图,
|
||||
// 让 CachedNetworkImage 走 errorWidget;下次能重新请求,而不是一直命中坏缓存。
|
||||
if (resp.err != null || statusCode < 200 || statusCode >= 300) {
|
||||
if (kDebugMode) {
|
||||
debugLog("image_request", "get()...failed...$url...code:($statusCode): ${resp.err}");
|
||||
}
|
||||
throw HttpException("image fetch failed ($statusCode): ${resp.err}", uri: Uri.tryParse(url));
|
||||
}
|
||||
|
||||
// 解密(Dio 不同版本 bytes 返回 List<int> 或 Uint8List)。解密失败 / 空字节同样抛出,不缓存坏图。
|
||||
Uint8List bytes;
|
||||
try {
|
||||
final raw = resp.data?.data;
|
||||
final decrypted = raw == null ? null : ImageCrypto.decryptImage(raw is Uint8List ? raw : Uint8List.fromList(raw));
|
||||
if (decrypted == null || decrypted.isEmpty) {
|
||||
throw const FormatException("empty image bytes");
|
||||
}
|
||||
//部分 CDN 返回的加密 JPEG 缺末尾 EOI(FF D9),Skia 严格 decoder 会拒;补 EOI 让标准 decoder 也能解
|
||||
bytes = _repairJpegEoiIfNeeded(decrypted);
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugLog("ImageCacheManager", "decrypt failed: $url -> $e");
|
||||
throw HttpException("image decrypt failed: $e", uri: Uri.tryParse(url));
|
||||
}
|
||||
|
||||
final respHeaders = <String, String>{};
|
||||
resp.data?.headers.forEach((key, arrayValue) {
|
||||
if (arrayValue.notEmpty()) respHeaders[key] = arrayValue[0];
|
||||
});
|
||||
|
||||
if (kDebugMode) {
|
||||
debugLog("image_request", "get()...success...$url...code:($statusCode): contentLength:${bytes.length}");
|
||||
}
|
||||
|
||||
return HttpGetResponse(StreamedResponse(
|
||||
Stream.value(bytes),
|
||||
statusCode,
|
||||
contentLength: bytes.length, // 补 EOI 后用真实长度
|
||||
headers: respHeaders,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
//判断是 jpeg 头但无 FF D9 结尾时补 EOI;其他格式 / 已完整 jpeg 原样返回
|
||||
Uint8List _repairJpegEoiIfNeeded(Uint8List bytes) {
|
||||
if (bytes.length < 4) return bytes;
|
||||
final isJpeg = bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF;
|
||||
if (!isJpeg) return bytes;
|
||||
final hasEoi = bytes[bytes.length - 2] == 0xFF && bytes[bytes.length - 1] == 0xD9;
|
||||
if (hasEoi) return bytes;
|
||||
final fixed = Uint8List(bytes.length + 2)
|
||||
..setRange(0, bytes.length, bytes)
|
||||
..[bytes.length] = 0xFF
|
||||
..[bytes.length + 1] = 0xD9;
|
||||
return fixed;
|
||||
}
|
||||
Reference in New Issue
Block a user