add:fronted

This commit is contained in:
lan
2023-08-14 13:30:10 +08:00
parent 0e06f9b6a3
commit a41613b3e8
16 changed files with 176 additions and 100 deletions
+49
View File
@@ -0,0 +1,49 @@
# @Time : 2023/8/14 12:20
# @Author : Lan
# @File : depends.py
# @Software: PyCharm
from typing import Union
from datetime import datetime, timedelta
from fastapi import Header, HTTPException, Request
from core.response import APIResponse
async def admin_required(pwd: Union[str, None] = Header(default=None), request: Request = None):
return False
class IPRateLimit:
def __init__(self, count, minutes):
self.ips = {}
self.count = count
self.minutes = minutes
def check_ip(self, ip):
# 检查ip是否被禁止
if ip in self.ips:
if self.ips[ip]['count'] >= self.count:
if self.ips[ip]['time'] + timedelta(minutes=self.minutes) > datetime.now():
return False
else:
self.ips.pop(ip)
return True
def add_ip(self, ip):
ip_info = self.ips.get(ip, {'count': 0, 'time': datetime.now()})
ip_info['count'] += 1
ip_info['time'] = datetime.now()
self.ips[ip] = ip_info
return ip_info['count']
async def remove_expired_ip(self):
for ip in list(self.ips.keys()):
if self.ips[ip]['time'] + timedelta(minutes=self.minutes) < datetime.now():
self.ips.pop(ip)
def __call__(self, request: Request):
ip = request.headers.get('X-Real-IP', request.headers.get('X-Forwarded-For', request.client.host))
if not self.check_ip(ip):
raise HTTPException(status_code=423, detail=f"请求次数过多,请稍后再试")
return ip
+1 -1
View File
@@ -28,7 +28,7 @@ class FileCodes(Model):
created_at: Optional[datetime] = fields.DatetimeField(auto_now_add=True, description='创建时间') created_at: Optional[datetime] = fields.DatetimeField(auto_now_add=True, description='创建时间')
async def is_expired(self): async def is_expired(self):
if self.expired_at: if self.expired_at and (self.expired_count == -1 or self.used_count < self.expired_count):
return self.expired_at < await get_now() return self.expired_at < await get_now()
else: else:
return self.expired_count != -1 and self.used_count >= self.expired_count return self.expired_count != -1 and self.used_count >= self.expired_count
+5
View File
@@ -0,0 +1,5 @@
from pydantic import BaseModel
class SelectFileModel(BaseModel):
code: str
+8 -1
View File
@@ -7,6 +7,7 @@ import uuid
import os import os
from fastapi import UploadFile from fastapi import UploadFile
from apps.base.depends import IPRateLimit
from apps.base.models import FileCodes from apps.base.models import FileCodes
from core.utils import get_random_num, get_random_string from core.utils import get_random_num, get_random_string
@@ -41,7 +42,6 @@ async def get_expire_info(expire_value: int, expire_style: str):
:return: expired_at 过期时间, expired_count 可用次数, used_count 已用次数, code 随机码 :return: expired_at 过期时间, expired_count 可用次数, used_count 已用次数, code 随机码
""" """
expired_count, used_count, now, code = -1, 0, datetime.datetime.now(), None expired_count, used_count, now, code = -1, 0, datetime.datetime.now(), None
if expire_style == 'day': if expire_style == 'day':
expired_at = now + datetime.timedelta(days=expire_value) expired_at = now + datetime.timedelta(days=expire_value)
elif expire_style == 'hour': elif expire_style == 'hour':
@@ -58,6 +58,7 @@ async def get_expire_info(expire_value: int, expire_style: str):
expired_at = now + datetime.timedelta(days=1) expired_at = now + datetime.timedelta(days=1)
if not code: if not code:
code = await get_random_code() code = await get_random_code()
print(expire_style, expire_value, expired_at, expired_count, used_count, code)
return expired_at, expired_count, used_count, code return expired_at, expired_count, used_count, code
@@ -70,3 +71,9 @@ async def get_random_code(style='num'):
code = await get_random_num() if style == 'num' else await get_random_string() code = await get_random_num() if style == 'num' else await get_random_string()
if not await FileCodes.filter(code=code).exists(): if not await FileCodes.filter(code=code).exists():
return code return code
# 错误IP限制器
error_ip_limit = IPRateLimit(1, 1)
# 上传文件限制器
upload_ip_limit = IPRateLimit(10, 1)
+23 -41
View File
@@ -2,11 +2,12 @@
# @Author : Lan # @Author : Lan
# @File : views.py # @File : views.py
# @Software: PyCharm # @Software: PyCharm
from fastapi import APIRouter, Form, UploadFile, File from fastapi import APIRouter, Form, UploadFile, File, Depends
from pydantic import BaseModel
from apps.base.models import FileCodes from apps.base.models import FileCodes
from apps.base.utils import get_expire_info, get_file_path_name from apps.base.pydantics import SelectFileModel
from apps.base.utils import get_expire_info, get_file_path_name, error_ip_limit
from core.response import APIResponse
from core.storage import file_storage from core.storage import file_storage
share_api = APIRouter( share_api = APIRouter(
@@ -27,13 +28,9 @@ async def share_text(text: str = Form(...), expire_value: int = Form(default=1,
size=len(text), size=len(text),
prefix='文本分享' prefix='文本分享'
) )
return { return APIResponse(detail={
'code': 200, 'code': code,
'msg': 'success', })
'data': {
'code': code,
}
}
@share_api.post('/file/') @share_api.post('/file/')
@@ -52,40 +49,25 @@ async def share_file(expire_value: int = Form(default=1, gt=0), expire_style: st
expired_count=expired_count, expired_count=expired_count,
used_count=used_count, used_count=used_count,
) )
return { return APIResponse(detail={
'code': 200, 'code': code,
'msg': 'success', 'name': file.filename,
'data': { })
'code': code,
'name': file.filename,
}
}
class SelectFileModel(BaseModel):
code: str
@share_api.post('/select/') @share_api.post('/select/')
async def select_file(data: SelectFileModel): async def select_file(data: SelectFileModel, ip: str = Depends(error_ip_limit)):
file_code = await FileCodes.filter(code=data.code).first() file_code = await FileCodes.filter(code=data.code).first()
if not file_code: if not file_code:
return { error_ip_limit.add_ip(ip)
'code': 404, return APIResponse(code=404, detail='文件不存在')
'msg': '文件不存在',
}
if await file_code.is_expired(): if await file_code.is_expired():
return { return APIResponse(code=403, detail='文件已过期')
'code': 403, file_code.used_count += 1
'msg': '文件已过期', await file_code.save()
} return APIResponse(detail={
return { 'code': file_code.code,
'code': 200, 'name': file_code.prefix + file_code.suffix,
'msg': 'success', 'size': file_code.size,
'data': { 'text': await file_storage.get_file_url(file_code),
'code': file_code.code, })
'name': file_code.prefix + file_code.suffix,
'size': file_code.size,
'text': await file_storage.get_file_url(file_code),
}
}
+15
View File
@@ -0,0 +1,15 @@
# @Time : 2023/8/14 11:48
# @Author : Lan
# @File : response.py
# @Software: PyCharm
from typing import Generic, TypeVar
from pydantic.v1.generics import GenericModel
T = TypeVar('T')
class APIResponse(GenericModel, Generic[T]):
code: int = 200
message: str = 'ok'
detail: T
+1 -1
View File
@@ -63,4 +63,4 @@ class S3FileStorage:
return result return result
file_storage = S3FileStorage() file_storage = SystemFileStorage()
+2
View File
@@ -6,6 +6,8 @@ import datetime
import random import random
import string import string
from apps.base.depends import IPRateLimit
async def get_random_num(): async def get_random_num():
""" """
-2
View File
@@ -11,11 +11,9 @@ declare module 'vue' {
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElModal: typeof import('element-plus/es')['ElModal']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
+2 -1
View File
@@ -50,7 +50,8 @@ const copyText = (text: any, style = 0) => {
<qrcode-vue :value="value.text" :size="100"></qrcode-vue> <qrcode-vue :value="value.text" :size="100"></qrcode-vue>
<div style="display: flex;flex-direction: column;justify-content: space-around"> <div style="display: flex;flex-direction: column;justify-content: space-around">
<el-tag size="large" style="cursor: pointer" @click="copyText(value.code)">{{ value.code }}</el-tag> <el-tag size="large" style="cursor: pointer" @click="copyText(value.code)">{{ value.code }}</el-tag>
<el-tag v-if="value.name!=='文本分享'" size="large" type="success" style="cursor: pointer" @click="openUrl(value.text);">点击下载 <el-tag v-if="value.name!=='文本分享'" size="large" type="success" style="cursor: pointer" @click="openUrl(value.text);">
点击下载
</el-tag> </el-tag>
<el-tag v-else size="large" type="success" style="cursor: pointer" @click="copyText(value.text);">点击复制</el-tag> <el-tag v-else size="large" type="success" style="cursor: pointer" @click="copyText(value.text);">点击复制</el-tag>
</div> </div>
+5 -5
View File
@@ -11,8 +11,8 @@ const props = defineProps({
type: Object, type: Object,
default: () => { default: () => {
return { return {
expireValue: 1, expire_value: 1,
expireStyle: 'day', expire_style: 'day',
} }
} }
} }
@@ -36,8 +36,8 @@ const handleHttpRequest = (options: any) => {
fileBoxStore.showFileBox = true; fileBoxStore.showFileBox = true;
const formData = new FormData(); const formData = new FormData();
formData.append('file', options.file); formData.append('file', options.file);
formData.append('expireValue', props.shareData.expireValue); formData.append('expire_value', props.shareData.expireValue);
formData.append('expireStyle', props.shareData.expireStyle); formData.append('expire_style', props.shareData.expireStyle);
request( request(
{ {
url: "share/file/", url: "share/file/",
@@ -54,7 +54,7 @@ const handleHttpRequest = (options: any) => {
} }
} }
).then((res: any) => { ).then((res: any) => {
const data = res.data.data; const data = res.detail;
fileStore.shareData.forEach((file: any) => { fileStore.shareData.forEach((file: any) => {
if (file.uid === options.file.uid) { if (file.uid === options.file.uid) {
file.status = 'success'; file.status = 'success';
+28 -24
View File
@@ -11,35 +11,39 @@ const props = defineProps({
type: Object, type: Object,
default: () => { default: () => {
return { return {
expireValue: 1, expire_value: 1,
expireStyle: 'day', expire_style: 'day',
} }
} }
} }
}) })
const handleSubmitShareText = ()=>{ const handleSubmitShareText = ()=>{
const formData = new FormData(); if (shareText.value === '') {
formData.append('text', shareText.value); alert('请输入您要分享的文本');
formData.append('expireValue', props.shareData.expireValue); } else {
formData.append('expireStyle', props.shareData.expireStyle); const formData = new FormData();
request({ formData.append('text', shareText.value);
'url': 'share/text/', formData.append('expire_value', props.shareData.expireValue);
'method': 'post', formData.append('expire_style', props.shareData.expireStyle);
'data': formData, request({
}).then((res: any) => { 'url': 'share/text/',
const data = res.data.data; 'method': 'post',
fileBoxStore.showFileBox = true; 'data': formData,
fileStore.addShareData({ }).then((res: any) => {
'name': '文本分享', const data = res.detail;
'text': data.text, fileBoxStore.showFileBox = true;
'code': data.code, fileStore.addShareData({
'status': 'success', 'name': '文本分享',
'percentage': 100, 'text': data.text,
'size': shareText.value.length, 'code': data.code,
'type': 'text', 'status': 'success',
'uid': Date.now(), 'percentage': 100,
}) 'size': shareText.value.length,
}) 'type': 'text',
'uid': Date.now(),
})
});
}
} }
</script> </script>
+15 -8
View File
@@ -2,17 +2,24 @@
import axios from "axios"; import axios from "axios";
const instance = axios.create({ const instance = axios.create({
baseURL: "", baseURL: "http://localhost:12345",
timeout: 6000000, timeout: 6000000,
headers:{ headers:{
'Authorization':localStorage.getItem('auth') 'Authorization':localStorage.getItem('auth')
} }
}); });
// 对响应进行拦截
instance.interceptors.response.use(
(response:any) => {
if (response.data.code === 200) {
return response.data;
} else {
alert(response.data.detail);
return Promise.reject(response.data);
}
}, (error:any) => {
alert(error.response.data.detail);
return Promise.reject(error);
});
const Request = axios.create({ export const request = instance;
baseURL: "http://localhost:12345",
timeout: 6000000,
});
export const request = Request;
export const http = instance;
+12 -15
View File
@@ -33,24 +33,21 @@ watch(code, (newVal) => {
'code': newVal 'code': newVal
} }
}).then((res: any) => { }).then((res: any) => {
fileBoxStore.showFileBox = true;
let flag = true;
fileStore.receiveData.forEach((file: any) => {
if (file.code === res.detail.code) {
flag = false;
return;
}
});
if (flag) {
fileStore.addReceiveData(res.detail);
}
}).finally(() => {
input_status.readonly = false; input_status.readonly = false;
input_status.loading = false; input_status.loading = false;
code.value = ''; code.value = '';
if (res.data.code === 200) {
fileBoxStore.showFileBox = true;
let flag = true;
fileStore.receiveData.forEach((file: any) => {
if (file.code === res.data.data.code) {
flag = false;
return;
}
});
if (flag) {
fileStore.addReceiveData(res.data.data);
}
} else {
alert(res.data.msg||'未知错误')
}
}); });
} }
}); });
Binary file not shown.
+10 -1
View File
@@ -4,6 +4,8 @@
# @Software: PyCharm # @Software: PyCharm
from fastapi import FastAPI from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
from starlette.responses import HTMLResponse
from starlette.staticfiles import StaticFiles
from tortoise.contrib.fastapi import register_tortoise from tortoise.contrib.fastapi import register_tortoise
from apps.base.views import share_api from apps.base.views import share_api
@@ -16,11 +18,13 @@ app.add_middleware(
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
app.mount('/assets', StaticFiles(directory='./fcb-fronted/dist/assets'), name="assets")
register_tortoise( register_tortoise(
app, app,
generate_schemas=True, generate_schemas=True,
add_exception_handlers=True, add_exception_handlers=True,
config={ config={
'connections': { 'connections': {
'default': 'sqlite://filecodebox.db' 'default': 'sqlite://filecodebox.db'
@@ -40,3 +44,8 @@ register_tortoise(
app.include_router( app.include_router(
share_api share_api
) )
@app.get('/')
async def index():
return HTMLResponse(content=open('./fcb-fronted/dist/index.html', 'r', encoding='utf-8').read(), status_code=200)