新增: SFTP直连+网站预览+OSS区域嗅探+热键+BGM播放
This commit is contained in:
@@ -45,9 +45,8 @@ var (
|
||||
es6DynamicImport = regexp.MustCompile(`import\s*\(\s*["']([^"']+)["']\s*\)`)
|
||||
es6BareImport = regexp.MustCompile(`(?m)^\s*import\s+["']([^"']+)["']`)
|
||||
|
||||
// HTML 预览路径修复
|
||||
locationPathRegex = regexp.MustCompile(`\blocation\.pathname\b`)
|
||||
winDriveRegex = regexp.MustCompile(`^[A-Za-z]:`)
|
||||
// Windows 盘符检测
|
||||
winDriveRegex = regexp.MustCompile(`^[A-Za-z]:`)
|
||||
)
|
||||
|
||||
// HTML 属性正则缓存(避免 replaceHtmlTagAttribute 中重复编译)
|
||||
@@ -78,6 +77,8 @@ func validateFilePath(rawPath string, logPrefix string) (string, error) {
|
||||
clean = strings.TrimPrefix(clean, "/localfs/")
|
||||
clean = strings.TrimPrefix(clean, "localfs/")
|
||||
}
|
||||
// 清理残留的前导斜杠(避免 /u-res/... 类路径在 Windows 上异常)
|
||||
clean = strings.TrimLeft(clean, "/")
|
||||
|
||||
// 平台适配:Windows 用反斜杠,Linux/macOS 保持正斜杠
|
||||
filePath := filepath.FromSlash(clean)
|
||||
@@ -304,7 +305,12 @@ func handleLocalFileRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// 设置响应头
|
||||
contentType := getContentType(ext)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
// 媒体文件禁用缓存(避免 Chromium ERR_CACHE_OPERATION_NOT_SUPPORTED)
|
||||
if isMediaExt(ext) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
}
|
||||
// 支持 Range 请求
|
||||
w.Header().Set("Accept-Ranges", "bytes")
|
||||
|
||||
@@ -365,6 +371,16 @@ func isAllowedFileType(ext string) bool {
|
||||
return defaultFileTypeManager.IsAllowed(ext)
|
||||
}
|
||||
|
||||
// isMediaExt 判断是否为音频/视频扩展名
|
||||
func isMediaExt(ext string) bool {
|
||||
switch ext {
|
||||
case ".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a", ".wma",
|
||||
".mp4", ".webm", ".mkv", ".avi", ".mov", ".wmv":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Shutdown 优雅关闭文件服务器
|
||||
func (lfs *LocalFileServer) Shutdown() error {
|
||||
if lfs == nil || lfs.server == nil {
|
||||
@@ -556,11 +572,8 @@ func handleHtmlPreviewRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// 转换资源路径(将相对路径和绝对路径都转换为完整的本地文件服务器 URL)
|
||||
processedContent := transformHtmlResourcePaths(string(content), baseDir)
|
||||
|
||||
// 修复 JS 中基于 location.pathname 的相对路径计算
|
||||
// 预览模式下 location.pathname = "/localfs/html-preview",与实际文件路径不一致
|
||||
// ⚠️ 会替换所有出现位置(含JS字符串内),HTML预览场景下可接受
|
||||
correctPathname := `"/localfs/` + strings.ReplaceAll(baseDir, "\\", "/") + `/`
|
||||
processedContent = locationPathRegex.ReplaceAllString(processedContent, correctPathname)
|
||||
// 注入路径拦截脚本(处理 webpack 等动态加载的绝对路径资源)
|
||||
processedContent = injectPathInterceptor(processedContent, baseDir)
|
||||
|
||||
// 注入链接点击拦截脚本
|
||||
finalContent := injectLinkInterceptor(processedContent)
|
||||
@@ -870,3 +883,38 @@ func injectLinkInterceptor(htmlContent string) string {
|
||||
// 没有 body 标签,在末尾插入
|
||||
return htmlContent + script
|
||||
}
|
||||
|
||||
// injectPathInterceptor 注入路径拦截脚本(处理 webpack 等动态加载的绝对路径资源)
|
||||
// 重写动态创建的 <script src="/..."> 和 <link href="/..."> 为 /localfs/ 前缀路径
|
||||
func injectPathInterceptor(htmlContent string, baseDir string) string {
|
||||
// 直接使用 baseDir(HTML 所在目录)作为 base,与 transformHtmlResourcePaths 的路径解析一致
|
||||
base := toLocalServerUrl(strings.ReplaceAll(baseDir, "\\", "/"))
|
||||
|
||||
script := `<script data-udesk-intercept="true">
|
||||
(function(){
|
||||
var base = "` + base + `/";
|
||||
function rw(v){if(typeof v!=='string')return v;if(v[0]==='/'&&!v.startsWith('/localfs/')&&!v.startsWith('//')&&!v.startsWith('http'))return base+v.substring(1);return v;}
|
||||
var sa=Element.prototype.setAttribute;
|
||||
Element.prototype.setAttribute=function(n,v){if((n==='src'||n==='href'||n==='data'||n==='poster')&&typeof v==='string')v=rw(v);return sa.call(this,n,v);};
|
||||
try{var d=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,'src');Object.defineProperty(HTMLScriptElement.prototype,'src',{set:function(v){d.set.call(this,rw(v))},get:d.get,configurable:true});}catch(e){}
|
||||
try{var d2=Object.getOwnPropertyDescriptor(HTMLLinkElement.prototype,'href');Object.defineProperty(HTMLLinkElement.prototype,'href',{set:function(v){d2.set.call(this,rw(v))},get:d2.get,configurable:true});}catch(e){}
|
||||
})();
|
||||
</script>`
|
||||
|
||||
// 在 <head> 后立即插入(确保在任何其他脚本之前执行)
|
||||
if idx := strings.Index(htmlContent, "<head>"); idx >= 0 {
|
||||
return htmlContent[:idx+6] + script + htmlContent[idx+6:]
|
||||
}
|
||||
if idx := strings.Index(htmlContent, "<HEAD>"); idx >= 0 {
|
||||
return htmlContent[:idx+6] + script + htmlContent[idx+6:]
|
||||
}
|
||||
// 没有 head 标签,在 <!DOCTYPE> 和 <html> 后插入
|
||||
if idx := strings.Index(htmlContent, "<html"); idx >= 0 {
|
||||
end := strings.Index(htmlContent[idx:], ">")
|
||||
if end >= 0 {
|
||||
pos := idx + end + 1
|
||||
return htmlContent[:pos] + script + htmlContent[pos:]
|
||||
}
|
||||
}
|
||||
return script + htmlContent
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user