Migrating the project to an asynchronous model
This commit is contained in:
parent
eeff5c307c
commit
463b2bcd8c
248
404.html
248
404.html
@ -1,115 +1,155 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Ghink Universe - 404</title>
|
<title>Ghink Universe - 404</title>
|
||||||
<link rel="icon" href="https://cdn.ghink.net/image/ghink/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="https://cdn.gh.ink/image/ghink/favicon.ico" type="image/x-icon">
|
||||||
<style>
|
<link rel="stylesheet" href="https://cdn.gh.ink/assembly/aplayer/1.10.0/APlayer.min.css">
|
||||||
body {
|
<script src="https://cdn.gh.ink/js/vue/2.6.14/vue.min.js"></script>
|
||||||
background-color: #2F3242;
|
<script src="https://cdn.gh.ink/js/axios/1.1.3/axios.min.js"></script>
|
||||||
}
|
<script src="https://cdn.gh.ink/site/public/js/msg.js"></script>
|
||||||
svg {
|
<style>
|
||||||
position: absolute;
|
body {
|
||||||
top: 50%;
|
background-color: #2F3242;
|
||||||
left: 50%;
|
}
|
||||||
margin-top: -250px;
|
.svg {
|
||||||
margin-left: -400px;
|
position: absolute;
|
||||||
}
|
top: 50%;
|
||||||
.message-box {
|
left: 50%;
|
||||||
height: 200px;
|
margin-top: -250px;
|
||||||
width: 380px;
|
margin-left: -400px;
|
||||||
position: absolute;
|
}
|
||||||
top: 50%;
|
.message-box {
|
||||||
left: 50%;
|
height: 200px;
|
||||||
margin-top: -100px;
|
width: 380px;
|
||||||
margin-left: 50px;
|
position: absolute;
|
||||||
color: #FFF;
|
top: 50%;
|
||||||
font-family: Roboto;
|
left: 50%;
|
||||||
font-weight: 300;
|
margin-top: -200px;
|
||||||
}
|
margin-left: 50px;
|
||||||
.message-box h1 {
|
color: #FFF;
|
||||||
font-size: 60px;
|
font-family: Roboto;
|
||||||
line-height: 46px;
|
font-weight: 300;
|
||||||
margin-bottom: 40px;
|
}
|
||||||
}
|
.message-box h1 {
|
||||||
.buttons-con .action-link-wrap {
|
font-size: 60px;
|
||||||
margin-top: 40px;
|
line-height: 46px;
|
||||||
}
|
margin-bottom: 40px;
|
||||||
.buttons-con .action-link-wrap a {
|
}
|
||||||
background: #68c950;
|
.music {
|
||||||
padding: 8px 25px;
|
margin-top: 50px;
|
||||||
border-radius: 4px;
|
color: #000;
|
||||||
color: #FFF;
|
}
|
||||||
font-weight: bold;
|
.buttons-con .action-link-wrap {
|
||||||
font-size: 14px;
|
margin-top: 40px;
|
||||||
transition: all 0.3s linear;
|
}
|
||||||
cursor: pointer;
|
.buttons-con .action-link-wrap a {
|
||||||
text-decoration: none;
|
background: #68c950;
|
||||||
margin-right: 10px
|
padding: 8px 25px;
|
||||||
}
|
border-radius: 4px;
|
||||||
.buttons-con .action-link-wrap a:hover {
|
color: #FFF;
|
||||||
background: #5A5C6C;
|
font-weight: bold;
|
||||||
color: #fff;
|
font-size: 14px;
|
||||||
}
|
transition: all 0.3s linear;
|
||||||
|
cursor: pointer;
|
||||||
#Polygon-1 , #Polygon-2 , #Polygon-3 , #Polygon-4 , #Polygon-4, #Polygon-5 {
|
text-decoration: none;
|
||||||
animation: float 1s infinite ease-in-out alternate;
|
margin-right: 10px
|
||||||
}
|
}
|
||||||
#Polygon-2 {
|
.buttons-con .action-link-wrap a:hover {
|
||||||
animation-delay: .2s;
|
background: #5A5C6C;
|
||||||
}
|
color: #fff;
|
||||||
#Polygon-3 {
|
}
|
||||||
animation-delay: .4s;
|
|
||||||
}
|
#Polygon-1 , #Polygon-2 , #Polygon-3 , #Polygon-4 , #Polygon-4, #Polygon-5 {
|
||||||
#Polygon-4 {
|
animation: float 1s infinite ease-in-out alternate;
|
||||||
animation-delay: .6s;
|
}
|
||||||
}
|
#Polygon-2 {
|
||||||
#Polygon-5 {
|
animation-delay: .2s;
|
||||||
animation-delay: .8s;
|
}
|
||||||
}
|
#Polygon-3 {
|
||||||
|
animation-delay: .4s;
|
||||||
@keyframes float {
|
}
|
||||||
100% {
|
#Polygon-4 {
|
||||||
transform: translateY(20px);
|
animation-delay: .6s;
|
||||||
}
|
}
|
||||||
}
|
#Polygon-5 {
|
||||||
@media (max-width: 450px) {
|
animation-delay: .8s;
|
||||||
svg {
|
}
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
@keyframes float {
|
||||||
left: 50%;
|
100% {
|
||||||
margin-top: -250px;
|
transform: translateY(20px);
|
||||||
margin-left: -190px;
|
}
|
||||||
}
|
}
|
||||||
.message-box {
|
@media (max-width: 450px) {
|
||||||
top: 50%;
|
.svg {
|
||||||
left: 50%;
|
top: 50%;
|
||||||
margin-top: -100px;
|
left: 50%;
|
||||||
margin-left: -190px;
|
margin-top: -250px;
|
||||||
text-align: center;
|
margin-left: -190px;
|
||||||
}
|
}
|
||||||
}
|
.message-box {
|
||||||
</style>
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -100px;
|
||||||
|
margin-left: -190px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.music {
|
||||||
|
margin-top: 50px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<svg width="380px" height="500px" viewBox="0 0 837 1045" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
<svg width="380px" height="500px" viewBox="0 0 837 1045" version="1.1" xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" xmlns:sketch="https://www.bohemiancoding.com/sketch/ns" class="svg">
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||||
<path d="M353,9 L626.664028,170 L626.664028,487 L353,642 L79.3359724,487 L79.3359724,170 L353,9 Z" id="Polygon-1" stroke="#007FB2" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
<path d="M353,9 L626.664028,170 L626.664028,487 L353,642 L79.3359724,487 L79.3359724,170 L353,9 Z" id="Polygon-1" stroke="#007FB2" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
||||||
<path d="M78.5,529 L147,569.186414 L147,648.311216 L78.5,687 L10,648.311216 L10,569.186414 L78.5,529 Z" id="Polygon-2" stroke="#EF4A5B" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
<path d="M78.5,529 L147,569.186414 L147,648.311216 L78.5,687 L10,648.311216 L10,569.186414 L78.5,529 Z" id="Polygon-2" stroke="#EF4A5B" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
||||||
<path d="M773,186 L827,217.538705 L827,279.636651 L773,310 L719,279.636651 L719,217.538705 L773,186 Z" id="Polygon-3" stroke="#795D9C" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
<path d="M773,186 L827,217.538705 L827,279.636651 L773,310 L719,279.636651 L719,217.538705 L773,186 Z" id="Polygon-3" stroke="#795D9C" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
||||||
<path d="M639,529 L773,607.846761 L773,763.091627 L639,839 L505,763.091627 L505,607.846761 L639,529 Z" id="Polygon-4" stroke="#F2773F" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
<path d="M639,529 L773,607.846761 L773,763.091627 L639,839 L505,763.091627 L505,607.846761 L639,529 Z" id="Polygon-4" stroke="#F2773F" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
||||||
<path d="M281,801 L383,861.025276 L383,979.21169 L281,1037 L179,979.21169 L179,861.025276 L281,801 Z" id="Polygon-5" stroke="#36B455" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
<path d="M281,801 L383,861.025276 L383,979.21169 L281,1037 L179,979.21169 L179,861.025276 L281,801 Z" id="Polygon-5" stroke="#36B455" stroke-width="6" sketch:type="MSShapeGroup"></path>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="message-box">
|
<div class="message-box">
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<p>Page not found 页面找不到了</p>
|
<p>
|
||||||
<div class="buttons-con">
|
<strong>Page not found 页面找不到了</strong>
|
||||||
<div class="action-link-wrap">
|
</p>
|
||||||
<a onclick="history.back(-1)" class="link-button link-back-button">Go Back 返回</a>
|
<div class="buttons-con">
|
||||||
<a href="https://www.ghink.net" class="link-button">Go Home 返回主页</a>
|
<div class="action-link-wrap">
|
||||||
</div>
|
<a onclick="history.back(-1)" class="link-button link-back-button">Go Back 返回</a>
|
||||||
</div>
|
<a href="https://www.ghink.net" class="link-button">Go Home 返回主页</a>
|
||||||
|
</div>
|
||||||
|
<div id="aplayer" class="music"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="https://cdn.gh.ink/assembly/aplayer/1.10.0/APlayer.min.js"></script>
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
mounted () {
|
||||||
|
axios
|
||||||
|
.get('https://api.gh.ink/404music')
|
||||||
|
.then(function (response) {
|
||||||
|
const ap = new APlayer({
|
||||||
|
container: document.getElementById('aplayer'),
|
||||||
|
autoplay: false,
|
||||||
|
loop: 'all',
|
||||||
|
listFolded: true,
|
||||||
|
theme: '#5A5C6C',
|
||||||
|
order: "random",
|
||||||
|
audio: response.data.content
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
10
README.md
10
README.md
@ -1,3 +1,9 @@
|
|||||||
# short_link
|
# Short Link
|
||||||
|
|
||||||
Ghink Universe Short Link Service Source Code
|
Ghink Universe Short Link Service Source Code
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Install all requirements
|
||||||
|
2. Configure like example config
|
||||||
|
3. Run it!
|
226
index.html
226
index.html
File diff suppressed because one or more lines are too long
168
main.py
168
main.py
@ -1,6 +1,11 @@
|
|||||||
import time, random, json, threading
|
import time
|
||||||
import pymysql
|
import random
|
||||||
from flask import Flask, request, redirect
|
import json
|
||||||
|
|
||||||
|
from quart import Quart, request, redirect
|
||||||
|
|
||||||
|
import aiomysql
|
||||||
|
import aiofiles
|
||||||
|
|
||||||
with open("config.json", "r") as fb:
|
with open("config.json", "r") as fb:
|
||||||
config = json.loads(fb.read())
|
config = json.loads(fb.read())
|
||||||
@ -9,13 +14,8 @@ with open("config.json", "r") as fb:
|
|||||||
LISTEN = config["LISTEN"]
|
LISTEN = config["LISTEN"]
|
||||||
DEBUG = config["DEBUG"]
|
DEBUG = config["DEBUG"]
|
||||||
|
|
||||||
app = Flask("Ghink Short Link Service")
|
app = Quart("Ghink Short Link Service")
|
||||||
db = pymysql.connect(
|
db_pool = None
|
||||||
host=DB["host"],
|
|
||||||
user=DB["user"],
|
|
||||||
password=DB["password"],
|
|
||||||
database=DB["database"]
|
|
||||||
)
|
|
||||||
|
|
||||||
field_map = {
|
field_map = {
|
||||||
'A': 0, 'a': 1, 'B': 2, 'b': 3,
|
'A': 0, 'a': 1, 'B': 2, 'b': 3,
|
||||||
@ -33,63 +33,67 @@ field_map = {
|
|||||||
'v': 48, 'W': 49, 'w': 50, 'X': 51,
|
'v': 48, 'W': 49, 'w': 50, 'X': 51,
|
||||||
'x': 52, '6': 53, 'Y': 54, 'y': 55,
|
'x': 52, '6': 53, 'Y': 54, 'y': 55,
|
||||||
'Z': 56, 'z': 57, '7': 58, '8': 59,
|
'Z': 56, 'z': 57, '7': 58, '8': 59,
|
||||||
'9': 60, '0': 61}
|
'9': 60, '0': 61
|
||||||
|
}
|
||||||
|
|
||||||
|
async def init_db():
|
||||||
|
global db_pool
|
||||||
|
db_pool = await aiomysql.create_pool(
|
||||||
|
host=DB["host"],
|
||||||
|
user=DB["user"],
|
||||||
|
password=DB["password"],
|
||||||
|
db=DB["database"],
|
||||||
|
autocommit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.before_serving
|
||||||
|
async def startup():
|
||||||
|
await init_db()
|
||||||
|
|
||||||
|
@app.after_serving
|
||||||
|
async def shutdown():
|
||||||
|
db_pool.close()
|
||||||
|
await db_pool.wait_closed()
|
||||||
|
|
||||||
@app.route("/", methods=["GET"])
|
@app.route("/", methods=["GET"])
|
||||||
def index():
|
async def index():
|
||||||
with open("index.html", "r", encoding="utf-8") as fb:
|
return redirect("https://k76u22n4gd.apifox.cn")
|
||||||
return fb.read()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<string:link_id>", methods=["GET"])
|
@app.route("/<string:link_id>", methods=["GET"])
|
||||||
def route(link_id: str):
|
async def route(link_id: str):
|
||||||
global db
|
|
||||||
for char in link_id:
|
for char in link_id:
|
||||||
if char not in tuple(field_map.keys()):
|
if char not in field_map:
|
||||||
return redirect("https://www.ghink.net")
|
return redirect("https://www.ghink.net")
|
||||||
link_id_converted = 0
|
|
||||||
for i in range(len(link_id)):
|
|
||||||
link_id_converted += field_map[link_id[::-1][i]] * 62 ** i
|
|
||||||
|
|
||||||
try:
|
link_id_converted = sum(field_map[link_id[::-1][i]] * 62 ** i for i in range(len(link_id)))
|
||||||
db.ping()
|
|
||||||
except pymysql.err.InterfaceError:
|
|
||||||
db = pymysql.connect(
|
|
||||||
host=DB["host"],
|
|
||||||
user=DB["user"],
|
|
||||||
password=DB["password"],
|
|
||||||
database=DB["database"]
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor = db.cursor()
|
async with db_pool.acquire() as conn:
|
||||||
cursor.execute('SELECT link, validity FROM links WHERE id=%s', link_id_converted)
|
async with conn.cursor() as cursor:
|
||||||
db.commit()
|
await cursor.execute('SELECT link, validity FROM links WHERE id=%s', (link_id_converted,))
|
||||||
link = cursor.fetchone()
|
link = await cursor.fetchone()
|
||||||
|
|
||||||
if link is None or link[0] is None:
|
if link is None or link[0] is None:
|
||||||
with open("404.html", "r", encoding="utf-8") as fb:
|
async with aiofiles.open("404.html", "r", encoding="utf-8") as fb:
|
||||||
return fb.read(), 404
|
return await fb.read(), 404
|
||||||
if link[1] is not None and link[1] < time.time():
|
if link[1] is not None and link[1] < time.time():
|
||||||
remove_thread = threading.Thread(target=remove_link, args=(link_id_converted,))
|
await remove_link(link_id_converted)
|
||||||
remove_thread.start()
|
async with aiofiles.open("404.html", "r", encoding="utf-8") as fb:
|
||||||
with open("404.html", "r", encoding="utf-8") as fb:
|
return await fb.read(), 404
|
||||||
return fb.read(), 404
|
|
||||||
return redirect(link[0])
|
return redirect(link[0])
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", methods=["POST"])
|
@app.route("/", methods=["POST"])
|
||||||
def add():
|
async def add():
|
||||||
global db
|
form = await request.form # Await the form to get the data
|
||||||
key = request.form.get("key")
|
key = form.get("key") # Access the key
|
||||||
link = request.form.get("link")
|
link = form.get("link") # Access the link
|
||||||
validity = request.form.get("validity")
|
validity = form.get("validity") # Access the validity
|
||||||
# Judge whether fields are empty
|
|
||||||
if key == "" or link == "":
|
if not key or not link:
|
||||||
return json.dumps({"ok": False, "message": "bad field(s)", "id": ""})
|
return json.dumps({"ok": False, "message": "bad field(s)", "id": ""})
|
||||||
# No access
|
|
||||||
if key not in KEYS:
|
if key not in KEYS:
|
||||||
return json.dumps({"ok": False, "message": "forbidden", "id": ""})
|
return json.dumps({"ok": False, "message": "forbidden", "id": ""})
|
||||||
# Check validity
|
|
||||||
if validity:
|
if validity:
|
||||||
if validity.isdecimal() and int(validity) > time.time():
|
if validity.isdecimal() and int(validity) > time.time():
|
||||||
validity = int(validity)
|
validity = int(validity)
|
||||||
@ -98,37 +102,25 @@ def add():
|
|||||||
else:
|
else:
|
||||||
validity = None
|
validity = None
|
||||||
|
|
||||||
# Random
|
|
||||||
while True:
|
while True:
|
||||||
link_id_random = ''.join(random.sample(tuple(field_map.keys()), 6))
|
link_id_random = ''.join(random.sample(field_map.keys(), 6))
|
||||||
link_id_converted = 0
|
link_id_converted = sum(field_map[link_id_random[::-1][i]] * 62 ** i for i in range(len(link_id_random)))
|
||||||
for i in range(len(link_id_random)):
|
|
||||||
link_id_converted += field_map[link_id_random[::-1][i]] * 62 ** i
|
async with db_pool.acquire() as conn:
|
||||||
# Get Cursor
|
async with conn.cursor() as cursor:
|
||||||
try:
|
await cursor.execute('SELECT link FROM links WHERE id=%s', (link_id_converted,))
|
||||||
db.ping()
|
link_selected = await cursor.fetchone()
|
||||||
except pymysql.err.InterfaceError:
|
if link_selected is None:
|
||||||
db = pymysql.connect(
|
break
|
||||||
host=DB["host"],
|
|
||||||
user=DB["user"],
|
async with db_pool.acquire() as conn:
|
||||||
password=DB["password"],
|
async with conn.cursor() as cursor:
|
||||||
database=DB["database"]
|
await cursor.execute("INSERT INTO `links` VALUES (%s, %s, %s)", (link_id_converted, link, validity))
|
||||||
)
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('SELECT link FROM links WHERE id=%s', link_id_converted)
|
|
||||||
db.commit()
|
|
||||||
link_selected = cursor.fetchone()
|
|
||||||
if link_selected is None:
|
|
||||||
break
|
|
||||||
# Insert
|
|
||||||
cursor.execute("INSERT INTO `links` VALUES (%s, %s, %s)", [link_id_converted, link, validity])
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
return json.dumps({"ok": True, "message": "successful", "id": link_id_random})
|
return json.dumps({"ok": True, "message": "successful", "id": link_id_random})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", methods=["PATCH"])
|
@app.route("/", methods=["PATCH"])
|
||||||
def reload():
|
async def reload():
|
||||||
global config, DB, KEYS, LISTEN, DEBUG
|
global config, DB, KEYS, LISTEN, DEBUG
|
||||||
|
|
||||||
with open("config.json", "r") as fb:
|
with open("config.json", "r") as fb:
|
||||||
@ -140,24 +132,10 @@ def reload():
|
|||||||
|
|
||||||
return json.dumps({"ok": True, "message": "successful"})
|
return json.dumps({"ok": True, "message": "successful"})
|
||||||
|
|
||||||
|
async def remove_link(id):
|
||||||
def remove_link(id):
|
async with db_pool.acquire() as conn:
|
||||||
global db
|
async with conn.cursor() as cursor:
|
||||||
# Get Cursor
|
await cursor.execute('DELETE FROM links WHERE id=%s', (id,))
|
||||||
try:
|
|
||||||
db.ping()
|
|
||||||
except pymysql.err.InterfaceError:
|
|
||||||
db = pymysql.connect(
|
|
||||||
host=DB["host"],
|
|
||||||
user=DB["user"],
|
|
||||||
password=DB["password"],
|
|
||||||
database=DB["database"]
|
|
||||||
)
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('DELETE FROM links WHERE id=%s', id)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(LISTEN[0], LISTEN[1], DEBUG)
|
app.run(host=LISTEN[0], port=LISTEN[1], debug=DEBUG)
|
||||||
db.close()
|
|
||||||
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
aiomysql
|
||||||
|
aiofiles
|
||||||
|
quart
|
Loading…
Reference in New Issue
Block a user