test/custom-admin-ui #3

Merged
orion merged 673 commits from test/custom-admin-ui into master 2026-06-05 17:20:58 +08:00
3 changed files with 166 additions and 80 deletions
Showing only changes of commit e6f05fbd19 - Show all commits
+97 -63
View File
@@ -1,5 +1,7 @@
import os import os
import asyncio import asyncio
import time
import uuid
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import BinaryIO from typing import BinaryIO
@@ -16,6 +18,101 @@ if settings.STORAGE_ENGINE == 'aliyunsystem':
import oss2 import oss2
class FileSystemStorage:
def __init__(self):
self.DATA_ROOT = Path(settings.DATA_ROOT)
self.STATIC_URL = settings.STATIC_URL
self.NAME = "filesystem"
self.DOWN_PATH = '/select'
async def get_filepath(self, text: str):
return self.DATA_ROOT / text.lstrip(self.STATIC_URL + '/')
async def get_url(self, info: Codes):
return f'{self.DOWN_PATH}?code={info.code}'
async def get_text(self, file: UploadFile, key: str):
ext = file.filename.split('.')[-1]
now = datetime.now()
path = self.DATA_ROOT / f"upload/{now.year}/{now.month}/{now.day}/"
if not path.exists():
path.mkdir(parents=True)
text = f"{self.STATIC_URL}/{(path / f'{key}.{ext}').relative_to(self.DATA_ROOT)}"
return text
@staticmethod
async def get_size(file: UploadFile):
f = file.file
f.seek(0, os.SEEK_END)
size = f.tell()
f.seek(0, os.SEEK_SET)
return size
@staticmethod
def _save(filepath, file: BinaryIO):
with open(filepath, 'wb') as f:
chunk_size = 256 * 1024
chunk = file.read(chunk_size)
while chunk:
f.write(chunk)
chunk = file.read(chunk_size)
@staticmethod
def _save_chunk(filepath, file: bytes):
with open(filepath, 'wb') as f:
f.write(file)
async def create_upload_file(self):
file_key = uuid.uuid4().hex
file_path = self.DATA_ROOT / f"temp/{file_key}"
if not file_path.exists():
file_path.mkdir(parents=True)
return file_key
async def save_chunk_file(self, file_key, file_chunk, chunk_index, chunk_total):
file_path = self.DATA_ROOT / f"temp/{file_key}/"
await asyncio.to_thread(self._save_chunk, file_path / f"{chunk_total}-{chunk_index}.temp", file_chunk)
async def merge_chunks(self, file_key, file_name, total_chunks: int):
ext = file_name.split('.')[-1]
now = datetime.now()
path = self.DATA_ROOT / f"upload/{now.year}/{now.month}/{now.day}/"
if not path.exists():
path.mkdir(parents=True)
text = f"{self.STATIC_URL}/{(path / f'{file_key}.{ext}').relative_to(self.DATA_ROOT)}"
with open(path / f'{file_key}.{ext}', 'wb') as f:
for i in range(1, total_chunks + 1):
now_temp = self.DATA_ROOT / f'temp/{file_key}/{total_chunks}-{i}.temp'
with open(now_temp, 'rb') as r:
f.write(r.read())
await asyncio.to_thread(os.remove, now_temp)
await asyncio.to_thread(os.rmdir, self.DATA_ROOT / f'temp/{file_key}/')
return text
async def save_file(self, file: UploadFile, text: str):
filepath = await self.get_filepath(text)
await asyncio.to_thread(self._save, filepath, file.file)
async def delete_file(self, text: str):
filepath = await self.get_filepath(text)
if filepath.exists():
await asyncio.to_thread(os.remove, filepath)
await asyncio.to_thread(self.judge_delete_folder, filepath)
async def delete_files(self, texts):
tasks = [self.delete_file(text) for text in texts]
await asyncio.gather(*tasks)
def judge_delete_folder(self, filepath):
current = filepath.parent
while current != self.DATA_ROOT:
if not list(current.iterdir()):
os.rmdir(current)
current = current.parent
else:
break
class AliyunFileStorage: class AliyunFileStorage:
def __init__(self): def __init__(self):
auth = oss2.Auth(settings.KeyId, settings.KeySecret) auth = oss2.Auth(settings.KeyId, settings.KeySecret)
@@ -71,69 +168,6 @@ class AliyunFileStorage:
self.bucket.delete_object(text) self.bucket.delete_object(text)
class FileSystemStorage:
def __init__(self):
self.DATA_ROOT = Path(settings.DATA_ROOT)
self.STATIC_URL = settings.STATIC_URL
self.NAME = "filesystem"
self.DOWN_PATH = '/select'
async def get_filepath(self, text: str):
return self.DATA_ROOT / text.lstrip(self.STATIC_URL + '/')
async def get_url(self, info: Codes):
return f'{self.DOWN_PATH}?code={info.code}'
async def get_text(self, file: UploadFile, key: str):
ext = file.filename.split('.')[-1]
now = datetime.now()
path = self.DATA_ROOT / f"upload/{now.year}/{now.month}/{now.day}/"
if not path.exists():
path.mkdir(parents=True)
text = f"{self.STATIC_URL}/{(path / f'{key}.{ext}').relative_to(self.DATA_ROOT)}"
return text
@staticmethod
async def get_size(file: UploadFile):
f = file.file
f.seek(0, os.SEEK_END)
size = f.tell()
f.seek(0, os.SEEK_SET)
return size
@staticmethod
def _save(filepath, file: BinaryIO):
with open(filepath, 'wb') as f:
chunk_size = 256 * 1024
chunk = file.read(chunk_size)
while chunk:
f.write(chunk)
chunk = file.read(chunk_size)
async def save_file(self, file: UploadFile, text: str):
filepath = await self.get_filepath(text)
await asyncio.to_thread(self._save, filepath, file.file)
async def delete_file(self, text: str):
filepath = await self.get_filepath(text)
if filepath.exists():
await asyncio.to_thread(os.remove, filepath)
await asyncio.to_thread(self.judge_delete_folder, filepath)
async def delete_files(self, texts):
tasks = [self.delete_file(text) for text in texts]
await asyncio.gather(*tasks)
def judge_delete_folder(self, filepath):
current = filepath.parent
while current != self.DATA_ROOT:
if not list(current.iterdir()):
os.rmdir(current)
current = current.parent
else:
break
STORAGE_ENGINE = { STORAGE_ENGINE = {
"filesystem": FileSystemStorage, "filesystem": FileSystemStorage,
"aliyunsystem": AliyunFileStorage "aliyunsystem": AliyunFileStorage
+20 -2
View File
@@ -9,12 +9,12 @@ from starlette.responses import HTMLResponse, FileResponse, RedirectResponse
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
from core.utils import error_ip_limit, upload_ip_limit, get_code, storage, delete_expire_files, get_token from core.utils import error_ip_limit, upload_ip_limit, get_code, storage, delete_expire_files, get_token
from core.depends import admin_required from core.depends import admin_required
from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks, Header
from core.database import init_models, Options, Codes, get_session from core.database import init_models, Options, Codes, get_session
from settings import settings from settings import settings
# 实例化FastAPI # 实例化FastAPI
app = FastAPI(debug=settings.DEBUG, redoc_url=None, docs_url=None, openapi_url=None) app = FastAPI(debug=settings.DEBUG, redoc_url=None, )
@app.on_event('startup') @app.on_event('startup')
@@ -172,6 +172,24 @@ async def get_file(code: str, token: str, ip: str = Depends(error_ip_limit), s:
return FileResponse(filepath, filename=info.name) return FileResponse(filepath, filename=info.name)
@app.post('/file/create/')
async def create_file():
# 生成随机字符串
return {'code': 200, 'data': await storage.create_upload_file()}
@app.post('/file/upload/{file_key}/')
async def upload_file(file_key: str, file: bytes = File(...), chunk_index: int = Form(...),
total_chunks: int = Form(...)):
await storage.save_chunk_file(file_key, file, chunk_index, total_chunks)
return {'code': 200}
@app.get('/file/merge/{file_key}')
async def merge_chunks(file_key: str, file_name: str, total_chunks: int):
return {'code': 200, 'data': await storage.merge_chunks(file_key, file_name, total_chunks)}
@app.post('/share', dependencies=[Depends(admin_required)], description='分享文件') @app.post('/share', dependencies=[Depends(admin_required)], description='分享文件')
async def share(background_tasks: BackgroundTasks, text: str = Form(default=None), async def share(background_tasks: BackgroundTasks, text: str = Form(default=None),
style: str = Form(default='2'), value: int = Form(default=1), file: UploadFile = File(default=None), style: str = Form(default='2'), value: int = Form(default=1), file: UploadFile = File(default=None),
+49 -15
View File
@@ -164,6 +164,7 @@
drag drag
action="/share" action="/share"
multiple multiple
:http-request="uploadFile"
style="margin: 1rem 0;" style="margin: 1rem 0;"
:data="uploadData" :data="uploadData"
:before-upload="beforeUpload" :before-upload="beforeUpload"
@@ -183,7 +184,6 @@
placeholder="请输入内容,使用按钮存入" placeholder="请输入内容,使用按钮存入"
v-model="uploadData.text"> v-model="uploadData.text">
</el-input> </el-input>
<div class="el-upload__tip"> <div class="el-upload__tip">
<el-button round @click="pageNum=0"> <el-button round @click="pageNum=0">
<div class="el-icon-back"></div> <div class="el-icon-back"></div>
@@ -253,7 +253,7 @@
</el-row> </el-row>
</el-card> </el-card>
</el-drawer> </el-drawer>
<div style="text-align: center; margin-top: 1rem;color: #606266"> <div style="text-align: center; margin-top: 1rem;color: #606266;visibility: hidden">
<a style="text-decoration: none;color: #606266" target="_blank" href="https://github.com/vastsa/FileCodeBox">FileCodeBox <a style="text-decoration: none;color: #606266" target="_blank" href="https://github.com/vastsa/FileCodeBox">FileCodeBox
V1.6 Beta</a> V1.6 Beta</a>
</div> </div>
@@ -346,19 +346,51 @@
data: data, data: data,
...config ...config
}).then(res => { }).then(res => {
this.$message({ if (res.data.detail) {
message: res.data.detail, this.$message({
type: 'success' message: res.data.detail,
}); type: 'success'
});
}
resolve(res.data) resolve(res.data)
}).catch(err => { }).catch(err => {
this.$message({ if (this.data.detail) {
message: err.response.data.detail, this.$message({
type: 'error' message: err.response.data.detail,
}); type: 'error'
});
}
}) })
}) })
}, },
uploadChunk: async function (chunk, file_key, chunk_index, total_chunks) {
const formData = new FormData();
formData.append('file', chunk);
formData.append('file_key', file_key);
formData.append('chunk_index', chunk_index);
formData.append('total_chunks', total_chunks);
await this.http('post', `/file/upload/${file_key}/`, formData)
},
uploadFile: async function (e) {
this.http('post', '/file/create/').then(async res => {
const file = e.file;
let chunk_index = 0;
const shardSize = 1024 * 1024 * 5;
const {name, size} = file;
const total_chunks = Math.ceil(size / shardSize);
while (chunk_index < total_chunks) {
const start = chunk_index * shardSize
const end = Math.min(start + shardSize, size)
chunk_index += 1;
await this.uploadChunk(file.slice(start, end), res.data, chunk_index, total_chunks);
}
this.http('get', `/file/merge/${res.data}/?file_name=${file.name}&total_chunks=${total_chunks}`).then(res => {
console.log(res)
})
console.log(chunk_index, name, size, total_chunks);
})
},
copyText: function (value, style = 1) { copyText: function (value, style = 1) {
if (style === 0) { if (style === 0) {
value = `我给你分享了文件取件码${value}${window.location.href}?code=${value}`; value = `我给你分享了文件取件码${value}${window.location.href}?code=${value}`;
@@ -439,11 +471,13 @@
} }
}, },
successUpload(response, file, fileList) { successUpload(response, file, fileList) {
this.$message({ if (response) {
message: response.detail, this.$message({
type: 'success' message: response.detail,
}); type: 'success'
this.jiFiles.unshift(response.data); });
this.jiFiles.unshift(response.data);
}
}, },
errorUpload(error, file, fileList) { errorUpload(error, file, fileList) {
error = JSON.parse(error.message) error = JSON.parse(error.message)