snap_wish/lib/core/utils/path_utils.dart
2025-09-17 11:42:38 +08:00

322 lines
9.9 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}