Skip to content

Commit 4d7c9de

Browse files
author
Lan
committed
fix: Fix abnormal Chinese names in downloaded files in the background #200
1 parent 731b631 commit 4d7c9de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+500
-447
lines changed

apps/admin/views.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,21 @@ async def dashboard(admin: bool = Depends(admin_required)):
6363

6464
@admin_api.delete("/file/delete")
6565
async def file_delete(
66-
data: IDData,
67-
file_service: FileService = Depends(get_file_service),
68-
admin: bool = Depends(admin_required),
66+
data: IDData,
67+
file_service: FileService = Depends(get_file_service),
68+
admin: bool = Depends(admin_required),
6969
):
7070
await file_service.delete_file(data.id)
7171
return APIResponse()
7272

7373

7474
@admin_api.get("/file/list")
7575
async def file_list(
76-
page: int = 1,
77-
size: int = 10,
78-
keyword: str = "",
79-
file_service: FileService = Depends(get_file_service),
80-
admin: bool = Depends(admin_required),
76+
page: int = 1,
77+
size: int = 10,
78+
keyword: str = "",
79+
file_service: FileService = Depends(get_file_service),
80+
admin: bool = Depends(admin_required),
8181
):
8282
files, total = await file_service.list_files(page, size, keyword)
8383
return APIResponse(
@@ -92,17 +92,17 @@ async def file_list(
9292

9393
@admin_api.get("/config/get")
9494
async def get_config(
95-
config_service: ConfigService = Depends(get_config_service),
96-
admin: bool = Depends(admin_required),
95+
config_service: ConfigService = Depends(get_config_service),
96+
admin: bool = Depends(admin_required),
9797
):
9898
return APIResponse(detail=config_service.get_config())
9999

100100

101101
@admin_api.patch("/config/update")
102102
async def update_config(
103-
data: dict,
104-
config_service: ConfigService = Depends(get_config_service),
105-
admin: bool = Depends(admin_required),
103+
data: dict,
104+
config_service: ConfigService = Depends(get_config_service),
105+
admin: bool = Depends(admin_required),
106106
):
107107
data.pop("themesChoices")
108108
await config_service.update_config(data)
@@ -111,38 +111,38 @@ async def update_config(
111111

112112
@admin_api.get("/file/download")
113113
async def file_download(
114-
id: int,
115-
file_service: FileService = Depends(get_file_service),
116-
admin: bool = Depends(admin_required),
114+
id: int,
115+
file_service: FileService = Depends(get_file_service),
116+
admin: bool = Depends(admin_required),
117117
):
118118
file_content = await file_service.download_file(id)
119119
return file_content
120120

121121

122122
@admin_api.get("/local/lists")
123123
async def get_local_lists(
124-
local_file_service: LocalFileService = Depends(get_local_file_service),
125-
admin: bool = Depends(admin_required),
124+
local_file_service: LocalFileService = Depends(get_local_file_service),
125+
admin: bool = Depends(admin_required),
126126
):
127127
files = await local_file_service.list_files()
128128
return APIResponse(detail=files)
129129

130130

131131
@admin_api.delete("/local/delete")
132132
async def delete_local_file(
133-
item: DeleteItem,
134-
local_file_service: LocalFileService = Depends(get_local_file_service),
135-
admin: bool = Depends(admin_required),
133+
item: DeleteItem,
134+
local_file_service: LocalFileService = Depends(get_local_file_service),
135+
admin: bool = Depends(admin_required),
136136
):
137137
result = await local_file_service.delete_file(item.filename)
138138
return APIResponse(detail=result)
139139

140140

141141
@admin_api.post("/local/share")
142142
async def share_local_file(
143-
item: ShareItem,
144-
file_service: FileService = Depends(get_file_service),
145-
admin: bool = Depends(admin_required),
143+
item: ShareItem,
144+
file_service: FileService = Depends(get_file_service),
145+
admin: bool = Depends(admin_required),
146146
):
147147
share_info = await file_service.share_local_file(item)
148148
return APIResponse(detail=share_info)

core/storage.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,14 @@ async def get_file_response(self, file_code: FileCodes):
9292
file_path = self.root_path / await file_code.get_file_path()
9393
if not file_path.exists():
9494
return APIResponse(code=404, detail="文件已过期删除")
95-
return FileResponse(file_path, filename=file_code.prefix + file_code.suffix)
95+
filename = f"{file_code.prefix}{file_code.suffix}"
96+
encoded_filename = quote(filename, safe='')
97+
content_disposition = f"attachment; filename*=UTF-8''{encoded_filename}"
98+
return FileResponse(
99+
file_path,
100+
headers={"Content-Disposition": content_disposition},
101+
filename=filename # 保留原始文件名以备某些场景使用
102+
)
96103

97104

98105
class S3FileStorage(FileStorageInterface):
@@ -118,11 +125,11 @@ def __init__(self):
118125

119126
async def save_file(self, file: UploadFile, save_path: str):
120127
async with self.session.client(
121-
"s3",
122-
endpoint_url=self.endpoint_url,
123-
aws_session_token=self.aws_session_token,
124-
region_name=self.region_name,
125-
config=Config(signature_version=self.signature_version),
128+
"s3",
129+
endpoint_url=self.endpoint_url,
130+
aws_session_token=self.aws_session_token,
131+
region_name=self.region_name,
132+
config=Config(signature_version=self.signature_version),
126133
) as s3:
127134
await s3.put_object(
128135
Bucket=self.bucket_name,
@@ -133,10 +140,10 @@ async def save_file(self, file: UploadFile, save_path: str):
133140

134141
async def delete_file(self, file_code: FileCodes):
135142
async with self.session.client(
136-
"s3",
137-
endpoint_url=self.endpoint_url,
138-
region_name=self.region_name,
139-
config=Config(signature_version=self.signature_version),
143+
"s3",
144+
endpoint_url=self.endpoint_url,
145+
region_name=self.region_name,
146+
config=Config(signature_version=self.signature_version),
140147
) as s3:
141148
await s3.delete_object(
142149
Bucket=self.bucket_name, Key=await file_code.get_file_path()
@@ -146,10 +153,10 @@ async def get_file_response(self, file_code: FileCodes):
146153
try:
147154
filename = file_code.prefix + file_code.suffix
148155
async with self.session.client(
149-
"s3",
150-
endpoint_url=self.endpoint_url,
151-
region_name=self.region_name,
152-
config=Config(signature_version=self.signature_version),
156+
"s3",
157+
endpoint_url=self.endpoint_url,
158+
region_name=self.region_name,
159+
config=Config(signature_version=self.signature_version),
153160
) as s3:
154161
link = await s3.generate_presigned_url(
155162
"get_object",
@@ -183,10 +190,10 @@ async def get_file_url(self, file_code: FileCodes):
183190
return await get_file_url(file_code.code)
184191
else:
185192
async with self.session.client(
186-
"s3",
187-
endpoint_url=self.endpoint_url,
188-
region_name=self.region_name,
189-
config=Config(signature_version=self.signature_version),
193+
"s3",
194+
endpoint_url=self.endpoint_url,
195+
region_name=self.region_name,
196+
config=Config(signature_version=self.signature_version),
190197
) as s3:
191198
result = await s3.generate_presigned_url(
192199
"get_object",
@@ -452,7 +459,7 @@ async def save_file(self, file: UploadFile, save_path: str):
452459
async with aiohttp.ClientSession(auth=self.auth) as session:
453460
content = await file.read() # 注意:大文件需要分块读取
454461
async with session.put(
455-
url, data=content, headers={"Content-Type": file.content_type}
462+
url, data=content, headers={"Content-Type": file.content_type}
456463
) as resp:
457464
if resp.status not in (200, 201, 204):
458465
content = await resp.text()

path/to/frontend/file

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { request } from '@/utils/request'; // 假设这是你的请求函数
2+
import { ElMessage } from 'element-plus'; // Element Plus 的消息组件
3+
import { t } from '@/locales'; // 国际化函数
4+
import { saveFileByWebApi, saveFileByElementA } from '@/utils/file'; // 文件保存工具函数
5+
6+
const downloadFile = (id: number) => {
7+
request({
8+
url: '/admin/file/download',
9+
method: 'get',
10+
params: {
11+
id,
12+
},
13+
responseType: 'blob'
14+
}).then((response: any) => {
15+
const contentDisposition = response.headers?.['content-disposition'] || '';
16+
let filename = 'file';
17+
18+
try {
19+
// 先尝试获取 filename* 参数
20+
const filenameStarMatch = contentDisposition.match(/filename\*=UTF-8''([^;]*)/);
21+
if (filenameStarMatch) {
22+
filename = decodeURIComponent(filenameStarMatch[1]);
23+
} else {
24+
// 如果没有 filename*,则尝试获取普通 filename
25+
const filenameMatch = contentDisposition.match(/filename="([^"]*)"/) || contentDisposition.match(/filename=([^;]*)/);
26+
if (filenameMatch) {
27+
filename = filenameMatch[1];
28+
}
29+
}
30+
} catch (error) {
31+
console.warn('解析文件名失败:', error);
32+
// 如果解析失败,使用默认文件名
33+
filename = `download_${id}`;
34+
}
35+
36+
// @ts-ignore
37+
if (window.showSaveFilePicker) {
38+
saveFileByWebApi(response.data, filename).catch(() => {
39+
saveFileByElementA(response.data, filename).catch(() => {
40+
ElMessage.error(t('admin.fileView.download_fail'));
41+
});
42+
});
43+
} else {
44+
saveFileByElementA(response.data, filename).catch(() => {
45+
ElMessage.error(t('admin.fileView.download_fail'));
46+
});
47+
}
48+
}).catch(error => {
49+
console.error('下载文件失败:', error);
50+
ElMessage.error(t('admin.fileView.download_fail'));
51+
});
52+
};
53+
54+
export default downloadFile;

themes/2023/assets/AboutView-C7hzxtgM.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

themes/2023/assets/AboutView-D5nNg17N.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/AdminView-B-o88PWR.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

themes/2023/assets/AdminView-CBxY_FuP.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/CardTools-4F6WeaAR.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

themes/2023/assets/CardTools-BeU-pM3r.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/CardTools.vue_vue_type_script_setup_true_lang-CMcmqUZX.js

Lines changed: 297 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/CardTools.vue_vue_type_script_setup_true_lang-ltPxer0O.js

Lines changed: 0 additions & 296 deletions
This file was deleted.

themes/2023/assets/FileView-B2wn8hon.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

themes/2023/assets/FileView-BtjtHAxi.js

Lines changed: 0 additions & 14 deletions
This file was deleted.

themes/2023/assets/FileView-DxNq42uF.js

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/FileView-TIfHKQHQ.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/HomeView-Bu388OX8.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

themes/2023/assets/HomeView-CBSLOGzV.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

themes/2023/assets/LocalView-8QbVv3kT.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)