Skip to content

Commit 27b45b4

Browse files
authored
Merge pull request #50 from JyiDeng/master
修复文件名乱码
2 parents 776a908 + 375f02f commit 27b45b4

File tree

2 files changed

+56
-24
lines changed

2 files changed

+56
-24
lines changed

rimetool/rimetool_gui/new_app.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ def _load_project_version() -> str:
6767
static_dir = template_dir # 将static也指向templates目录
6868

6969
app = Flask(__name__, template_folder=template_dir, static_folder=static_dir)
70-
# 启用 CORS
70+
# 启用 CORS,并暴露Content-Disposition头供前端JavaScript访问
7171
# CORS(app, origins="http://localhost:5500") # 允许来自 http://localhost:5500 的请求
72-
CORS(app, origins="*")
72+
CORS(app, origins="*", expose_headers=["Content-Disposition"])
7373

7474
# 配置详细的日志
7575
# 从环境变量获取日志目录,如果没有设置则使用默认位置
@@ -289,17 +289,26 @@ def process_file():
289289
download_name=original_filename,
290290
mimetype='application/octet-stream'
291291
)
292-
# 设置文件下载的响应头
292+
# 设置Content-Disposition,兼容所有浏览器
293+
# 参考: https://www.cnblogs.com/PengZhao-Mr/p/18489371
293294
try:
295+
# 检查文件名是否只包含ASCII字符
294296
original_filename.encode('ascii')
295297
response.headers["Content-Disposition"] = f'attachment; filename="{original_filename}"'
298+
logger.info(f"文件名使用ASCII编码: {original_filename}")
296299
except UnicodeEncodeError:
297-
from urllib.parse import quote as url_quote
298-
encoded_filename = url_quote(original_filename)
300+
# 包含中文,使用RFC 2231标准格式
301+
# filename*=UTF-8''编码后的文件名
302+
encoded_filename = quote(original_filename)
299303
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
304+
logger.info(f"设置UTF-8编码文件名: {encoded_filename}")
305+
300306
response.headers["Content-Type"] = "application/octet-stream"
301307
response.headers["X-Content-Type-Options"] = "nosniff"
308+
# 确保前端JavaScript可以访问Content-Disposition头
309+
response.headers["Access-Control-Expose-Headers"] = "Content-Disposition"
302310
logger.info(f"返回文件: {original_filename}")
311+
logger.info(f"Content-Disposition: {response.headers.get('Content-Disposition')}")
303312
return response
304313
except Exception as e:
305314
logger.error(f"返回文件失败: {str(e)}\n{traceback.format_exc()}")
@@ -340,27 +349,26 @@ def process_file():
340349

341350
logger.info(f"返回ZIP文件: {safe_filename}")
342351

343-
# 手动构建响应,同时提供 filename 和 filename* 以支持各种浏览器
352+
# 手动构建响应
344353
response = make_response(memory_file.getvalue())
345354
response.headers['Content-Type'] = 'application/zip'
346355

347-
# 构建符合 RFC 6266 的 Content-Disposition 头
348-
# 同时提供 ASCII 回退文件名和 UTF-8 编码文件名
356+
# 设置Content-Disposition,使用双格式兼容所有浏览器
349357
try:
350-
# 尝试编码为 ASCII
358+
# 检查文件名是否只包含ASCII字符
351359
safe_filename.encode('ascii')
352-
# 如果成功,使用简单格式
353360
response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}"'
361+
logger.info(f"文件名使用ASCII编码: {safe_filename}")
354362
except UnicodeEncodeError:
355-
# 包含非 ASCII 字符,使用双格式
356-
# filename 使用 ASCII 安全的回退名称
357-
ascii_filename = f"output_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
358-
# filename* 使用 RFC 2231 格式的 UTF-8 编码
363+
# 包含中文,使用RFC 2231标准格式
364+
# filename*=UTF-8''编码后的文件名
359365
encoded_filename = quote(safe_filename)
360-
response.headers['Content-Disposition'] = (
361-
f'attachment; filename="{ascii_filename}"; '
362-
f"filename*=UTF-8''{encoded_filename}"
363-
)
366+
response.headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{encoded_filename}"
367+
logger.info(f"设置UTF-8编码文件名: {encoded_filename}")
368+
369+
# 确保前端JavaScript可以访问Content-Disposition头
370+
response.headers['Access-Control-Expose-Headers'] = 'Content-Disposition'
371+
logger.info(f"Content-Disposition已设置: {response.headers.get('Content-Disposition')}")
364372

365373
return response
366374
except Exception as e:

rimetool/rimetool_gui/templates/new_index.html

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,15 +333,39 @@ <h3 class="text-xl font-bold mb-4 text-red-600">错误详情</h3>
333333
const contentDisposition = response.headers.get('Content-Disposition');
334334
let filename = '';
335335

336+
// 调试:打印响应头信息
337+
log(`Content-Disposition: ${contentDisposition}`, 'info');
338+
336339
if (contentDisposition) {
337-
// 使用正则表达式从Content-Disposition中提取文件名
338-
const matches = /filename=(.+)/.exec(contentDisposition);
339-
if (matches && matches[1]) {
340-
filename = matches[1];
341-
log(`从响应头获取到文件名: ${filename}`, 'info');
340+
// 优先解析 RFC 2231 格式的 filename* (支持UTF-8编码的中文文件名)
341+
const filenameStarMatch = /filename\*=UTF-8''([^;]+)/i.exec(contentDisposition);
342+
if (filenameStarMatch && filenameStarMatch[1]) {
343+
// URL解码,将 %E8%84%89 这样的编码转换为中文
344+
filename = decodeURIComponent(filenameStarMatch[1].trim());
345+
log(`✓ 成功提取文件名: ${filename}`, 'success');
342346
} else {
343-
log('无法从Content-Disposition中提取文件名', 'warning');
347+
// 标准的 filename= 格式(可能包含在引号中)
348+
const filenameMatch = /filename="?([^";]+)"?/i.exec(contentDisposition);
349+
if (filenameMatch && filenameMatch[1]) {
350+
filename = filenameMatch[1].trim();
351+
// 尝试URL解码
352+
try {
353+
const decoded = decodeURIComponent(filename);
354+
if (decoded !== filename) {
355+
filename = decoded;
356+
log(`✓ 成功提取并解码文件名: ${filename}`, 'success');
357+
} else {
358+
log(`✓ 成功提取文件名: ${filename}`, 'success');
359+
}
360+
} catch (e) {
361+
log(`✓ 成功提取文件名(解码失败,使用原值): ${filename}`, 'success');
362+
}
363+
} else {
364+
log('✗ 无法从Content-Disposition中提取文件名', 'warning');
365+
}
344366
}
367+
} else {
368+
log('✗ Content-Disposition头不存在', 'warning');
345369
}
346370

347371
// 如果没有成功获取文件名,使用时间戳生成文件名

0 commit comments

Comments
 (0)