核心工具类、错误处理和日志系统

This commit is contained in:
ddshi 2025-09-17 10:08:18 +08:00
parent 9cc9288d6e
commit 8005e21110
5 changed files with 340 additions and 16 deletions

View File

@ -10,7 +10,8 @@
"Bash(flutter test:*)",
"Bash(flutter build:*)",
"Bash(flutter clean:*)",
"Bash(dart analyze:*)"
"Bash(dart analyze:*)",
"Bash(dart test:*)"
],
"deny": [],
"ask": []

View File

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;
import '../errors/app_error.dart';
import 'logger.dart';
@ -12,7 +13,7 @@ class FileUtils {
///
static Future<Directory> getApplicationDocumentsDirectory() async {
try {
final directory = await getApplicationDocumentsDirectory();
final directory = await path_provider.getApplicationDocumentsDirectory();
Logger.debug('获取应用文档目录: ${directory.path}');
return directory;
} catch (error, stackTrace) {
@ -28,7 +29,7 @@ class FileUtils {
///
static Future<Directory> getTemporaryDirectory() async {
try {
final directory = await getTemporaryDirectory();
final directory = await path_provider.getTemporaryDirectory();
Logger.debug('获取临时目录: ${directory.path}');
return directory;
} catch (error, stackTrace) {
@ -44,7 +45,7 @@ class FileUtils {
///
static Future<Directory> getApplicationSupportDirectory() async {
try {
final directory = await getApplicationSupportDirectory();
final directory = await path_provider.getApplicationSupportDirectory();
Logger.debug('获取应用支持目录: ${directory.path}');
return directory;
} catch (error, stackTrace) {

View File

@ -0,0 +1,322 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:uuid/uuid.dart';
import '../errors/app_error.dart';
import 'logger.dart';
///
/// UUID生成
class PathUtils {
//
PathUtils._();
static const Uuid _uuid = Uuid();
/// IDUUID v4
/// UUID格式字符串
static String generateUniqueId() {
return _uuid.v4();
}
/// ID
/// UUID前8位使
static String generateShortId() {
return _uuid.v4().substring(0, 8);
}
/// ID
/// _UUID
static String generateTimeBasedId() {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final shortUuid = generateShortId();
return '${timestamp}_$shortUuid';
}
///
/// [extension] .jpg
/// [prefix] "IMG_"
static String generateImageFileName({
required String extension,
String? prefix,
}) {
final baseName = generateTimeBasedId();
final fileName = prefix != null ? '${prefix}$baseName' : baseName;
return '$fileName$extension';
}
///
/// _thumb
static String generateThumbnailFileName(String originalFileName) {
final nameWithoutExt = path.basenameWithoutExtension(originalFileName);
final extension = path.extension(originalFileName);
return '${nameWithoutExt}_thumb$extension';
}
///
/// /yyyy/MM/dd/
/// [basePath]
/// [date]
static String buildDateBasedPath({
required String basePath,
DateTime? date,
}) {
final targetDate = date ?? DateTime.now();
final year = targetDate.year.toString();
final month = targetDate.month.toString().padLeft(2, '0');
final day = targetDate.day.toString().padLeft(2, '0');
return path.join(basePath, year, month, day);
}
///
///
static Future<String> buildImageStoragePath({
required String fileName,
DateTime? date,
}) async {
try {
final supportDir = await path_provider.getApplicationSupportDirectory();
final imagesBasePath = path.join(supportDir.path, 'images');
final dateBasedPath = buildDateBasedPath(
basePath: imagesBasePath,
date: date,
);
return path.join(dateBasedPath, fileName);
} catch (error, stackTrace) {
Logger.error('构建图片存储路径失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '构建图片存储路径失败: ${error.toString()}',
code: 'PATH_ERROR',
stackTrace: stackTrace,
);
}
}
///
///
static Future<String> buildThumbnailStoragePath({
required String fileName,
DateTime? date,
}) async {
try {
final supportDir = await path_provider.getApplicationSupportDirectory();
final thumbnailsBasePath = path.join(supportDir.path, 'thumbnails');
final dateBasedPath = buildDateBasedPath(
basePath: thumbnailsBasePath,
date: date,
);
return path.join(dateBasedPath, fileName);
} catch (error, stackTrace) {
Logger.error('构建缩略图存储路径失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '构建缩略图存储路径失败: ${error.toString()}',
code: 'PATH_ERROR',
stackTrace: stackTrace,
);
}
}
///
///
static Future<String> getRelativePath(String fullPath) async {
try {
final supportDir = await path_provider.getApplicationSupportDirectory();
return path.relative(fullPath, from: supportDir.path);
} catch (error, stackTrace) {
Logger.error('获取相对路径失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '获取相对路径失败: ${error.toString()}',
code: 'PATH_ERROR',
stackTrace: stackTrace,
);
}
}
///
static Future<String> getFullPath(String relativePath) async {
try {
final supportDir = await path_provider.getApplicationSupportDirectory();
return path.normalize(path.join(supportDir.path, relativePath));
} catch (error, stackTrace) {
Logger.error('获取完整路径失败', error: error, stackTrace: stackTrace);
throw StorageError(
message: '获取完整路径失败: ${error.toString()}',
code: 'PATH_ERROR',
stackTrace: stackTrace,
);
}
}
///
///
static bool isPathSafe(String pathToCheck) {
try {
//
if (pathToCheck.contains('..') || pathToCheck.contains('~')) {
return false;
}
//
if (path.isAbsolute(pathToCheck)) {
return false;
}
//
final normalizedPath = path.normalize(pathToCheck);
return normalizedPath == pathToCheck;
} catch (error) {
Logger.error('路径安全检查失败', error: error);
return false;
}
}
///
static String getParentDirectory(String filePath) {
return path.dirname(filePath);
}
///
static bool isValidFileExtension(String fileName, List<String> validExtensions) {
final extension = path.extension(fileName).toLowerCase();
return validExtensions.map((ext) => ext.toLowerCase()).contains(extension);
}
///
///
static String generateBackupFileName(String originalFileName) {
final nameWithoutExt = path.basenameWithoutExtension(originalFileName);
final extension = path.extension(originalFileName);
final timestamp = DateTime.now().millisecondsSinceEpoch;
return '${nameWithoutExt}_backup_$timestamp$extension';
}
///
/// temp_UUID.
static String generateTempFileName(String extension) {
final uuid = generateShortId();
return 'temp_$uuid$extension';
}
///
/// /yyyy/MM/dd/
static DateTime? parseDateFromPath(String dateBasedPath) {
try {
//
final normalizedPath = path.normalize(dateBasedPath);
final pathParts = normalizedPath.split(path.separator);
//
int yearIndex = -1, monthIndex = -1, dayIndex = -1;
for (int i = 0; i < pathParts.length; i++) {
final part = pathParts[i];
if (part.length == 4 && int.tryParse(part) != null && int.parse(part) > 1900 && int.parse(part) < 3000) {
yearIndex = i;
} else if (part.length == 2 && int.tryParse(part) != null && int.parse(part) >= 1 && int.parse(part) <= 12) {
if (monthIndex == -1) {
monthIndex = i;
} else if (dayIndex == -1) {
dayIndex = i;
}
}
}
if (yearIndex != -1 && monthIndex != -1 && dayIndex != -1) {
final year = int.parse(pathParts[yearIndex]);
final month = int.parse(pathParts[monthIndex]);
final day = int.parse(pathParts[dayIndex]);
return DateTime(year, month, day);
}
return null;
} catch (error) {
Logger.error('解析日期路径失败', error: error);
return null;
}
}
///
static int getPathDepth(String pathToCheck) {
return path.split(path.normalize(pathToCheck)).length;
}
///
static bool hasSameParentDirectory(String path1, String path2) {
final parent1 = path.dirname(path.normalize(path1));
final parent2 = path.dirname(path.normalize(path2));
return parent1 == parent2;
}
///
///
static String sanitizeFileName(String fileName) {
//
final sanitized = fileName
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
.replaceAll(RegExp(r'\s+'), '_')
.replaceAll(RegExp(r'_{2,}'), '_')
.trim();
//
if (sanitized.isEmpty) {
return 'unnamed_file';
}
return sanitized;
}
}
///
///
class PathBuilder {
final String _basePath;
final List<String> _segments;
PathBuilder(this._basePath) : _segments = [];
///
PathBuilder add(String segment) {
_segments.add(segment);
return this;
}
///
PathBuilder addDate(DateTime date) {
final year = date.year.toString();
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
_segments.addAll([year, month, day]);
return this;
}
/// UUID段
PathBuilder addUuid() {
_segments.add(PathUtils.generateShortId());
return this;
}
///
String build() {
if (_segments.isEmpty) {
return _basePath;
}
return path.join(_basePath, _segments.join(path.separator));
}
///
Future<Directory> createDirectory() async {
final fullPath = build();
final directory = Directory(fullPath);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
return directory;
}
}

View File

@ -2,6 +2,7 @@ import 'package:hive/hive.dart';
import '../../models/hive_inspiration_image.dart';
import '../../models/hive_image_folder.dart';
import '../../models/hive_image_tag.dart';
import '../../../core/utils/logger.dart';
/// -
///
@ -120,8 +121,7 @@ class DatabaseMigration {
await foldersBox.put(folder.id, folder);
} else {
// TODO: 使print
// print('默认文件夹已存在: ${folderConfig['name']}');
Logger.debug('默认文件夹已存在: ${folderConfig['name']}');
}
}
}
@ -129,7 +129,7 @@ class DatabaseMigration {
/// -
/// [tagsBox]
static Future<void> createDefaultTags(Box<HiveImageTag> tagsBox) async {
print('创建默认标签...');
Logger.info('创建默认标签...');
//
final defaultTags = [
@ -219,8 +219,7 @@ class DatabaseMigration {
Box<HiveImageFolder> foldersBox,
Box<HiveImageTag> tagsBox,
) async {
// TODO: 使print
// print('开始数据库完整性验证...');
Logger.info('开始数据库完整性验证...');
bool isValid = true;

View File

@ -3,6 +3,7 @@ import 'package:path_provider/path_provider.dart' as path_provider;
import '../../../data/models/hive_inspiration_image.dart';
import '../../../data/models/hive_image_folder.dart';
import '../../../data/models/hive_image_tag.dart';
import '../../../core/utils/logger.dart';
import 'database_migration.dart';
/// Hive数据库管理类 -
@ -65,7 +66,7 @@ class HiveDatabase {
if (currentDbVersion < _currentVersion) {
try {
print('检测到数据库版本更新: $currentDbVersion -> $_currentVersion');
Logger.info('检测到数据库版本更新: $currentDbVersion -> $_currentVersion');
// 使
await DatabaseMigration.migrateFromVersion(
@ -76,15 +77,15 @@ class HiveDatabase {
settingsBox,
);
print('数据库迁移完成');
Logger.info('数据库迁移完成');
} catch (e) {
print('数据库迁移失败: $e');
Logger.error('数据库迁移失败', error: e);
//
await _handleMigrationFailure(currentDbVersion, e);
}
} else if (currentDbVersion > _currentVersion) {
//
print('警告:数据库版本高于应用版本 ($currentDbVersion > $_currentVersion)');
Logger.warning('数据库版本高于应用版本 ($currentDbVersion > $_currentVersion)');
await settingsBox.put(_versionKey, _currentVersion);
}
}
@ -93,7 +94,7 @@ class HiveDatabase {
/// [fromVersion]
/// [error]
static Future<void> _handleMigrationFailure(int fromVersion, dynamic error) async {
print('处理迁移失败,尝试恢复到安全状态...');
Logger.error('处理迁移失败,尝试恢复到安全状态...', error: error);
try {
// 0
@ -107,7 +108,7 @@ class HiveDatabase {
await settingsBox.put('last_migration_time', DateTime.now().toIso8601String());
} catch (recoveryError) {
print('迁移恢复失败: $recoveryError');
Logger.fatal('迁移恢复失败', error: recoveryError);
//
throw DatabaseMigrationException(
fromVersion: fromVersion,
@ -132,7 +133,7 @@ class HiveDatabase {
/// -
///
static Future<void> _createEmergencyData() async {
print('创建紧急恢复数据...');
Logger.warning('创建紧急恢复数据...');
try {
final foldersBox = Hive.box<HiveImageFolder>(_foldersBoxName);