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(); /// 生成唯一ID(UUID 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 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 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 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 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 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 _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 createDirectory() async { final fullPath = build(); final directory = Directory(fullPath); if (!await directory.exists()) { await directory.create(recursive: true); } return directory; } }