Skip to content

Commit 7fc733d

Browse files
committed
update:切片上传
1 parent c77fd35 commit 7fc733d

File tree

3 files changed

+158
-72
lines changed

3 files changed

+158
-72
lines changed

core/storage.py

Lines changed: 89 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22
import asyncio
3+
import time
4+
import uuid
35
from datetime import datetime
46
from pathlib import Path
57
from typing import BinaryIO
@@ -16,61 +18,6 @@
1618
import oss2
1719

1820

19-
class AliyunFileStorage:
20-
def __init__(self):
21-
auth = oss2.Auth(settings.KeyId, settings.KeySecret)
22-
self.bucket = oss2.Bucket(auth, settings.OSS_ENDPOINT, settings.BUCKET_NAME)
23-
24-
def upload_file(self, upload_filepath, remote_filepath):
25-
self.bucket.put_object_from_file(remote_filepath, upload_filepath)
26-
27-
async def get_text(self, file: UploadFile, key: str):
28-
ext = file.filename.split('.')[-1]
29-
now = datetime.now()
30-
path = f"FileCodeBox/upload/{now.year}/{now.month}/{now.day}"
31-
text = f"{path}/{f'{key}.{ext}'}"
32-
return f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/{text}"
33-
34-
async def get_url(self, info: Codes):
35-
text = info.text.strip(f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/")
36-
url = self.bucket.sign_url('GET', text, settings.ACCESSTIME, slash_safe=True)
37-
return url
38-
39-
@staticmethod
40-
async def get_size(file: UploadFile):
41-
f = file.file
42-
f.seek(0, os.SEEK_END)
43-
size = f.tell()
44-
f.seek(0, os.SEEK_SET)
45-
return size
46-
47-
@staticmethod
48-
def _save(filepath, file: BinaryIO):
49-
with open(filepath, 'wb') as f:
50-
chunk_size = 256 * 1024
51-
chunk = file.read(chunk_size)
52-
while chunk:
53-
f.write(chunk)
54-
chunk = file.read(chunk_size)
55-
56-
async def save_file(self, file: UploadFile, remote_filepath: str):
57-
now = int(datetime.now().timestamp())
58-
upload_filepath = settings.DATA_ROOT + str(now)
59-
await asyncio.to_thread(self._save, upload_filepath, file.file)
60-
self.upload_file(upload_filepath, remote_filepath)
61-
remote_filepath = remote_filepath.strip(f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/")
62-
self.upload_file(upload_filepath, remote_filepath)
63-
await asyncio.to_thread(os.remove, upload_filepath)
64-
65-
async def delete_files(self, texts):
66-
tasks = [self.delete_file(text) for text in texts]
67-
await asyncio.gather(*tasks)
68-
69-
async def delete_file(self, text: str):
70-
text = text.strip(f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/")
71-
self.bucket.delete_object(text)
72-
73-
7421
class FileSystemStorage:
7522
def __init__(self):
7623
self.DATA_ROOT = Path(settings.DATA_ROOT)
@@ -110,6 +57,38 @@ def _save(filepath, file: BinaryIO):
11057
f.write(chunk)
11158
chunk = file.read(chunk_size)
11259

60+
@staticmethod
61+
def _save_chunk(filepath, file: bytes):
62+
with open(filepath, 'wb') as f:
63+
f.write(file)
64+
65+
async def create_upload_file(self):
66+
file_key = uuid.uuid4().hex
67+
file_path = self.DATA_ROOT / f"temp/{file_key}"
68+
if not file_path.exists():
69+
file_path.mkdir(parents=True)
70+
return file_key
71+
72+
async def save_chunk_file(self, file_key, file_chunk, chunk_index, chunk_total):
73+
file_path = self.DATA_ROOT / f"temp/{file_key}/"
74+
await asyncio.to_thread(self._save_chunk, file_path / f"{chunk_total}-{chunk_index}.temp", file_chunk)
75+
76+
async def merge_chunks(self, file_key, file_name, total_chunks: int):
77+
ext = file_name.split('.')[-1]
78+
now = datetime.now()
79+
path = self.DATA_ROOT / f"upload/{now.year}/{now.month}/{now.day}/"
80+
if not path.exists():
81+
path.mkdir(parents=True)
82+
text = f"{self.STATIC_URL}/{(path / f'{file_key}.{ext}').relative_to(self.DATA_ROOT)}"
83+
with open(path / f'{file_key}.{ext}', 'wb') as f:
84+
for i in range(1, total_chunks + 1):
85+
now_temp = self.DATA_ROOT / f'temp/{file_key}/{total_chunks}-{i}.temp'
86+
with open(now_temp, 'rb') as r:
87+
f.write(r.read())
88+
await asyncio.to_thread(os.remove, now_temp)
89+
await asyncio.to_thread(os.rmdir, self.DATA_ROOT / f'temp/{file_key}/')
90+
return text
91+
11392
async def save_file(self, file: UploadFile, text: str):
11493
filepath = await self.get_filepath(text)
11594
await asyncio.to_thread(self._save, filepath, file.file)
@@ -134,6 +113,61 @@ def judge_delete_folder(self, filepath):
134113
break
135114

136115

116+
class AliyunFileStorage:
117+
def __init__(self):
118+
auth = oss2.Auth(settings.KeyId, settings.KeySecret)
119+
self.bucket = oss2.Bucket(auth, settings.OSS_ENDPOINT, settings.BUCKET_NAME)
120+
121+
def upload_file(self, upload_filepath, remote_filepath):
122+
self.bucket.put_object_from_file(remote_filepath, upload_filepath)
123+
124+
async def get_text(self, file: UploadFile, key: str):
125+
ext = file.filename.split('.')[-1]
126+
now = datetime.now()
127+
path = f"FileCodeBox/upload/{now.year}/{now.month}/{now.day}"
128+
text = f"{path}/{f'{key}.{ext}'}"
129+
return f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/{text}"
130+
131+
async def get_url(self, info: Codes):
132+
text = info.text.strip(f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/")
133+
url = self.bucket.sign_url('GET', text, settings.ACCESSTIME, slash_safe=True)
134+
return url
135+
136+
@staticmethod
137+
async def get_size(file: UploadFile):
138+
f = file.file
139+
f.seek(0, os.SEEK_END)
140+
size = f.tell()
141+
f.seek(0, os.SEEK_SET)
142+
return size
143+
144+
@staticmethod
145+
def _save(filepath, file: BinaryIO):
146+
with open(filepath, 'wb') as f:
147+
chunk_size = 256 * 1024
148+
chunk = file.read(chunk_size)
149+
while chunk:
150+
f.write(chunk)
151+
chunk = file.read(chunk_size)
152+
153+
async def save_file(self, file: UploadFile, remote_filepath: str):
154+
now = int(datetime.now().timestamp())
155+
upload_filepath = settings.DATA_ROOT + str(now)
156+
await asyncio.to_thread(self._save, upload_filepath, file.file)
157+
self.upload_file(upload_filepath, remote_filepath)
158+
remote_filepath = remote_filepath.strip(f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/")
159+
self.upload_file(upload_filepath, remote_filepath)
160+
await asyncio.to_thread(os.remove, upload_filepath)
161+
162+
async def delete_files(self, texts):
163+
tasks = [self.delete_file(text) for text in texts]
164+
await asyncio.gather(*tasks)
165+
166+
async def delete_file(self, text: str):
167+
text = text.strip(f"https://{settings.BUCKET_NAME}.{settings.OSS_ENDPOINT}/")
168+
self.bucket.delete_object(text)
169+
170+
137171
STORAGE_ENGINE = {
138172
"filesystem": FileSystemStorage,
139173
"aliyunsystem": AliyunFileStorage

main.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
from starlette.staticfiles import StaticFiles
1010
from core.utils import error_ip_limit, upload_ip_limit, get_code, storage, delete_expire_files, get_token
1111
from core.depends import admin_required
12-
from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks
12+
from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks, Header
1313
from core.database import init_models, Options, Codes, get_session
1414
from settings import settings
1515

1616
# 实例化FastAPI
17-
app = FastAPI(debug=settings.DEBUG, redoc_url=None, docs_url=None, openapi_url=None)
17+
app = FastAPI(debug=settings.DEBUG, redoc_url=None, )
1818

1919

2020
@app.on_event('startup')
@@ -172,6 +172,24 @@ async def get_file(code: str, token: str, ip: str = Depends(error_ip_limit), s:
172172
return FileResponse(filepath, filename=info.name)
173173

174174

175+
@app.post('/file/create/')
176+
async def create_file():
177+
# 生成随机字符串
178+
return {'code': 200, 'data': await storage.create_upload_file()}
179+
180+
181+
@app.post('/file/upload/{file_key}/')
182+
async def upload_file(file_key: str, file: bytes = File(...), chunk_index: int = Form(...),
183+
total_chunks: int = Form(...)):
184+
await storage.save_chunk_file(file_key, file, chunk_index, total_chunks)
185+
return {'code': 200}
186+
187+
188+
@app.get('/file/merge/{file_key}')
189+
async def merge_chunks(file_key: str, file_name: str, total_chunks: int):
190+
return {'code': 200, 'data': await storage.merge_chunks(file_key, file_name, total_chunks)}
191+
192+
175193
@app.post('/share', dependencies=[Depends(admin_required)], description='分享文件')
176194
async def share(background_tasks: BackgroundTasks, text: str = Form(default=None),
177195
style: str = Form(default='2'), value: int = Form(default=1), file: UploadFile = File(default=None),

templates/index.html

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
drag
165165
action="/share"
166166
multiple
167+
:http-request="uploadFile"
167168
style="margin: 1rem 0;"
168169
:data="uploadData"
169170
:before-upload="beforeUpload"
@@ -183,7 +184,6 @@
183184
placeholder="请输入内容,使用按钮存入"
184185
v-model="uploadData.text">
185186
</el-input>
186-
187187
<div class="el-upload__tip">
188188
<el-button round @click="pageNum=0">
189189
<div class="el-icon-back"></div>
@@ -253,7 +253,7 @@ <h1 @click="copyText(file.code,0)" style="margin: 0;display: inline;cursor: poin
253253
</el-row>
254254
</el-card>
255255
</el-drawer>
256-
<div style="text-align: center; margin-top: 1rem;color: #606266">
256+
<div style="text-align: center; margin-top: 1rem;color: #606266;visibility: hidden">
257257
<a style="text-decoration: none;color: #606266" target="_blank" href="https://github.com/vastsa/FileCodeBox">FileCodeBox
258258
V1.6 Beta</a>
259259
</div>
@@ -346,18 +346,50 @@ <h1 @click="copyText(file.code,0)" style="margin: 0;display: inline;cursor: poin
346346
data: data,
347347
...config
348348
}).then(res => {
349-
this.$message({
350-
message: res.data.detail,
351-
type: 'success'
352-
});
349+
if (res.data.detail) {
350+
this.$message({
351+
message: res.data.detail,
352+
type: 'success'
353+
});
354+
}
353355
resolve(res.data)
354356
}).catch(err => {
355-
this.$message({
356-
message: err.response.data.detail,
357-
type: 'error'
358-
});
357+
if (this.data.detail) {
358+
this.$message({
359+
message: err.response.data.detail,
360+
type: 'error'
361+
});
362+
}
363+
})
364+
})
365+
},
366+
uploadChunk: async function (chunk, file_key, chunk_index, total_chunks) {
367+
const formData = new FormData();
368+
formData.append('file', chunk);
369+
formData.append('file_key', file_key);
370+
formData.append('chunk_index', chunk_index);
371+
formData.append('total_chunks', total_chunks);
372+
await this.http('post', `/file/upload/${file_key}/`, formData)
373+
},
374+
uploadFile: async function (e) {
375+
this.http('post', '/file/create/').then(async res => {
376+
const file = e.file;
377+
let chunk_index = 0;
378+
const shardSize = 1024 * 1024 * 5;
379+
const {name, size} = file;
380+
const total_chunks = Math.ceil(size / shardSize);
381+
while (chunk_index < total_chunks) {
382+
const start = chunk_index * shardSize
383+
const end = Math.min(start + shardSize, size)
384+
chunk_index += 1;
385+
await this.uploadChunk(file.slice(start, end), res.data, chunk_index, total_chunks);
386+
}
387+
this.http('get', `/file/merge/${res.data}/?file_name=${file.name}&total_chunks=${total_chunks}`).then(res => {
388+
console.log(res)
359389
})
390+
console.log(chunk_index, name, size, total_chunks);
360391
})
392+
361393
},
362394
copyText: function (value, style = 1) {
363395
if (style === 0) {
@@ -439,11 +471,13 @@ <h1 @click="copyText(file.code,0)" style="margin: 0;display: inline;cursor: poin
439471
}
440472
},
441473
successUpload(response, file, fileList) {
442-
this.$message({
443-
message: response.detail,
444-
type: 'success'
445-
});
446-
this.jiFiles.unshift(response.data);
474+
if (response) {
475+
this.$message({
476+
message: response.detail,
477+
type: 'success'
478+
});
479+
this.jiFiles.unshift(response.data);
480+
}
447481
},
448482
errorUpload(error, file, fileList) {
449483
error = JSON.parse(error.message)

0 commit comments

Comments
 (0)