Skip to content

Commit 8c5d603

Browse files
committed
fix: 修复 Alpine 环境 MIME 类型检测和非 HTTPS 剪贴板功能
后端修复: - 添加常见 MIME 类型的 fallback 映射表(video/mp4, audio/mp3, image/jpeg等) - 解决 Alpine Linux 缺少 /etc/mime.types 导致文件扩展名检测失败问题 - 增强日志输出,便于调试文件类型检测流程 前端修复: - 修复非安全上下文(http://192.168.x.x)下剪贴板功能失效问题 - 改进 execCommand 回退方案: 移除 readonly 属性,调整 textarea 定位 - 修复文本选中问题,确保实际复制到剪贴板 - 添加详细调试日志 - 保存并恢复原始文本选区 部署优化: - Dockerfile 添加 mailcap 包安装 MIME 数据库 - 统一设置 UTF-8 环境变量(LANG, LC_ALL) - supervisord 为所有程序配置 UTF-8 环境 根因分析: - 后端: Alpine 缺少 mime.types 导致 mime.ExtensionsByType 返回空数组 - 前端: navigator.clipboard 仅在安全上下文可用,execCommand 方案存在 textarea 选中问题
1 parent e508ff7 commit 8c5d603

File tree

4 files changed

+103
-15
lines changed

4 files changed

+103
-15
lines changed

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o telegram-bot ./plugins/telegram-bot
3232

3333
FROM alpine:latest
3434

35-
RUN apk --no-cache add ca-certificates tzdata nginx supervisor
35+
RUN apk --no-cache add ca-certificates tzdata nginx supervisor mailcap
3636

3737
WORKDIR /app
3838

@@ -75,6 +75,8 @@ EOF
7575
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
7676

7777
ENV TZ=Asia/Shanghai
78+
ENV LANG=C.UTF-8
79+
ENV LC_ALL=C.UTF-8
7880

7981
EXPOSE 80
8082

backend/service/download_service.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,12 +507,40 @@ func (s *DownloadService) detectFilenameByContentType(ctx context.Context, urlSt
507507
return ""
508508
}
509509

510-
if len(exts) == 0 {
511-
log.Printf("[Download] ⚠️ Content-Type '%s' 没有对应的扩展名", contentType)
512-
return ""
513-
}
510+
var ext string
511+
if len(exts) > 0 {
512+
ext = exts[0] // 使用第一个扩展名
513+
log.Printf("[Download] 📋 从系统 MIME 数据库获取扩展名: %s", ext)
514+
} else {
515+
// Fallback: 常见 MIME 类型的硬编码映射(Alpine 环境可能缺少 /etc/mime.types)
516+
commonTypes := map[string]string{
517+
"video/mp4": ".mp4",
518+
"video/mpeg": ".mpeg",
519+
"video/webm": ".webm",
520+
"video/x-matroska": ".mkv",
521+
"video/quicktime": ".mov",
522+
"audio/mpeg": ".mp3",
523+
"audio/wav": ".wav",
524+
"audio/ogg": ".ogg",
525+
"image/jpeg": ".jpg",
526+
"image/png": ".png",
527+
"image/gif": ".gif",
528+
"image/webp": ".webp",
529+
"image/svg+xml": ".svg",
530+
"application/pdf": ".pdf",
531+
"application/zip": ".zip",
532+
"text/plain": ".txt",
533+
"text/html": ".html",
534+
}
514535

515-
ext := exts[0] // 使用第一个扩展名
536+
if fallbackExt, ok := commonTypes[contentType]; ok {
537+
ext = fallbackExt
538+
log.Printf("[Download] 📋 使用 fallback 扩展名映射: %s -> %s", contentType, ext)
539+
} else {
540+
log.Printf("[Download] ⚠️ Content-Type '%s' 没有对应的扩展名(系统和 fallback 都未找到)", contentType)
541+
return ""
542+
}
543+
}
516544
timestamp := time.Now().Unix()
517545
filename := fmt.Sprintf("file_%d%s", timestamp, ext)
518546
log.Printf("[Download] ✅ 检测成功: Content-Type=%s, 扩展名=%s, 文件名=%s", contentType, ext, filename)

frontend/src/lib/utils.ts

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,90 @@ export function cn(...inputs: ClassValue[]) {
1212
* @returns Promise<boolean> 是否成功
1313
*/
1414
export async function copyToClipboard(text: string): Promise<boolean> {
15+
console.log('[Copy] 开始复制文本:', text.substring(0, 50) + '...')
16+
1517
try {
1618
// 方法1: 优先使用现代 Clipboard API
1719
if (navigator.clipboard && window.isSecureContext) {
20+
console.log('[Copy] 使用 Clipboard API')
1821
await navigator.clipboard.writeText(text)
22+
console.log('[Copy] ✅ Clipboard API 复制成功')
1923
return true
2024
}
2125

26+
console.log('[Copy] 回退到 execCommand 方法 (isSecureContext:', window.isSecureContext, ')')
27+
2228
// 方法2: 回退到 execCommand (适用于非 HTTPS 环境)
2329
const textArea = document.createElement('textarea')
2430
textArea.value = text
25-
textArea.style.position = 'fixed'
26-
textArea.style.left = '-999999px'
27-
textArea.style.top = '-999999px'
31+
32+
// 关键修复:textarea 必须可见但在视口外,并且不能是 readonly
33+
textArea.style.position = 'absolute'
34+
textArea.style.left = '-9999px'
35+
textArea.style.top = '0'
36+
textArea.style.width = '1px'
37+
textArea.style.height = '1px'
38+
textArea.style.padding = '0'
39+
textArea.style.border = 'none'
40+
textArea.style.outline = 'none'
41+
textArea.style.boxShadow = 'none'
42+
textArea.style.background = 'transparent'
43+
// 不设置 readonly,某些浏览器需要可编辑才能复制
44+
2845
document.body.appendChild(textArea)
46+
47+
// 先移除当前选区
48+
const selection = window.getSelection()
49+
const originalSelection = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null
50+
51+
// 聚焦并选中 textarea
2952
textArea.focus()
3053
textArea.select()
3154

55+
// 使用 setSelectionRange 确保选中全部内容
56+
try {
57+
textArea.setSelectionRange(0, textArea.value.length)
58+
} catch (err) {
59+
console.warn('[Copy] setSelectionRange 失败:', err)
60+
}
61+
62+
// 验证是否真的选中了
63+
const selectedText = window.getSelection()?.toString()
64+
console.log('[Copy] 选中的文本:', selectedText?.substring(0, 50) + '...')
65+
3266
try {
3367
const successful = document.execCommand('copy')
34-
textArea.remove()
35-
return successful
68+
console.log('[Copy] execCommand 执行结果:', successful)
69+
70+
// 恢复原来的选区
71+
if (originalSelection) {
72+
selection?.removeAllRanges()
73+
selection?.addRange(originalSelection)
74+
}
75+
76+
document.body.removeChild(textArea)
77+
78+
if (successful) {
79+
console.log('[Copy] ✅ execCommand 复制成功')
80+
return true
81+
} else {
82+
console.error('[Copy] ❌ execCommand 返回 false')
83+
return false
84+
}
3685
} catch (err) {
37-
console.error('execCommand copy failed:', err)
38-
textArea.remove()
86+
console.error('[Copy] ❌ execCommand 抛出异常:', err)
87+
88+
// 恢复原来的选区
89+
if (originalSelection) {
90+
selection?.removeAllRanges()
91+
selection?.addRange(originalSelection)
92+
}
93+
94+
document.body.removeChild(textArea)
3995
return false
4096
}
4197
} catch (err) {
42-
console.error('Failed to copy text:', err)
98+
console.error('[Copy] ❌ 复制失败:', err)
4399
return false
44100
}
45101
}

supervisord.conf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ user=root
77
[program:mynest-core]
88
command=/app/mynest
99
directory=/app
10+
environment=LANG="C.UTF-8",LC_ALL="C.UTF-8"
1011
autostart=true
1112
autorestart=true
1213
redirect_stderr=true
@@ -18,7 +19,7 @@ stdout_logfile_backups=3
1819
[program:telegram-bot]
1920
command=/app/telegram-bot
2021
directory=/app
21-
environment=PLUGIN_MODE=grpc,PLUGIN_PORT=50051
22+
environment=PLUGIN_MODE=grpc,PLUGIN_PORT=50051,LANG="C.UTF-8",LC_ALL="C.UTF-8"
2223
autostart=true
2324
autorestart=true
2425
redirect_stderr=true
@@ -29,6 +30,7 @@ stdout_logfile_backups=3
2930

3031
[program:nginx]
3132
command=/usr/sbin/nginx -g "daemon off;"
33+
environment=LANG="C.UTF-8",LC_ALL="C.UTF-8"
3234
autostart=true
3335
autorestart=true
3436
redirect_stderr=true

0 commit comments

Comments
 (0)