Migrating the project to an asynchronous model

This commit is contained in:
Bigsk 2024-09-26 18:45:19 +08:00
parent eeff5c307c
commit 463b2bcd8c
5 changed files with 228 additions and 427 deletions

248
404.html
View File

@ -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>

View File

@ -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!

File diff suppressed because one or more lines are too long

168
main.py
View File

@ -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
View File

@ -0,0 +1,3 @@
aiomysql
aiofiles
quart