新增取件码过期条件,上传大小限制

This commit is contained in:
lan-air
2022-12-10 21:11:25 +08:00
parent e7dcc69524
commit 073aff59be
5 changed files with 199 additions and 90 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 78 KiB

+110 -62
View File
@@ -18,24 +18,43 @@ app = FastAPI()
if not os.path.exists('./static'): if not os.path.exists('./static'):
os.makedirs('./static') os.makedirs('./static')
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")
index_html = open('templates/index.html', 'r', encoding='utf-8').read()
admin_html = open('templates/admin.html', 'r', encoding='utf-8').read() ############################################
# 过期时间 # 需要修改的参数
exp_hour = 24
# 允许错误次数 # 允许错误次数
error_count = 5 error_count = 5
# 禁止分钟数 # 禁止分钟数
error_minute = 60 error_minute = 10
# 后台地址 # 后台地址
admin_address = 'admin' admin_address = 'admin'
# 管理密码 # 管理密码
admin_password = 'admin' admin_password = 'admin'
# 文件大小限制 10M
file_size_limit = 1024 * 1024 * 10
# 系统标题
title = '口令传送箱'
# 系统描述
description = 'FileCodeBox,口令传送箱,匿名口令分享文本,文件,图片,视频,音频,压缩包等文件'
# 系统关键字
keywords = 'FileCodeBox,口令传送箱,匿名口令分享文本,文件,图片,视频,音频,压缩包等文件'
############################################
index_html = open('templates/index.html', 'r', encoding='utf-8').read() \
.replace('{{title}}', title) \
.replace('{{description}}', description) \
.replace('{{keywords}}', keywords)
admin_html = open('templates/admin.html', 'r', encoding='utf-8').read() \
.replace('{{title}}', title) \
.replace('{{description}}', description) \
.replace('{{keywords}}', keywords)
error_ip_count = {} error_ip_count = {}
def delete_file(files): def delete_file(files):
for file in files: for file in files:
os.remove('.' + file) if file['type'] != 'text':
os.remove('.' + file['text'])
def get_db(): def get_db():
@@ -55,14 +74,17 @@ def get_code(db: Session = Depends(get_db)):
def get_file_name(key, ext, file): def get_file_name(key, ext, file):
now = datetime.datetime.now() now = datetime.datetime.now()
file_bytes = file.file.read()
size = len(file_bytes)
if size > file_size_limit:
return size, '', '', ''
path = f'./static/upload/{now.year}/{now.month}/{now.day}/' path = f'./static/upload/{now.year}/{now.month}/{now.day}/'
name = f'{key}.{ext}' name = f'{key}.{ext}'
if not os.path.exists(path): if not os.path.exists(path):
os.makedirs(path) os.makedirs(path)
file = file.file.read()
with open(f'{os.path.join(path, name)}', 'wb') as f: with open(f'{os.path.join(path, name)}', 'wb') as f:
f.write(file) f.write(file_bytes)
return key, len(file), path[1:] + name return size, path[1:] + name, file.content_type, file.filename
@app.get(f'/{admin_address}') @app.get(f'/{admin_address}')
@@ -76,20 +98,19 @@ async def admin_post(request: Request, db: Session = Depends(get_db)):
codes = db.query(database.Codes).all() codes = db.query(database.Codes).all()
return {'code': 200, 'msg': '查询成功', 'data': codes} return {'code': 200, 'msg': '查询成功', 'data': codes}
else: else:
return {'code': 400, 'msg': '密码错误'} return {'code': 404, 'msg': '密码错误'}
@app.delete(f'/{admin_address}') @app.delete(f'/{admin_address}')
async def admin_delete(request: Request, code: str, db: Session = Depends(get_db)): async def admin_delete(request: Request, code: str, db: Session = Depends(get_db)):
if request.headers.get('pwd') == admin_password: if request.headers.get('pwd') == admin_password:
file = db.query(database.Codes).filter(database.Codes.code == code) file = db.query(database.Codes).filter(database.Codes.code == code).first()
if file.first().type != 'text/plain': threading.Thread(target=delete_file, args=([{'type': file.type, 'text': file.text}],)).start()
threading.Thread(target=delete_file, args=([file.first().text],)).start() db.delete(file)
file.delete()
db.commit() db.commit()
return {'code': 200, 'msg': '删除成功'} return {'code': 200, 'msg': '删除成功'}
else: else:
return {'code': 400, 'msg': '密码错误'} return {'code': 404, 'msg': '密码错误'}
@app.get('/') @app.get('/')
@@ -97,60 +118,87 @@ async def index():
return HTMLResponse(index_html) return HTMLResponse(index_html)
def check_ip(ip):
# 检查ip是否被禁止
if ip in error_ip_count:
if error_ip_count[ip]['count'] >= error_count:
if error_ip_count[ip]['time'] + datetime.timedelta(minutes=error_minute) > datetime.datetime.now():
return False
else:
error_ip_count.pop(ip)
return True
def ip_error(ip):
ip_info = error_ip_count.get(ip, {'count': 0, 'time': datetime.datetime.now()})
ip_info['count'] += 1
error_ip_count[ip] = ip_info
return ip_info['count']
@app.post('/') @app.post('/')
async def index(request: Request, code: str, db: Session = Depends(get_db)): async def index(request: Request, code: str, db: Session = Depends(get_db)):
ip = request.client.host
if not check_ip(ip):
return {'code': 404, 'msg': '错误次数过多,请稍后再试'}
info = db.query(database.Codes).filter(database.Codes.code == code).first() info = db.query(database.Codes).filter(database.Codes.code == code).first()
error = error_ip_count.get(request.client.host, {'count': 0, 'time': datetime.datetime.now()})
if error['count'] > error_count:
if datetime.datetime.now() - error['time'] < datetime.timedelta(minutes=error_minute):
return {'code': 404, 'msg': '请求过于频繁,请稍后再试'}
else:
error['count'] = 0
else:
if not info: if not info:
error['count'] += 1 return {'code': 404, 'msg': f'取件码错误,错误{error_count - ip_error(ip)}次将被禁止10分钟'}
error_ip_count[request.client.host] = error if info.exp_time < datetime.datetime.now():
return {'code': 404, 'msg': f'取件码错误,错误5次将被禁止10分钟'} threading.Thread(target=delete_file, args=([{'type': info.type, 'text': info.text}],)).start()
else: db.delete(info)
return {'code': 200, 'msg': '取件成功,请点击“取”查看', 'data': info} db.commit()
return {'code': 404, 'msg': '取件码已过期,请联系寄件人'}
info.count -= 1
if info.count == 0:
threading.Thread(target=delete_file, args=([{'type': info.type, 'text': info.text}],)).start()
db.delete(info)
db.commit()
return {'code': 200, 'msg': '取件成功,请点击""查看', 'data': info}
@app.post('/share') @app.post('/share')
async def share(text: str = Form(default=None), file: UploadFile = File(default=None), db: Session = Depends(get_db)): async def share(text: str = Form(default=None), style: str = Form(default='2'), value: int = Form(default=1),
cutoff_time = datetime.datetime.now() - datetime.timedelta(hours=exp_hour) file: UploadFile = File(default=None), db: Session = Depends(get_db)):
olds = db.query(database.Codes).filter(database.Codes.use_time < cutoff_time) exps = db.query(database.Codes).filter(database.Codes.exp_time < datetime.datetime.now())
threading.Thread(target=delete_file, args=([old.text for old in olds if old.type != 'text/plain'],)).start() threading.Thread(target=delete_file, args=([[{'type': old.type, 'text': old.text}] for old in exps.all()],)).start()
olds.delete() exps.delete()
db.commit() db.commit()
code = get_code(db) code = get_code(db)
if text: if style == '2':
info = database.Codes( if value > 7:
code=code, return {'code': 404, 'msg': '最大有效天数为7天'}
text=text, exp_time = datetime.datetime.now() + datetime.timedelta(days=value)
type='text/plain', exp_count = -1
key=uuid.uuid4().hex, elif style == '1':
size=len(text), if value < 1:
used=True, return {'code': 404, 'msg': '最小有效次数为1次'}
name='分享文本' exp_time = datetime.datetime.now() + datetime.timedelta(days=1)
) exp_count = value
db.add(info)
db.commit()
return {'code': 200, 'msg': '上传成功,请点击文件库查看',
'data': {'code': code, 'name': '分享文本', 'text': text}}
elif file:
key, size, full_path = get_file_name(uuid.uuid4().hex, file.filename.split('.')[-1], file)
info = database.Codes(
code=code,
text=full_path,
type=file.content_type,
key=key,
size=size,
used=True,
name=file.filename
)
db.add(info)
db.commit()
return {'code': 200, 'msg': '上传成功,请点击文件库查看',
'data': {'code': code, 'name': file.filename, 'text': full_path}}
else: else:
return {'code': 422, 'msg': '参数错误', 'data': []} exp_time = datetime.datetime.now() + datetime.timedelta(days=1)
exp_count = -1
key = uuid.uuid4().hex
if file:
size, _text, _type, name = get_file_name(key, file.filename.split('.')[-1], file)
if size > file_size_limit:
return {'code': 404, 'msg': '文件过大'}
else:
size, _text, _type, name = len(text), text, 'text', '文本分享'
info = database.Codes(
code=code,
text=_text,
size=size,
type=_type,
name=name,
count=exp_count,
exp_time=exp_time,
key=key
)
db.add(info)
db.commit()
return {
'code': 200,
'msg': '分享成功,请点击文件箱查看取件码',
'data': {'code': code, 'key': key, 'name': name, 'text': _text}
}
+8 -1
View File
@@ -17,7 +17,7 @@
- [x] Sqlite3数据库:无需安装数据库 - [x] Sqlite3数据库:无需安装数据库
- [x] 可以加get参数code,这样打开就会读取取件码如:http://xxx.com?code=12345 - [x] 可以加get参数code,这样打开就会读取取件码如:http://xxx.com?code=12345
- [x] 管理面板:简单列表页删除违规文件 - [x] 管理面板:简单列表页删除违规文件
- [ ] 口令使用次数,口令有效期,二维码分享 - [x] 口令使用次数,口令有效期,二维码分享
## 更新记录 ## 更新记录
@@ -26,6 +26,13 @@
1. 管理面板已新增,一如既往的极简,只有删除 1. 管理面板已新增,一如既往的极简,只有删除
2. 二维码图片(调用的网络接口,如果离线环境将无法使用,一切为了极简) 2. 二维码图片(调用的网络接口,如果离线环境将无法使用,一切为了极简)
### 2022年12月10日 21:10:00
1. 取件码有效期,取件码使用次数
2. 优化代码逻辑
3. 限制上传文件大小
4. 完善配置参数
## 系统截图 ## 系统截图
### 取件 ### 取件
+20 -7
View File
@@ -6,13 +6,19 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/assert/index.css"> <link rel="stylesheet" href="/static/assert/index.css">
<title>后台管理-口令传送箱</title> <title>后台管理-{{title}}</title>
<meta name="description" content="{{description}}"/>
<meta name="keywords" content="{{keywords}}"/>
<meta name="generator" content="FileCodeBox"/>
<meta name="template" content="Lan"/>
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<el-row v-if="login"> <el-row v-if="login" :gutter="10">
<el-col :span="4">&nbsp;</el-col> <el-col :xs="0" :sm="4" :md="4" :lg="4" :xl="4">
<el-col :span="16"> &nbsp;
</el-col>
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
<el-card> <el-card>
<el-empty v-if="files.length===0" description="暂时还没有文件"></el-empty> <el-empty v-if="files.length===0" description="暂时还没有文件"></el-empty>
<el-card v-for="file in files" :key="file.code"> <el-card v-for="file in files" :key="file.code">
@@ -21,6 +27,8 @@
<div style="cursor: pointer;text-align: left"> <div style="cursor: pointer;text-align: left">
<div>取件码:${ file.code }</div> <div>取件码:${ file.code }</div>
<div>文件名:${ file.name }</div> <div>文件名:${ file.name }</div>
<div>&nbsp;&nbsp; 数:${ file.count }</div>
<div>&nbsp;&nbsp; 期:${ file.exp_time.slice(0,19) }</div>
<div v-if="file.name==='分享文本'"> <div v-if="file.name==='分享文本'">
<span>&nbsp;&nbsp; 容:${ file.text }</span> <span>&nbsp;&nbsp; 容:${ file.text }</span>
</div> </div>
@@ -39,7 +47,7 @@
</el-card> </el-card>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="4">&nbsp;</el-col> <el-col :xs="0" :sm="4" :md="4" :lg="4" :xl="4">&nbsp;</el-col>
</el-row> </el-row>
<div v-else style="width: 250px;text-align: center;margin: 40vh auto"> <div v-else style="width: 250px;text-align: center;margin: 40vh auto">
<el-input v-model="pwd" placeholder="请输入登录密码"> <el-input v-model="pwd" placeholder="请输入登录密码">
@@ -60,7 +68,12 @@
return { return {
login: false, login: false,
pwd: '', pwd: '',
files: [] files: [],
config: {
error_count: 0,
file_size: 0,
admin_pwd: 'admin'
}
}; };
}, },
mounted: function () { mounted: function () {
@@ -97,7 +110,7 @@
}); });
} }
}) })
} },
} }
}) })
</script> </script>
+59 -18
View File
@@ -6,8 +6,11 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/assert/index.css"> <link rel="stylesheet" href="/static/assert/index.css">
<!-- 引入组件库 --> <title>{{title}}</title>
<title>口令传送箱</title> <meta name="description" content="{{description}}"/>
<meta name="keywords" content="{{keywords}}"/>
<meta name="generator" content="FileCodeBox"/>
<meta name="template" content="Lan"/>
<style> <style>
.qu .el-button { .qu .el-button {
width: 100px; width: 100px;
@@ -27,7 +30,7 @@
</head> </head>
<body> <body>
<div id="app" style="text-align: center;"> <div id="app" style="text-align: center;">
<el-row v-if="tool==0" class="qu" style="width:400px;margin: auto"> <el-row v-if="tool === 0" class="qu" style="width:400px;margin: auto">
<el-card style="padding-bottom: 1rem"> <el-card style="padding-bottom: 1rem">
<el-col :span="24"> <el-col :span="24">
<el-input autofocus @input="inputing" clearable v-model:value="code" maxlength="5" <el-input autofocus @input="inputing" clearable v-model:value="code" maxlength="5"
@@ -72,17 +75,29 @@
</el-col> </el-col>
</el-card> </el-card>
</el-row> </el-row>
<el-row v-else style="width:400px;margin: auto"> <el-row v-else style="width:400px;margin: 20vh auto 0 auto">
<el-col :span="24" style="margin: 1rem 0"> <el-col :span="24" style="margin: 1rem 0">
<el-card> <el-card>
<div style="margin-bottom: 1rem">
<el-radio-group v-model="destoryData.style">
<el-radio label="1">次数</el-radio>
<el-radio label="2">天数</el-radio>
</el-radio-group>
<el-input-number
placeholder="输入数量,默认1"
style="margin-left: 1rem" v-model="destoryData.value" controls-position="right" :min="1">
</el-input-number>
</div>
<el-upload <el-upload
drag drag
action="/share" action="/share"
multiple multiple
:data="destoryData"
:on-success="successUpload" :on-success="successUpload"
> >
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖、粘贴到此处,或<em>点击上传</em>24h后删除</div> <div class="el-upload__text">将文字、文件拖、粘贴到此处,或<em>点击上传</em></div>
<div class="el-upload__text" style="font-size: 10px">天数<7或限制次数24h后删除</div>
<div class="el-upload__tip" slot="tip"> <div class="el-upload__tip" slot="tip">
<el-button @click="tool=0">去取件</el-button> <el-button @click="tool=0">去取件</el-button>
<el-button @click="cunDrawer=true">文件箱</el-button> <el-button @click="cunDrawer=true">文件箱</el-button>
@@ -96,7 +111,7 @@
href="https://github.com/vastsa/FileCodeBox">FileCodeBox</a></span> href="https://github.com/vastsa/FileCodeBox">FileCodeBox</a></span>
</div> </div>
<el-drawer <el-drawer
title="文件" title="文件"
:visible.sync="quDrawer" :visible.sync="quDrawer"
:direction="direction" :direction="direction"
size="50%" size="50%"
@@ -105,7 +120,7 @@
<div style="cursor: pointer;text-align: left"> <div style="cursor: pointer;text-align: left">
<div>取件码:${ file.code }</div> <div>取件码:${ file.code }</div>
<div>文件名:${ file.name }</div> <div>文件名:${ file.name }</div>
<div v-if="file.name==='分享文本'"> <div v-if="file.name==='文本分享'">
<span>&nbsp;&nbsp; 容:${ file.text }</span> <span>&nbsp;&nbsp; 容:${ file.text }</span>
</div> </div>
<div v-else> <div v-else>
@@ -119,13 +134,13 @@
<el-empty v-if="files.length===0" description="请输入取件码,收取文件"></el-empty> <el-empty v-if="files.length===0" description="请输入取件码,收取文件"></el-empty>
</el-drawer> </el-drawer>
<el-drawer <el-drawer
title="文件" title="文件"
:visible.sync="cunDrawer" :visible.sync="cunDrawer"
direction="btt" direction="btt"
size="50%"> size="50%">
<el-card> <el-card>
<el-empty v-if="files.length===0" description="请上传文件"></el-empty> <el-empty v-if="files.length===0" description="请上传文件"></el-empty>
<el-card style="margin-top: 0.2rem" v-for="file in files" :key="file.code"> <el-card style="margin-top: 0.2rem" v-for="(file,index) in files" :key="index">
<el-row> <el-row>
<el-col :span="20"> <el-col :span="20">
<el-row> <el-row>
@@ -139,7 +154,7 @@
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<img style="width: 80px;height: 80px;" <img style="width: 80px;height: 80px;"
:src="qrcodeUrl(file)"> :src="qrcodeUrl(file)" :alt="file.code">
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
@@ -164,7 +179,11 @@
quDrawer: false, quDrawer: false,
cunDrawer: false, cunDrawer: false,
direction: 'btt', direction: 'btt',
tool: 0 tool: 0,
destoryData: {
style: '2',
value: 1
},
}; };
}, },
mounted: function () { mounted: function () {
@@ -184,10 +203,16 @@
that.$message('剪切板文字正在上传,请稍等'); that.$message('剪切板文字正在上传,请稍等');
items[i].getAsString(function (str) { items[i].getAsString(function (str) {
FileData.append('text', str); FileData.append('text', str);
FileData.append('style', that.destoryData.style);
FileData.append('value', that.destoryData.value);
axios.post('/share', FileData) axios.post('/share', FileData)
.then(function (response) { .then(function (res) {
that.$message({'message': response.data.msg, 'type': 'success'}); if (res.data.code === 200) {
that.files.push(response.data.data) that.$message({'message': res.data.msg, 'type': 'success'});
that.files.push(res.data.data)
} else {
that.$message({'message': res.data.msg, 'type': 'error'});
}
}) })
.catch(function (error) { .catch(function (error) {
that.$message({'message': error.data.msg, 'type': 'error'}); that.$message({'message': error.data.msg, 'type': 'error'});
@@ -199,9 +224,15 @@
that.$message('剪切板文件正在上传,请稍等'); that.$message('剪切板文件正在上传,请稍等');
const FileData = new FormData(); const FileData = new FormData();
FileData.append('file', file || ''); FileData.append('file', file || '');
FileData.append('style', that.destoryData.style);
FileData.append('value', that.destoryData.value);
axios.post('/share', FileData).then(async res => { axios.post('/share', FileData).then(async res => {
that.files.push(res.data.data); if (res.data.code === 200) {
that.$message({'message': res.data.msg, 'type': 'success'}); that.$message({'message': res.data.msg, 'type': 'success'});
that.files.push(res.data.data)
} else {
that.$message({'message': res.data.msg, 'type': 'error'});
}
}) })
} }
@@ -254,11 +285,21 @@
} }
}, },
successUpload(response, file, fileList) { successUpload(response, file, fileList) {
this.$message({'message': '上传成功,请点击文件库查看', 'type': 'success'}); if (response.code === 200) {
this.files.push(response.data) this.$message({
message: response.msg,
type: 'success'
});
this.files.push(response.data);
} else {
this.$message({
message: response.msg,
type: 'error'
});
}
}, },
qrcodeUrl(file) { qrcodeUrl(file) {
return 'https://api.qrserver.com/v1/create-qr-code?data=' + window.location.origin + '/?code=' + file.code return 'https://api.qrserver.com/v1/create-qr-code/?data=' + window.location.origin + '/?code=' + file.code
}, },
} }
}) })