From 2eafca2f0c45653df4a31ca9b982674da321b2f2 Mon Sep 17 00:00:00 2001 From: lan Date: Wed, 1 Mar 2023 01:09:47 +0800 Subject: [PATCH] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E5=88=86=E6=9E=90?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 + core/base_model.py | 0 core/utils.py | 39 +++++++++- main.py | 91 ++++++++++++---------- templates/index.html | 179 +++++++++++++++++++++++-------------------- 5 files changed, 183 insertions(+), 128 deletions(-) create mode 100644 core/base_model.py diff --git a/Dockerfile b/Dockerfile index 15314f8..baecefd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,8 @@ LABEL version="6" COPY . /app +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +RUN echo 'Asia/Shanghai' >/etc/timezone WORKDIR /app RUN pip install -r requirements.txt EXPOSE 12345 diff --git a/core/base_model.py b/core/base_model.py new file mode 100644 index 0000000..e69de29 diff --git a/core/utils.py b/core/utils.py index f7274a3..7999c7d 100644 --- a/core/utils.py +++ b/core/utils.py @@ -2,6 +2,7 @@ import datetime import hashlib import random import asyncio +import string import time from sqlalchemy import or_, select, delete @@ -39,12 +40,44 @@ async def delete_expire_files(): await asyncio.sleep(settings.DELETE_EXPIRE_FILES_INTERVAL * 60) -async def get_code(s: AsyncSession): - code = random.randint(10000, 99999) +async def get_random_num(): + 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(): - code = random.randint(10000, 99999) + code = await generate() return str(code) async def get_token(ip, code): 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 diff --git a/main.py b/main.py index cd34985..db6a575 100644 --- a/main.py +++ b/main.py @@ -2,12 +2,15 @@ import asyncio import datetime import uuid from pathlib import Path + +from pydantic import BaseModel from sqlalchemy import select, func, update from sqlalchemy.ext.asyncio import AsyncSession from starlette.requests import Request from starlette.responses import HTMLResponse, FileResponse, RedirectResponse 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 fastapi import FastAPI, Depends, UploadFile, Form, File, HTTPException, BackgroundTasks, Header 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('/') async def index(): return HTMLResponse( - index_html.replace('{{title}}', settings.TITLE).replace('{{description}}', settings.DESCRIPTION).replace( - '{{keywords}}', settings.KEYWORDS).replace("'{{fileSizeLimit}}'", str(settings.FILE_SIZE_LIMIT)) + index_html + .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='管理页面') async def admin(): return HTMLResponse( - admin_html.replace('{{title}}', settings.TITLE).replace('{{description}}', settings.DESCRIPTION).replace( - '{{admin_address}}', settings.ADMIN_ADDRESS).replace('{{keywords}}', settings.KEYWORDS) + admin_html + .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: error_count = settings.ERROR_COUNT - error_ip_limit.add_ip(ip) 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="取件码已失效,请联系寄件人") await s.execute(update(Codes).where(Codes.id == info.id).values(count=info.count - 1)) await s.commit() - print(info.type) if info.type != 'text': info.text = f'/select?code={info.code}&token={await get_token(code, ip)}' return { @@ -140,7 +148,6 @@ async def index(code: str, ip: str = Depends(error_ip_limit), s: AsyncSession = @app.get('/banner') async def banner(request: Request): - # 数据库查询config return { 'detail': '查询成功', '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)} -@app.post('/share', dependencies=[Depends(admin_required)], description='分享文件') -async def share(text: str = Form(default=None), size: int = Form(default=0), file_key: str = Form(default=None), - _type: str = Form(default=None), name: str = Form(default='text'), - style: str = Form(default='2'), value: int = Form(default=1), is_file: int = Form(default=True), - ip: str = Depends(upload_ip_limit), s: AsyncSession = Depends(get_session)): - if style == '2': - if value > settings.MAX_DAYS: - raise HTTPException(status_code=400, detail=f"最大有效天数为{settings.MAX_DAYS}天") - # 如果天数大于0,就设置过期时间,否则就设置为永久,无过期时间 - if settings.ENABLE_PERMANENT and value < 0: - exp_time = None - else: - exp_time = datetime.datetime.now() + datetime.timedelta(days=value) - exp_count = -1 - elif style == '1': - if value < 1: - raise HTTPException(status_code=400, detail="最小有效次数为1次") - exp_time = datetime.datetime.now() + datetime.timedelta(days=1) - exp_count = value - else: - exp_time = datetime.datetime.now() + datetime.timedelta(days=1) - exp_count = -1 - print(is_file) - if is_file: - key = file_key - # size = await storage.get_size(file) - # if size > settings.FILE_SIZE_LIMIT: - # raise HTTPException(status_code=400, detail="文件过大") - _text, size, name = text, size, name - # background_tasks.add_task(storage.save_file, file, _text) - else: - key = uuid.uuid4().hex - size, _text, _type, name = len(text), text, 'text', '文本分享' - code = await get_code(s) +class ShareDataModel(BaseModel): + text: str + size: int = 0 + exp_style: str + exp_value: int + type: str + name: str + key: str = uuid.uuid4().hex + + +@app.post('/share/file/', dependencies=[Depends(admin_required)], description='分享文件') +async def share_file(file_model: ShareDataModel, s: AsyncSession = Depends(get_session), + ip: str = Depends(error_ip_limit)): + exp_error, exp_time, exp_count, code = await get_expire_info(file_model.exp_style, file_model.exp_value, s) + if exp_error: + raise HTTPException(status_code=400, detail='过期值异常') + s.add(Codes(code=code, text=file_model.text, size=file_model.size, type=file_model.type, name=file_model.name, + count=exp_count, exp_time=exp_time, key=file_model.key)) + await s.commit() + upload_ip_limit.add_ip(ip) + return { + 'detail': '分享成功,请点击我的文件按钮查看上传列表', + 'data': {'code': code, 'key': file_model.key, 'name': file_model.name} + } + + +@app.post('/share/text/', dependencies=[Depends(admin_required)]) +async def share_text(text_model: ShareDataModel, s: AsyncSession = Depends(get_session), + ip: str = Depends(error_ip_limit)): + exp_error, exp_time, exp_count, code = await get_expire_info(text_model.exp_style, text_model.exp_value, s, ) + if exp_error: + raise HTTPException(status_code=400, detail='过期值异常') + exp_status, exp_time, exp_count, code = await get_expire_info(text_model.exp_style, text_model.exp_value, 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)) await s.commit() upload_ip_limit.add_ip(ip) diff --git a/templates/index.html b/templates/index.html index a0b74eb..d644de5 100644 --- a/templates/index.html +++ b/templates/index.html @@ -18,10 +18,10 @@ } .qu .el-button { - width: 100px; - height: 100px; + width: 6rem; + height: 6rem; margin: 0.2rem; - font-size: 30px; + font-size: 2rem; font-weight: bold; } @@ -64,6 +64,14 @@ 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 { background-color: rgba(0, 0, 0, 0.5); border: 1px solid transparent; @@ -138,18 +146,19 @@ - - - - - - - - - - + + + + + + + + + ${exp_style_dict[uploadData.exp_style]} + 文件 @@ -193,7 +202,7 @@
我的文件 - +
存入
@@ -270,6 +279,13 @@ quDrawer: false, jiDrawer: false, direction: 'btt', + exp_style_dict: { + 'day': '天', + 'hour': '时', + 'minute': '分', + 'forever': '无', + 'count': '次', + }, quFiles: [], jiFiles: [], pageNum: 0, @@ -279,12 +295,13 @@ banners: [], enableUpload: false, uploadData: { - style: '2', + exp_style: 'day', + exp_value: 1, type: 'text', - value: 1, - file: null, + name: '文本分享', is_file: '1', - text: '' + text: '', + size: 0, }, }; }, @@ -307,14 +324,13 @@ that.$message('剪切板文字正在上传,请稍等'); items[i].getAsString(function (str) { that.uploadData.text = str; - that.toUploadData(); + that.uploadText(); }); } } else { const file = items[i].getAsFile(); that.$message('剪切板文件正在上传,请稍等'); - that.uploadData.file = file - that.toUploadData(); + that.uploadFile({file: file}); } } } @@ -355,7 +371,7 @@ } resolve(res.data) }).catch(err => { - if (this.data.detail) { + if (err.response.data.detail) { this.$message({ message: err.response.data.detail, type: 'error' @@ -372,50 +388,6 @@ formData.append('total_chunks', total_chunks); 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) { if (style === 0) { value = `我给你分享了文件,取件码:${value},${window.location.href}?code=${value}`; @@ -473,26 +445,67 @@ } return true; }, - toUploadData: function () { - if (this.uploadData.text === '' && this.uploadData.file === null) { + uploadText: function () { + if (this.uploadData.text === '') { this.$message({ - message: '请先粘贴文字或者上传文件', + message: '请先粘贴文字', type: 'error' }); } else { - if (this.checkFile(this.uploadData.file)) { - this.http('post', '/share', this.uploadData, { - headers: { - 'Content-Type': 'multipart/form-data', - 'pwd': this.pwd - } - }).then(res => { - this.jiFiles.unshift(res.data); - this.jiDrawer = true; - this.uploadData.text = ''; - this.uploadData.file = null; + this.http('post', '/share/text/', this.uploadData, { + headers: { + 'pwd': this.pwd + } + }).then(res => { + this.jiFiles.unshift(res.data); + this.jiDrawer = true; + this.uploadData.text = ''; + this.$message({ + message: '上传成功', + 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) {