Private
Public Access
1
0

新增:Markdown编辑器/数据库优化/安全修复

- Markdown 编辑器:实时预览、PDF 导出、独立查看器
- 数据库优化:动态连接池、查询缓存、Redis Pipeline
- 窗口置顶功能
- 文件系统增强:右键菜单、编辑器集成、收藏夹重构
- 安全修复:XSS 防护、路径穿越、HTML 注入
- 代码质量:正则预编译、缓存锁优化、死代码清理
This commit is contained in:
2026-03-31 09:18:06 +08:00
parent 5f94ccf13b
commit e5dbe89a6f
59 changed files with 5289 additions and 1316 deletions

View File

@@ -5,8 +5,51 @@
* @description 提供文件相关的通用工具函数,避免代码重复
*/
import { normalizePathSeparators } from './pathHelpers.js'
import { FILE_ICON_MAP, FILE_ICONS, BYTE_UNITS, FILE_SIZE_FORMAT, FILE_EXTENSIONS } from './constants'
import { FILE_ICON_MAP, FILE_ICONS, BYTE_UNITS, FILE_SIZE_FORMAT } from './constants'
/**
* 路径分隔符正则(匹配 Windows 和 Unix 风格)
* @type {RegExp}
*/
export const PATH_SEPARATOR_REGEX = /[/\\]/
/**
* 规范化路径分隔符(统一为正斜杠)
* @param {string} path - 文件路径
* @returns {string} 规范化后的路径
*/
export const normalizePathSeparators = (path) => {
if (!path) return ''
return path.replace(/\\/g, '/')
}
/**
* HTML 转义,防止 XSS
* @param {string} str - 原始字符串
* @returns {string} 转义后的字符串
*/
export const escapeHtml = (str) => {
if (str == null) return ''
return String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
/**
* 获取文件扩展名(路径安全)
* @param {string} path - 文件路径
* @returns {string} 扩展名(小写,不含点号)
*/
export const getExt = (path) => {
if (!path) return ''
const dot = path.lastIndexOf('.')
const slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
if (dot === -1 || dot < slash) return ''
return path.slice(dot + 1).toLowerCase()
}
/**
* 格式化文件大小
@@ -46,34 +89,29 @@ export function formatBytes(bytes) {
*/
export function getFileName(path) {
if (!path) return ''
// 后端已统一返回 / 路径,直接分割
const parts = path.split('/')
const parts = path.split(PATH_SEPARATOR_REGEX)
return parts[parts.length - 1] || path
}
/**
* 从文件路径中提取文件扩展名
* 分割路径为多个部分
* @param {string} path - 文件路径
* @returns {string} 扩展名(小写,不含点号)
*
* @example
* getFileExtension('/path/to/file.txt') // "txt"
* getFileExtension('/path/to/file.TXT') // "txt"
* getFileExtension('/path/to/file') // ""
* @returns {string[]} 路径数组
*/
export function getFileExtension(path) {
if (!path) return ''
export const splitPath = (path) => {
if (!path) return []
return path.split(PATH_SEPARATOR_REGEX)
}
/**
* 获取文件名(不含扩展名)
* @param {string} path - 文件路径
* @returns {string} 文件名(不含扩展名)
*/
export const getFileNameWithoutExt = (path) => {
const fileName = getFileName(path)
const lastDotIndex = fileName.lastIndexOf('.')
if (lastDotIndex === -1 || lastDotIndex === fileName.length - 1) {
return ''
}
return fileName.substring(lastDotIndex + 1).toLowerCase()
const lastDot = fileName.lastIndexOf('.')
return lastDot > 0 ? fileName.substring(0, lastDot) : fileName
}
/**
@@ -97,51 +135,12 @@ export function getFileIcon(fileInfo) {
}
// 获取文件扩展名
const ext = getFileExtension(fileInfo.name)
const ext = getExt(fileInfo.name)
// 从映射表中查找图标
return FILE_ICON_MAP.get(ext) || FILE_ICONS.FILE
}
/**
* 判断文件是否为图片
* @param {string} path - 文件路径
* @returns {boolean} 是否为图片文件
*/
export function isImageFile(path) {
const ext = getFileExtension(path)
return FILE_EXTENSIONS.IMAGE.includes(ext)
}
/**
* 判断文件是否为视频
* @param {string} path - 文件路径
* @returns {boolean} 是否为视频文件
*/
export function isVideoFile(path) {
const ext = getFileExtension(path)
return [...FILE_EXTENSIONS.VIDEO_BROWSER, ...FILE_EXTENSIONS.VIDEO_EXTERNAL].includes(ext)
}
/**
* 判断文件是否为音频
* @param {string} path - 文件路径
* @returns {boolean} 是否为音频文件
*/
export function isAudioFile(path) {
const ext = getFileExtension(path)
return FILE_EXTENSIONS.AUDIO.includes(ext)
}
/**
* 判断文件是否为PDF
* @param {string} path - 文件路径
* @returns {boolean} 是否为PDF文件
*/
export function isPdfFile(path) {
return getFileExtension(path) === 'pdf'
}
/**
* 规范化文件路径将反斜杠转换为正斜杠并进行URL编码
* @param {string} path - 原始路径
@@ -189,7 +188,7 @@ export function normalizeFilePath(path, encode = false) {
* getFileTypeName('unknown.xyz') // "XYZ文件"
*/
export function getFileTypeName(path) {
const ext = getFileExtension(path)
const ext = getExt(path)
const extUpper = ext.toUpperCase()
// 图片
@@ -226,23 +225,6 @@ export function getFileTypeName(path) {
return ext ? `${extUpper}文件` : '文件'
}
/**
* 判断文件是否为二进制文件
* @param {string} path - 文件路径
* @returns {boolean} 是否为二进制文件
*/
export function isBinaryFile(path) {
const ext = getFileExtension(path)
const binaryExtensions = [
'exe', 'dll', 'so', 'dylib', // 可执行文件
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', // 压缩文件
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', // Office文档
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'mp3', 'mp4', // 媒体文件
'eot', 'ttf', 'otf', 'woff', 'woff2', // 字体文件
]
return binaryExtensions.includes(ext)
}
/**
* 检查路径是否为绝对路径
* @param {string} path - 文件路径
@@ -278,6 +260,15 @@ export function joinPaths(...parts) {
return parts.join('/').replace(/\/+/g, '/')
}
/**
* 获取路径使用的分隔符Windows 反斜杠或 Unix 正斜杠)
* @param {string} path - 文件路径
* @returns {string} 分隔符 '\\' 或 '/'
*/
export function getPathSeparator(path) {
return path.includes('\\') ? '\\' : '/'
}
/**
* 获取父目录路径
* @param {string} path - 文件路径
@@ -290,14 +281,24 @@ export function joinPaths(...parts) {
export function getParentPath(path) {
if (!path) return ''
const normalizedPath = normalizeFilePath(path)
const normalizedPath = normalizePathSeparators(path)
const lastSlashIndex = normalizedPath.lastIndexOf('/')
if (lastSlashIndex <= 0) {
return '/' // 根目录
if (/^[A-Za-z]:$/.test(normalizedPath)) {
return normalizedPath + '/'
}
return normalizedPath || '/'
}
return normalizedPath.substring(0, lastSlashIndex)
const parentPath = normalizedPath.substring(0, lastSlashIndex)
// 盘符根目录下文件E:/file.txt → E:/
if (/^[A-Za-z]:$/.test(parentPath)) {
return parentPath + '/'
}
return parentPath || '/'
}
/**