This commit is contained in:
lan-air
2022-12-09 19:06:47 +08:00
commit 19806f04c1
9 changed files with 747 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
media/
logs/
.idea
static/
__pycache__/
*.py[cod]
*$py.class
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
# C extensions
*.so
*.env
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test.pdf / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# Project
.vscode
.DS_Store
for_test.py
.html
/evaluate/temp.py
/evaluation/back.json
.env*
.backup/
/cloc-1.64.exe
*.db
+13
View File
@@ -0,0 +1,13 @@
FROM python:3.9.5
LABEL author="Lan"
LABEL email="vast@tom.com"
LABEL version="1.0"
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 -i https://pypi.tuna.tsinghua.edu.cn/simple/
EXPOSE 123456
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "12345"]
BIN
View File
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
import datetime
from sqlalchemy import create_engine, DateTime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Boolean, Column, Integer, String
engine = create_engine('sqlite:///database.db', connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Codes(Base):
__tablename__ = 'codes'
id = Column(Integer, primary_key=True, index=True)
code = Column(String(10), unique=True, index=True)
key = Column(String(30), unique=True, index=True)
name = Column(String(500))
size = Column(Integer)
type = Column(String(20))
text = Column(String(500))
used = Column(Boolean, default=False)
use_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
+124
View File
@@ -0,0 +1,124 @@
import datetime
import os
import uuid
from fastapi import FastAPI, Depends, UploadFile, Form, File
from sqlalchemy.orm import Session
from starlette.requests import Request
from starlette.responses import HTMLResponse
import random
from starlette.staticfiles import StaticFiles
import database
from database import engine, SessionLocal, Base
Base.metadata.create_all(bind=engine)
app = FastAPI()
if not os.path.exists('./static'):
os.makedirs('./static')
app.mount("/static", StaticFiles(directory="static"), name="static")
# 过期时间
exp_hour = 24
# 允许错误次数
error_count = 5
# 禁止分钟数
error_minute = 60
error_ip_count = {}
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def get_code(db: Session = Depends(get_db)):
code = random.randint(10000, 99999)
while db.query(database.Codes).filter(database.Codes.code == code).first():
code = random.randint(10000, 99999)
return str(code)
def get_file_name(key, ext, file):
now = datetime.datetime.now()
path = f'./static/upload/{now.year}/{now.month}/{now.day}/'
name = f'{key}.{ext}'
if not os.path.exists(path):
os.makedirs(path)
file = file.file.read()
with open(f'{os.path.join(path, name)}', 'wb') as f:
f.write(file)
return key, len(file), path[1:] + name
@app.get('/')
async def index():
with open('templates/index.html', 'r') as f:
return HTMLResponse(f.read())
@app.post('/')
async def index(request: Request, code: str, db: Session = Depends(get_db)):
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:
error['count'] += 1
error_ip_count[request.client.host] = error
return {'code': 404, 'msg': f'取件码错误,错误5次将被禁止10分钟'}
else:
return {'code': 200, 'msg': '取件成功,请点击库查看', 'data': info}
@app.get('/share')
async def share():
with open('templates/upload.html', 'r') as f:
return HTMLResponse(f.read())
@app.post('/share')
async def share(text: str = Form(default=None), file: UploadFile = File(default=None), db: Session = Depends(get_db)):
cutoff_time = datetime.datetime.now() - datetime.timedelta(hours=exp_hour)
db.query(database.Codes).filter(database.Codes.use_time < cutoff_time).delete()
db.commit()
code = get_code(db)
if text:
info = database.Codes(
code=code,
text=text,
type='text/plain',
key=uuid.uuid4().hex,
size=len(text),
used=True,
name='分享文本'
)
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:
return {'code': 422, 'msg': '参数错误', 'data': []}
+34
View File
@@ -0,0 +1,34 @@
# 口令传送箱
## 主要特色
- [x] 拖拽,复制粘贴上传
- [x] 文件口令传输
- [x] 分享文件:多种上传方式供你选择
- [x] 分享文本:直接复制粘贴直接上传
- [x] 防爆破:错误五次拉黑十分钟
- [x] 完全匿名:不记录任何信息
- [x] 无需注册:无需注册,无需登录
- [x] Sqlite3数据库:无需安装数据库
## 部署方式
### 服务端部署
1. 安装Python3
2. 拉取代码,解压缩
3. 安装依赖包:`pip install -r requirements.txt`
4. 运行` uvicorn main:app --host 0.0.0.0 --port 12345`
5. 然后你自己看怎么进程守护吧
### 宝塔部署
1. 安装宝塔Python Manager
2. 然后你自己看着填吧
### Docker部署
```bash
docker build --file Dockerfile --tag filecodebox .
docker run -d -p 12345:12345 --name filecodebox filecodebox
```
+19
View File
@@ -0,0 +1,19 @@
anyio==3.6.2
click==8.1.3
fastapi==0.88.0
h11==0.14.0
httptools==0.5.0
idna==3.4
pydantic==1.10.2
python-dotenv==0.21.0
python-multipart==0.0.5
PyYAML==6.0
six==1.16.0
sniffio==1.3.0
SQLAlchemy==1.4.44
starlette==0.22.0
typing_extensions==4.4.0
uvicorn==0.20.0
uvloop==0.17.0
watchfiles==0.18.1
websockets==10.4
+257
View File
@@ -0,0 +1,257 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
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">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<title>取件箱-口令传送箱</title>
<style>
.qu .el-button {
width: 100px;
height: 100px;
margin: 0.2rem;
font-size: 30px;
font-weight: bold;
}
.qu .el-input__inner {
height: 100px;
margin: 1rem 0;
font-size: 30px;
font-weight: bold;
}
</style>
</head>
<body>
<div id="app" style="text-align: center;">
<el-row v-if="tool==0" class="qu" style="width:400px;margin: auto">
<el-card style="padding-bottom: 1rem">
<el-col :span="24">
<el-input autofocus @input="inputing" clearable v-model:value="code" maxlength="5"
:disabled="inout_disable"
placeholder="请输入取件码"></el-input>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('1')">1</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('2')">2</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('3')">3</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('4')">4</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('5')">5</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('6')">6</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('7')">7</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('8')">8</el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('9')">9</el-button>
</el-col>
<el-col :span=8>
<el-button @click="tool=1"></el-button>
</el-col>
<el-col :span=8>
<el-button @click="inputNumber('0')">0</el-button>
</el-col>
<el-col :span=8>
<el-button @click="quDrawer=true"></el-button>
</el-col>
</el-card>
</el-row>
<el-row v-else style="width:400px;margin: auto">
<el-col :span="24" style="margin: 1rem 0">
<el-card>
<el-upload
drag
action="/share"
multiple
:on-success="successUpload"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖、粘贴到此处,或<em>点击上传</em>24h后删除</div>
<div class="el-upload__tip" slot="tip">
<el-button @click="tool=0">去取件</el-button>
<el-button @click="cunDrawer=true">文件箱</el-button>
</div>
</el-upload>
</el-card>
</el-col>
</el-row>
<div style="text-align: center; margin-top: 1rem;color: #606266">
<span> By: <a style="text-decoration: none;color: #606266" target="_blank"
href="https://github.com/vastsa/FileShare">Lan</a></span>
</div>
<el-drawer
title="文件库"
:visible.sync="quDrawer"
:direction="direction"
size="50%"
>
<el-card v-for="(file,index) in files" :key="index" class="box-card">
<div style="cursor: pointer;text-align: left">
<div>取件码:${ file.code }</div>
<div>文件名:${ file.name }</div>
<div v-if="file.name==='分享文本'">
<span>&nbsp;&nbsp; 容:${ file.text }</span>
</div>
<div v-else>
<span>&nbsp;&nbsp; 接:</span>
<a :href="file.text" target="_blank"
style="color: #1E9FFF;text-underline: none"
type="primary">点击下载</a>
</div>
</div>
</el-card>
<el-empty v-if="files.length===0" description="请输入取件码,收取文件"></el-empty>
</el-drawer>
<el-drawer
title="文件库"
:visible.sync="cunDrawer"
direction="btt"
size="50%">
<el-card>
<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-row>
<el-col :span="24" style="line-height: 45px">
${ file.name }
</el-col>
<el-col :span="24">
取件码:<h1 style="margin: 0;display: inline">${ file.code }</h1>
</el-col>
</el-row>
</el-card>
</el-card>
</el-drawer>
</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="//unpkg.com/axios/dist/axios.min.js"></script>
<script>
new Vue({
el: '#app',
delimiters: ['${', '}'],
data: function () {
return {
code: '',
inout_disable: false,
files: [],
quDrawer: false,
cunDrawer: false,
direction: 'btt',
tool: 0
};
},
mounted: function () {
this.code = window.location.search.substring('code=='.length)
if (this.code) {
this.get_file();
}
const that = this
document.addEventListener('paste', function (event) {
if (that.tool === 1) {
const items = event.clipboardData && event.clipboardData.items;
if (items && items.length) {
for (let i = 0; i < items.length; i++) {
if (items[i].kind === 'string') {
if (items[i].type.match(/^text\/plain/)) {
const FileData = new FormData();
that.$message('剪切板文字正在上传,请稍等');
items[i].getAsString(function (str) {
FileData.append('text', str);
axios.post('/share', FileData)
.then(function (response) {
that.$message({'message': response.data.msg, 'type': 'success'});
that.files.push(response.data.data)
})
.catch(function (error) {
that.$message({'message': error.data.msg, 'type': 'error'});
});
});
}
} else {
const file = items[i].getAsFile();
that.$message('剪切板文件正在上传,请稍等');
const FileData = new FormData();
FileData.append('file', file || '');
axios.post('/share', FileData).then(async res => {
that.files.push(res.data.data);
that.$message({'message': res.data.msg, 'type': 'success'});
})
}
}
}
}
});
},
methods: {
inputing: function (value) {
if (value.length === 5) {
this.inout_disable = true;
this.get_file();
}
},
get_file: function () {
const that = this;
axios.post('?code=' + this.code).then(function (response) {
if (response.data.code === 404) {
that.$message({
message: response.data.msg,
type: 'error'
});
} else {
that.files.push(response.data.data);
that.$message({
message: response.data.msg,
type: 'success'
});
}
that.code = '';
that.inout_disable = false
}).catch(function (error) {
console.log(error);
});
},
inputNumber: function (number) {
if (number === 'C') {
this.code = '';
} else if (number === 'X') {
this.code = this.code.substring(0, this.code.length - 1);
} else {
if (this.code.length < 5) {
this.code += number;
}
}
if (this.code.length === 5) {
this.inout_disable = true
this.get_file()
}
},
successUpload(response, file, fileList) {
this.files.push(response.data[0])
},
}
})
</script>
</html>
+121
View File
@@ -0,0 +1,121 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
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">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<title>寄件箱-口令传送箱</title>
<style>
.el-upload-dragger {
}
</style>
</head>
<body>
<div id="app" style="text-align: center;height: 100vh">
<el-row style="width:400px;margin: auto">
<el-col :span="24" style="margin: 1rem 0">
<el-card>
<el-upload
drag
action=""
multiple
:on-success="successUpload"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖、粘贴到此处,或<em>点击上传</em>24h后删除</div>
<div class="el-upload__tip" slot="tip">
<a href="/">
<el-button>去取件</el-button>
</a>
<el-button @click="drawer=true">文件箱</el-button>
</div>
</el-upload>
</el-card>
</el-col>
</el-row>
<el-drawer
title="文件库"
:visible.sync="drawer"
direction="btt"
size="50%">
<el-card>
<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-row>
<el-col :span="24" style="line-height: 45px">
${ file.name }
</el-col>
<el-col :span="24">
取件码:<h1 style="margin: 0;display: inline">${ file.code }</h1>
</el-col>
</el-row>
</el-card>
</el-card>
</el-drawer>
</div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="//unpkg.com/axios/dist/axios.min.js"></script>
<script>
new Vue({
el: '#app',
delimiters: ['${', '}'],
data: function () {
return {
files: [],
drawer: false
};
},
mounted: function () {
const that = this
document.addEventListener('paste', function () {
const items = event.clipboardData && event.clipboardData.items;
if (items && items.length) {
for (let i = 0; i < items.length; i++) {
if (items[i].kind === 'string') {
if (items[i].type.match(/^text\/plain/)) {
const FileData = new FormData();
that.$message('剪切板文字正在上传,请稍等');
items[i].getAsString(function (str) {
FileData.append('text', str);
axios.post('', FileData)
.then(function (response) {
that.$message({'message': response.data.msg, 'type': 'success'});
that.files.push(response.data.data)
})
.catch(function (error) {
that.$message({'message': error.data.msg, 'type': 'error'});
});
});
}
} else {
const file = items[i].getAsFile();
that.$message('剪切板文件正在上传,请稍等');
const FileData = new FormData();
FileData.append('file', file || '');
axios.post('', FileData).then(async res => {
that.files.push(res.data.data);
that.$message({'message': res.data.msg, 'type': 'success'});
})
}
}
}
});
},
methods: {
successUpload(response, file, fileList) {
this.files.push(response.data[0])
},
}
})
</script>
</html>