修复代码格式

This commit is contained in:
2026-04-03 20:56:21 +08:00
parent d0f81bf18e
commit 668199ce3f
6 changed files with 87 additions and 67 deletions

View File

@@ -20,8 +20,8 @@ class AppConfig {
/// 从环境变量创建配置 /// 从环境变量创建配置
AppConfig._fromEnvironment() AppConfig._fromEnvironment()
: dataDir = Platform.environment['DATA_DIR'] ?? 'data', : dataDir = Platform.environment['DATA_DIR'] ?? 'data',
cacheDir = Platform.environment['CACHE_DIR'] ?? 'cache'; cacheDir = Platform.environment['CACHE_DIR'] ?? 'cache';
/// 获取相册预览缓存目录 /// 获取相册预览缓存目录
String get previewCacheDir => '$cacheDir/preview'; String get previewCacheDir => '$cacheDir/preview';

View File

@@ -37,5 +37,4 @@ class AlbumRepository {
final albums = await getAllAlbums(); final albums = await getAllAlbums();
return albums.where((a) => a.id == id).firstOrNull; return albums.where((a) => a.id == id).firstOrNull;
} }
} }

View File

@@ -18,12 +18,13 @@ class PhotoRepository {
Directory? albumDir; Directory? albumDir;
try { try {
albumDir = await dir albumDir =
.list() await dir
.where((f) => f is Directory) .list()
.where((d) => p.basename(d.path).hashCode == id) .where((f) => f is Directory)
.first .where((d) => p.basename(d.path).hashCode == id)
as Directory; .first
as Directory;
} on StateError { } on StateError {
return []; return [];
} }
@@ -192,24 +193,32 @@ class PhotoRepository {
/// 解析 PNG 图片尺寸 /// 解析 PNG 图片尺寸
(int, int)? _parsePngDimensions(Uint8List data) { (int, int)? _parsePngDimensions(Uint8List data) {
if (data.length < 24 || if (data.length < 24 ||
data[0] != 0x89 || data[1] != 0x50 || data[0] != 0x89 ||
data[2] != 0x4E || data[3] != 0x47 || data[1] != 0x50 ||
data[4] != 0x0D || data[5] != 0x0A || data[2] != 0x4E ||
data[6] != 0x1A || data[7] != 0x0A) { data[3] != 0x47 ||
data[4] != 0x0D ||
data[5] != 0x0A ||
data[6] != 0x1A ||
data[7] != 0x0A) {
return null; // 不是有效的 PNG return null; // 不是有效的 PNG
} }
// IHDR chunk 在第 16-23 字节 // IHDR chunk 在第 16-23 字节
final width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19]; final width =
final height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23]; (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19];
final height =
(data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23];
return (width, height); return (width, height);
} }
/// 解析 GIF 图片尺寸 /// 解析 GIF 图片尺寸
(int, int)? _parseGifDimensions(Uint8List data) { (int, int)? _parseGifDimensions(Uint8List data) {
if (data.length < 10 || if (data.length < 10 ||
data[0] != 0x47 || data[1] != 0x49 || data[0] != 0x47 ||
data[2] != 0x46 || data[3] != 0x38) { data[1] != 0x49 ||
data[2] != 0x46 ||
data[3] != 0x38) {
return null; // 不是有效的 GIF return null; // 不是有效的 GIF
} }
@@ -224,18 +233,24 @@ class PhotoRepository {
return null; // 不是有效的 BMP return null; // 不是有效的 BMP
} }
final width = data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24); final width =
final height = data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24); data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24);
final height =
data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24);
return (width, height.abs()); // BMP 高度可能是负数 return (width, height.abs()); // BMP 高度可能是负数
} }
/// 解析 WebP 图片尺寸 /// 解析 WebP 图片尺寸
(int, int)? _parseWebpDimensions(Uint8List data) { (int, int)? _parseWebpDimensions(Uint8List data) {
if (data.length < 30 || if (data.length < 30 ||
data[0] != 0x52 || data[1] != 0x49 || data[0] != 0x52 ||
data[2] != 0x46 || data[3] != 0x46 || data[1] != 0x49 ||
data[8] != 0x57 || data[9] != 0x45 || data[2] != 0x46 ||
data[10] != 0x42 || data[11] != 0x50) { data[3] != 0x46 ||
data[8] != 0x57 ||
data[9] != 0x45 ||
data[10] != 0x42 ||
data[11] != 0x50) {
return null; // 不是有效的 WebP return null; // 不是有效的 WebP
} }
@@ -254,7 +269,8 @@ class PhotoRepository {
if (data.length < 29) return null; if (data.length < 29) return null;
if (data[21] != 0x2F) return null; if (data[21] != 0x2F) return null;
final width = 1 + ((data[22] | (data[23] << 8)) & 0x3FFF); final width = 1 + ((data[22] | (data[23] << 8)) & 0x3FFF);
final height = 1 + (((data[23] >> 6) | (data[24] << 2) | (data[25] << 10)) & 0x3FFF); final height =
1 + (((data[23] >> 6) | (data[24] << 2) | (data[25] << 10)) & 0x3FFF);
return (width, height); return (width, height);
} else if (type == 'VP8X') { } else if (type == 'VP8X') {
// Extended WebP // Extended WebP

View File

@@ -185,11 +185,7 @@ Future<Response> _getPhotoPreviewHandler(Request req) async {
final w = int.tryParse(req.url.queryParameters['w'] ?? ''); final w = int.tryParse(req.url.queryParameters['w'] ?? '');
final h = int.tryParse(req.url.queryParameters['h'] ?? ''); final h = int.tryParse(req.url.queryParameters['h'] ?? '');
final file = await vips.generatePreview( final file = await vips.generatePreview(photo.filePath, w: w, h: h);
photo.filePath,
w: w,
h: h,
);
if (!await file.exists()) { if (!await file.exists()) {
return Response.notFound('File not found'); return Response.notFound('File not found');

View File

@@ -15,11 +15,7 @@ class Vips {
/// [h] 预览图高度(可选) /// [h] 预览图高度(可选)
/// ///
/// 如果同时指定 [w] 和 [h],图片将按比例缩放以适应指定尺寸 /// 如果同时指定 [w] 和 [h],图片将按比例缩放以适应指定尺寸
Future<File> generatePreview( Future<File> generatePreview(String imgPath, {int? w, int? h}) async {
String imgPath, {
int? w,
int? h,
}) async {
// 生成缓存文件名:包含原图 hash 和尺寸信息 // 生成缓存文件名:包含原图 hash 和尺寸信息
final baseName = basename(imgPath).hashCode.toString(); final baseName = basename(imgPath).hashCode.toString();
final sizeSuffix = _buildSizeSuffix(w, h); final sizeSuffix = _buildSizeSuffix(w, h);

View File

@@ -43,7 +43,9 @@ void main() {
}); });
test('GET /api/v1/echo/<message> with special characters', () async { test('GET /api/v1/echo/<message> with special characters', () async {
final response = await http.get(Uri.parse('$host/api/v1/echo/test%20message')); final response = await http.get(
Uri.parse('$host/api/v1/echo/test%20message'),
);
expect(response.statusCode, 200); expect(response.statusCode, 200);
expect(response.body, contains('test')); expect(response.body, contains('test'));
}); });
@@ -63,7 +65,9 @@ void main() {
}); });
test('GET /api/v1/album/<id> with invalid id returns 400', () async { test('GET /api/v1/album/<id> with invalid id returns 400', () async {
final response = await http.get(Uri.parse('$host/api/v1/album/invalid_id')); final response = await http.get(
Uri.parse('$host/api/v1/album/invalid_id'),
);
expect(response.statusCode, 400); expect(response.statusCode, 400);
expect(response.body, contains('Invalid album id')); expect(response.body, contains('Invalid album id'));
}); });
@@ -75,7 +79,9 @@ void main() {
}); });
test('GET /api/v1/album/<id>/photo with invalid id returns 400', () async { test('GET /api/v1/album/<id>/photo with invalid id returns 400', () async {
final response = await http.get(Uri.parse('$host/api/v1/album/invalid_id/photo')); final response = await http.get(
Uri.parse('$host/api/v1/album/invalid_id/photo'),
);
expect(response.statusCode, 400); expect(response.statusCode, 400);
expect(response.body, contains('Invalid album id')); expect(response.body, contains('Invalid album id'));
}); });
@@ -94,7 +100,9 @@ void main() {
final album = albums.first as Map<String, dynamic>; final album = albums.first as Map<String, dynamic>;
final albumId = album['id']; final albumId = album['id'];
final response = await http.get(Uri.parse('$host/api/v1/album/$albumId/photo')); final response = await http.get(
Uri.parse('$host/api/v1/album/$albumId/photo'),
);
expect(response.statusCode, 200); expect(response.statusCode, 200);
expect(response.headers['content-type'], contains('application/json')); expect(response.headers['content-type'], contains('application/json'));
final photos = jsonDecode(response.body) as List; final photos = jsonDecode(response.body) as List;
@@ -122,12 +130,17 @@ void main() {
expect(response.body, contains('Invalid photo id')); expect(response.body, contains('Invalid photo id'));
}); });
test('GET /api/v1/photo/<id>/file with non-existent id returns 404', () async { test(
// Use a hash that won't match any file 'GET /api/v1/photo/<id>/file with non-existent id returns 404',
final response = await http.get(Uri.parse('$host/api/v1/photo/12345/file')); () async {
expect(response.statusCode, 404); // Use a hash that won't match any file
expect(response.body, contains('Photo not found')); final response = await http.get(
}); Uri.parse('$host/api/v1/photo/12345/file'),
);
expect(response.statusCode, 404);
expect(response.body, contains('Photo not found'));
},
);
test('GET /api/v1/photo/<id>/file with valid id returns file', () async { test('GET /api/v1/photo/<id>/file with valid id returns file', () async {
// First get a valid photo id from an album // First get a valid photo id from an album