update:优化分析模式

This commit is contained in:
lan
2023-03-01 01:09:47 +08:00
parent db6064330e
commit 80ec07c605
5 changed files with 183 additions and 128 deletions
+2
View File
@@ -5,6 +5,8 @@ LABEL version="6"
COPY . /app COPY . /app
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
WORKDIR /app WORKDIR /app
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
EXPOSE 12345 EXPOSE 12345
View File
+36 -3
View File
@@ -2,6 +2,7 @@ import datetime
import hashlib import hashlib
import random import random
import asyncio import asyncio
import string
import time import time
from sqlalchemy import or_, select, delete from sqlalchemy import or_, select, delete
@@ -39,12 +40,44 @@ async def delete_expire_files():
await asyncio.sleep(settings.DELETE_EXPIRE_FILES_INTERVAL * 60) await asyncio.sleep(settings.DELETE_EXPIRE_FILES_INTERVAL * 60)
async def get_code(s: AsyncSession): async def get_random_num():
code = random.randint(10000, 99999) return random.randint(10000, 99999)
async def get_random_string():
r_s = string.ascii_letters + string.digits
return ''.join(random.choice(r_s) for _ in range(5)).upper()
async def get_code(s: AsyncSession, exp_style):
if exp_style == 'forever':
generate = get_random_string
else:
generate = get_random_num
code = await generate()
while (await s.execute(select(Codes.id).where(Codes.code == code))).scalar(): while (await s.execute(select(Codes.id).where(Codes.code == code))).scalar():
code = random.randint(10000, 99999) code = await generate()
return str(code) return str(code)
async def get_token(ip, code): async def get_token(ip, code):
return hashlib.sha256(f"{ip}{code}{int(time.time() / 1000)}000{settings.ADMIN_PASSWORD}".encode()).hexdigest() return hashlib.sha256(f"{ip}{code}{int(time.time() / 1000)}000{settings.ADMIN_PASSWORD}".encode()).hexdigest()
async def get_expire_info(expire_style, expire_value, s):
now = datetime.datetime.now()
if expire_value <= 0 or expire_value > 999:
return True, None, None
code = await get_code(s, expire_style)
if expire_style == 'day':
return False, now + datetime.timedelta(days=expire_value), -1, code
elif expire_style == 'hour':
return False, now + datetime.timedelta(hours=expire_value), -1, code
elif expire_style == 'minute':
return False, now + datetime.timedelta(minutes=expire_value), -1, code
elif expire_style == 'forever':
return False, None, -1, code
elif expire_style == 'count':
return False, None, expire_value, code
else:
return True, None, None
+49 -42
View File
@@ -2,12 +2,15 @@ import asyncio
import datetime import datetime
import uuid import uuid
from pathlib import Path from pathlib import Path
from pydantic import BaseModel
from sqlalchemy import select, func, update from sqlalchemy import select, func, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import HTMLResponse, FileResponse, RedirectResponse 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, \
get_expire_info
from core.depends import admin_required from core.depends import admin_required
from fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks, Header 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
@@ -42,16 +45,22 @@ admin_html = open('templates/admin.html', 'r', encoding='utf-8').read()
@app.get('/') @app.get('/')
async def index(): async def index():
return HTMLResponse( return HTMLResponse(
index_html.replace('{{title}}', settings.TITLE).replace('{{description}}', settings.DESCRIPTION).replace( index_html
'{{keywords}}', settings.KEYWORDS).replace("'{{fileSizeLimit}}'", str(settings.FILE_SIZE_LIMIT)) .replace('{{title}}', settings.TITLE)
.replace('{{description}}', settings.DESCRIPTION)
.replace('{{keywords}}', settings.KEYWORDS)
.replace("'{{fileSizeLimit}}'", str(settings.FILE_SIZE_LIMIT))
) )
@app.get(f'/{settings.ADMIN_ADDRESS}', description='管理页面') @app.get(f'/{settings.ADMIN_ADDRESS}', description='管理页面')
async def admin(): async def admin():
return HTMLResponse( return HTMLResponse(
admin_html.replace('{{title}}', settings.TITLE).replace('{{description}}', settings.DESCRIPTION).replace( admin_html
'{{admin_address}}', settings.ADMIN_ADDRESS).replace('{{keywords}}', settings.KEYWORDS) .replace('{{title}}', settings.TITLE)
.replace('{{description}}', settings.DESCRIPTION)
.replace('{{admin_address}}', settings.ADMIN_ADDRESS)
.replace('{{keywords}}', settings.KEYWORDS)
) )
@@ -125,11 +134,10 @@ async def index(code: str, ip: str = Depends(error_ip_limit), s: AsyncSession =
if not info: if not info:
error_count = settings.ERROR_COUNT - error_ip_limit.add_ip(ip) error_count = settings.ERROR_COUNT - error_ip_limit.add_ip(ip)
raise HTTPException(status_code=404, detail=f"取件码错误,{error_count}次后将被禁止{settings.ERROR_MINUTE}分钟") raise HTTPException(status_code=404, detail=f"取件码错误,{error_count}次后将被禁止{settings.ERROR_MINUTE}分钟")
if info.exp_time < datetime.datetime.now() or info.count == 0: if (info.exp_time and info.exp_time < datetime.datetime.now()) or info.count == 0:
raise HTTPException(status_code=404, detail="取件码已失效,请联系寄件人") raise HTTPException(status_code=404, detail="取件码已失效,请联系寄件人")
await s.execute(update(Codes).where(Codes.id == info.id).values(count=info.count - 1)) await s.execute(update(Codes).where(Codes.id == info.id).values(count=info.count - 1))
await s.commit() await s.commit()
print(info.type)
if info.type != 'text': if info.type != 'text':
info.text = f'/select?code={info.code}&token={await get_token(code, ip)}' info.text = f'/select?code={info.code}&token={await get_token(code, ip)}'
return { return {
@@ -140,7 +148,6 @@ async def index(code: str, ip: str = Depends(error_ip_limit), s: AsyncSession =
@app.get('/banner') @app.get('/banner')
async def banner(request: Request): async def banner(request: Request):
# 数据库查询config
return { return {
'detail': '查询成功', 'detail': '查询成功',
'data': settings.BANNERS, 'data': settings.BANNERS,
@@ -191,40 +198,40 @@ 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)} return {'code': 200, 'data': await storage.merge_chunks(file_key, file_name, total_chunks)}
@app.post('/share', dependencies=[Depends(admin_required)], description='分享文件') class ShareDataModel(BaseModel):
async def share(text: str = Form(default=None), size: int = Form(default=0), file_key: str = Form(default=None), text: str
_type: str = Form(default=None), name: str = Form(default='text'), size: int = 0
style: str = Form(default='2'), value: int = Form(default=1), is_file: int = Form(default=True), exp_style: str
ip: str = Depends(upload_ip_limit), s: AsyncSession = Depends(get_session)): exp_value: int
if style == '2': type: str
if value > settings.MAX_DAYS: name: str
raise HTTPException(status_code=400, detail=f"最大有效天数为{settings.MAX_DAYS}") key: str = uuid.uuid4().hex
# 如果天数大于0,就设置过期时间,否则就设置为永久,无过期时间
if settings.ENABLE_PERMANENT and value < 0:
exp_time = None @app.post('/share/file/', dependencies=[Depends(admin_required)], description='分享文件')
else: async def share_file(file_model: ShareDataModel, s: AsyncSession = Depends(get_session),
exp_time = datetime.datetime.now() + datetime.timedelta(days=value) ip: str = Depends(error_ip_limit)):
exp_count = -1 exp_error, exp_time, exp_count, code = await get_expire_info(file_model.exp_style, file_model.exp_value, s)
elif style == '1': if exp_error:
if value < 1: raise HTTPException(status_code=400, detail='过期值异常')
raise HTTPException(status_code=400, detail="最小有效次数为1次") s.add(Codes(code=code, text=file_model.text, size=file_model.size, type=file_model.type, name=file_model.name,
exp_time = datetime.datetime.now() + datetime.timedelta(days=1) count=exp_count, exp_time=exp_time, key=file_model.key))
exp_count = value await s.commit()
else: upload_ip_limit.add_ip(ip)
exp_time = datetime.datetime.now() + datetime.timedelta(days=1) return {
exp_count = -1 'detail': '分享成功,请点击我的文件按钮查看上传列表',
print(is_file) 'data': {'code': code, 'key': file_model.key, 'name': file_model.name}
if is_file: }
key = file_key
# size = await storage.get_size(file)
# if size > settings.FILE_SIZE_LIMIT: @app.post('/share/text/', dependencies=[Depends(admin_required)])
# raise HTTPException(status_code=400, detail="文件过大") async def share_text(text_model: ShareDataModel, s: AsyncSession = Depends(get_session),
_text, size, name = text, size, name ip: str = Depends(error_ip_limit)):
# background_tasks.add_task(storage.save_file, file, _text) exp_error, exp_time, exp_count, code = await get_expire_info(text_model.exp_style, text_model.exp_value, s, )
else: if exp_error:
key = uuid.uuid4().hex raise HTTPException(status_code=400, detail='过期值异常')
size, _text, _type, name = len(text), text, 'text', '文本分享' exp_status, exp_time, exp_count, code = await get_expire_info(text_model.exp_style, text_model.exp_value, s)
code = await get_code(s) size, _text, _type, name, key = len(text_model.text), text_model.text, 'text', '文本分享', text_model.key
s.add(Codes(code=code, text=_text, size=size, type=_type, name=name, count=exp_count, exp_time=exp_time, key=key)) s.add(Codes(code=code, text=_text, size=size, type=_type, name=name, count=exp_count, exp_time=exp_time, key=key))
await s.commit() await s.commit()
upload_ip_limit.add_ip(ip) upload_ip_limit.add_ip(ip)
+96 -83
View File
@@ -18,10 +18,10 @@
} }
.qu .el-button { .qu .el-button {
width: 100px; width: 6rem;
height: 100px; height: 6rem;
margin: 0.2rem; margin: 0.2rem;
font-size: 30px; font-size: 2rem;
font-weight: bold; font-weight: bold;
} }
@@ -64,6 +64,14 @@
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
} }
.el-button:focus, .el-button:hover {
background-color: rgba(0, 0, 0, 0.3);
}
.el-button:active {
background-color: rgba(0, 0, 0, 0.4);
}
.el-drawer, #el-drawer__title, .el-drawer__body, .el-drawer__wrapper { .el-drawer, #el-drawer__title, .el-drawer__body, .el-drawer__wrapper {
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
border: 1px solid transparent; border: 1px solid transparent;
@@ -138,18 +146,19 @@
</el-carousel> </el-carousel>
</div> </div>
<el-row> <el-row>
<el-tooltip class="item" effect="dark" content="天数为-1则为永久" placement="top"> <el-input style="width: 190px" :readonly="uploadData.exp_style==='forever'" placeholder="数量"
<el-input style="width: 190px" placeholder="数量" v-model="uploadData.value" v-model="uploadData.exp_value"
class="input-with-select"> class="input-with-select">
<el-select style="width: 75px" v-model="uploadData.style" slot="prepend" <el-select style="width: 75px" v-model="uploadData.exp_style" slot="prepend"
placeholder="请选择"> placeholder="请选择">
<el-option label="天数" value="2"></el-option> <el-option label="天数" value="day"></el-option>
<el-option label="次数" value="1"></el-option> <el-option label="小时" value="hour"></el-option>
</el-select> <el-option label="分钟" value="minute"></el-option>
<el-button v-if="uploadData.style === '1'" slot="append" disabled></el-button> <el-option label="永久" value="forever"></el-option>
<el-button v-else slot="append" disabled></el-button> <el-option label="次数" value="count"></el-option>
</el-input> </el-select>
</el-tooltip> <el-button slot="append" disabled>${exp_style_dict[uploadData.exp_style]}</el-button>
</el-input>
<el-radio-group style="margin-left: 18px" v-model="uploadData.is_file"> <el-radio-group style="margin-left: 18px" v-model="uploadData.is_file">
<el-radio label="1"> <el-radio label="1">
文件 文件
@@ -193,7 +202,7 @@
<div class="el-icon-takeaway-box"></div> <div class="el-icon-takeaway-box"></div>
我的文件 我的文件
</el-button> </el-button>
<el-button round v-if="uploadData.is_file === '0'" @click="toUploadData"> <el-button round v-if="uploadData.is_file === '0'" @click="uploadText">
<div class="el-icon-upload2"></div> <div class="el-icon-upload2"></div>
存入 存入
</el-button> </el-button>
@@ -270,6 +279,13 @@
quDrawer: false, quDrawer: false,
jiDrawer: false, jiDrawer: false,
direction: 'btt', direction: 'btt',
exp_style_dict: {
'day': '',
'hour': '',
'minute': '',
'forever': '',
'count': '',
},
quFiles: [], quFiles: [],
jiFiles: [], jiFiles: [],
pageNum: 0, pageNum: 0,
@@ -279,12 +295,13 @@
banners: [], banners: [],
enableUpload: false, enableUpload: false,
uploadData: { uploadData: {
style: '2', exp_style: 'day',
exp_value: 1,
type: 'text', type: 'text',
value: 1, name: '文本分享',
file: null,
is_file: '1', is_file: '1',
text: '' text: '',
size: 0,
}, },
}; };
}, },
@@ -307,14 +324,13 @@
that.$message('剪切板文字正在上传,请稍等'); that.$message('剪切板文字正在上传,请稍等');
items[i].getAsString(function (str) { items[i].getAsString(function (str) {
that.uploadData.text = str; that.uploadData.text = str;
that.toUploadData(); that.uploadText();
}); });
} }
} else { } else {
const file = items[i].getAsFile(); const file = items[i].getAsFile();
that.$message('剪切板文件正在上传,请稍等'); that.$message('剪切板文件正在上传,请稍等');
that.uploadData.file = file that.uploadFile({file: file});
that.toUploadData();
} }
} }
} }
@@ -355,7 +371,7 @@
} }
resolve(res.data) resolve(res.data)
}).catch(err => { }).catch(err => {
if (this.data.detail) { if (err.response.data.detail) {
this.$message({ this.$message({
message: err.response.data.detail, message: err.response.data.detail,
type: 'error' type: 'error'
@@ -372,50 +388,6 @@
formData.append('total_chunks', total_chunks); formData.append('total_chunks', total_chunks);
await this.http('post', `/file/upload/${file_key}/`, formData) await this.http('post', `/file/upload/${file_key}/`, formData)
}, },
uploadFile: async function (e) {
if (this.checkFile(e.file)) {
this.http('post', '/file/create/').then(async res => {
const file = e.file;
let chunk_index = 0;
const shardSize = 1024 * 1024 * 5;
const {name, size, type} = 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=${name}&total_chunks=${total_chunks}`).then(text => {
this.http('post', '/share', {
file_key: res.data,
text: text.data,
size: size,
style: this.uploadData.style,
type: type,
name: name,
value: this.uploadData.value,
pwd: this.pwd
}, {
headers: {
'Content-Type': 'multipart/form-data',
'pwd': this.pwd
}
}).then(res => {
console.log(res.data)
this.jiFiles.unshift(res.data);
this.jiDrawer = true;
this.uploadData.text = '';
this.uploadData.file = null;
this.$message({
message: '上传成功',
type: 'success'
});
})
})
});
}
},
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}`;
@@ -473,26 +445,67 @@
} }
return true; return true;
}, },
toUploadData: function () { uploadText: function () {
if (this.uploadData.text === '' && this.uploadData.file === null) { if (this.uploadData.text === '') {
this.$message({ this.$message({
message: '请先粘贴文字或者上传文件', message: '请先粘贴文字',
type: 'error' type: 'error'
}); });
} else { } else {
if (this.checkFile(this.uploadData.file)) { this.http('post', '/share/text/', this.uploadData, {
this.http('post', '/share', this.uploadData, { headers: {
headers: { 'pwd': this.pwd
'Content-Type': 'multipart/form-data', }
'pwd': this.pwd }).then(res => {
} this.jiFiles.unshift(res.data);
}).then(res => { this.jiDrawer = true;
this.jiFiles.unshift(res.data); this.uploadData.text = '';
this.jiDrawer = true; this.$message({
this.uploadData.text = ''; message: '上传成功',
this.uploadData.file = null; type: 'success'
}); });
} })
}
},
uploadFile: async function (e) {
if (this.checkFile(e.file)) {
this.http('post', '/file/create/').then(async res => {
const file = e.file;
let chunk_index = 0;
const shardSize = 1024 * 1024 * 5;
const {name, size, type} = 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=${name}&total_chunks=${total_chunks}`).then(text => {
this.http('post', '/share/file/', {
text: text.data,
size: size,
exp_style: this.uploadData.exp_style,
exp_value: this.uploadData.exp_value,
type: type,
name: name,
key: res.data,
}, {
headers: {
'pwd': this.pwd
}
}).then(res => {
this.jiFiles.unshift(res.data);
this.jiDrawer = true;
this.uploadData.text = '';
this.uploadData.file = null;
this.$message({
message: '上传成功',
type: 'success'
});
})
})
});
} }
}, },
successUpload(response, file, fileList) { successUpload(response, file, fileList) {