mirror of
https://github.com/qwerfd2/Groove_Coaster_2_Server.git
synced 2025-12-22 03:30:18 +00:00
Redis support for linux
This commit is contained in:
3
7002.py
3
7002.py
@@ -15,7 +15,7 @@ from api.ranking import routes as rank_routes
|
||||
from api.shop import routes as shop_routes
|
||||
from api.play import routes as play_routes
|
||||
|
||||
from config import HOST, PORT, DEBUG, SSL_CERT, SSL_KEY, ROOT_FOLDER, ACTUAL_HOST, ACTUAL_PORT
|
||||
from config import DEBUG, SSL_CERT, SSL_KEY, ROOT_FOLDER, ACTUAL_HOST, ACTUAL_PORT
|
||||
|
||||
if (os.path.isfile('./files/dlc_4max.html')):
|
||||
get_4max_version_string()
|
||||
@@ -42,6 +42,7 @@ app = Starlette(debug=DEBUG, routes=routes)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
global redis
|
||||
await database.connect()
|
||||
await init_db()
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.requests import Request
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import Table, Column, Integer, String, DateTime, ForeignKey
|
||||
from sqlalchemy import Table, Column, Integer, String, DateTime
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy import select, update
|
||||
|
||||
from config import REDIS_ADDRESS, USE_REDIS_CACHE
|
||||
|
||||
import os
|
||||
import databases
|
||||
import datetime
|
||||
if USE_REDIS_CACHE:
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
redis = None
|
||||
|
||||
DB_NAME = "player.db"
|
||||
DB_PATH = os.path.join(os.getcwd(), DB_NAME)
|
||||
@@ -81,7 +84,7 @@ blacklist = Table(
|
||||
)
|
||||
|
||||
async def init_db():
|
||||
|
||||
global redis
|
||||
if not os.path.exists(DB_PATH):
|
||||
print("[DB] Creating new database:", DB_PATH)
|
||||
|
||||
@@ -93,6 +96,9 @@ async def init_db():
|
||||
await engine.dispose()
|
||||
print("[DB] Database initialized successfully.")
|
||||
await ensure_user_columns()
|
||||
if USE_REDIS_CACHE:
|
||||
print("[DB] Connecting to Redis at", REDIS_ADDRESS)
|
||||
redis = await aioredis.from_url("redis://" + REDIS_ADDRESS)
|
||||
|
||||
async def get_user_data(uid, data_field):
|
||||
query = select(user.c[data_field]).where(user.c.device_id == uid[b'vid'][0].decode())
|
||||
|
||||
145
api/ranking.py
145
api/ranking.py
@@ -2,11 +2,12 @@ from starlette.responses import HTMLResponse
|
||||
from starlette.requests import Request
|
||||
from starlette.routing import Route
|
||||
import os
|
||||
import json
|
||||
from sqlalchemy import select, update
|
||||
|
||||
from config import AUTHORIZATION_NEEDED
|
||||
from config import AUTHORIZATION_NEEDED, USE_REDIS_CACHE
|
||||
|
||||
from api.database import database, user, result, daily_reward, check_blacklist, check_whitelist
|
||||
import api.database
|
||||
from api.crypt import decrypt_fields, encryptAES
|
||||
from api.templates import EXP_UNLOCKED_SONGS, TITLE_LISTS, SONG_LIST
|
||||
from api.misc import inform_page
|
||||
@@ -18,7 +19,7 @@ async def ranking(request: Request):
|
||||
|
||||
should_serve = True
|
||||
if AUTHORIZATION_NEEDED:
|
||||
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
|
||||
should_serve = await api.database.check_whitelist(decrypted_fields) and not await api.database.check_blacklist(decrypted_fields)
|
||||
|
||||
if should_serve:
|
||||
device_id = decrypted_fields[b'vid'][0].decode()
|
||||
@@ -60,7 +61,7 @@ async def ranking_detail(request: Request):
|
||||
|
||||
should_serve = True
|
||||
if AUTHORIZATION_NEEDED:
|
||||
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
|
||||
should_serve = await api.database.check_whitelist(decrypted_fields) and not await api.database.check_blacklist(decrypted_fields)
|
||||
|
||||
if should_serve:
|
||||
device_id = decrypted_fields[b'vid'][0].decode()
|
||||
@@ -108,6 +109,11 @@ async def ranking_detail(request: Request):
|
||||
play_results = None
|
||||
user_result = None
|
||||
device_result = None
|
||||
if USE_REDIS_CACHE:
|
||||
cache_key = f"{song_id}-{mode}"
|
||||
cached = await api.database.redis.get(cache_key)
|
||||
else:
|
||||
cached = False
|
||||
|
||||
if (song_id == -1):
|
||||
# Filter out the mobile/AC modes
|
||||
@@ -118,50 +124,57 @@ async def ranking_detail(request: Request):
|
||||
else:
|
||||
exclude = [1, 2, 3]
|
||||
|
||||
query = select(result.c.vid, result.c.sid, result.c.mode, result.c.avatar, result.c.score)
|
||||
play_results = await database.fetch_all(query)
|
||||
|
||||
query = select(daily_reward.c.device_id, daily_reward.c.title, daily_reward.c.avatar)
|
||||
device_results_raw = await database.fetch_all(query)
|
||||
device_results = {row["device_id"]: {"title": row["title"], "avatar": row["avatar"]} for row in device_results_raw}
|
||||
|
||||
query = select(user.c.id, user.c.username, user.c.device_id)
|
||||
user_results_raw = await database.fetch_all(query)
|
||||
user_results = {row["id"]: {"username": row["username"], "device_id": row["device_id"]} for row in user_results_raw}
|
||||
|
||||
query = select(user).where(user.c.device_id == device_id)
|
||||
cur_user = await database.fetch_one(query)
|
||||
if cached and USE_REDIS_CACHE:
|
||||
sorted_players = json.loads(cached)
|
||||
|
||||
player_scores = {}
|
||||
else:
|
||||
query = select(api.database.result.c.vid, api.database.result.c.sid, api.database.result.c.mode, api.database.result.c.avatar, api.database.result.c.score)
|
||||
play_results = await api.database.database.fetch_all(query)
|
||||
|
||||
filtered_play_results = [play for play in play_results if int(play[2]) not in exclude]
|
||||
query = select(api.database.daily_reward.c.device_id, api.database.daily_reward.c.title, api.database.daily_reward.c.avatar)
|
||||
device_results_raw = await api.database.database.fetch_all(query)
|
||||
device_results = {row["device_id"]: {"title": row["title"], "avatar": row["avatar"]} for row in device_results_raw}
|
||||
|
||||
for play in filtered_play_results:
|
||||
did = play[0]
|
||||
sid = play[1]
|
||||
avatar = play[3]
|
||||
score = play[4]
|
||||
username, title = None, None
|
||||
query = select(api.database.user.c.id, api.database.user.c.username, api.database.user.c.device_id)
|
||||
user_results_raw = await api.database.database.fetch_all(query)
|
||||
user_results = {row["id"]: {"username": row["username"], "device_id": row["device_id"]} for row in user_results_raw}
|
||||
|
||||
query = select(api.database.user).where(api.database.user.c.device_id == device_id)
|
||||
cur_user = await api.database.database.fetch_one(query)
|
||||
|
||||
if sid:
|
||||
sid = int(sid)
|
||||
if sid in user_results:
|
||||
username = user_results[sid]["username"]
|
||||
did = user_results[sid]["device_id"]
|
||||
else: # Guest
|
||||
username = f"Guest({did[-6:]})"
|
||||
player_scores = {}
|
||||
|
||||
# title is device-specific
|
||||
title = device_results.get(did, {}).get("title", "1")
|
||||
filtered_play_results = [play for play in play_results if int(play[2]) not in exclude]
|
||||
|
||||
if username in player_scores:
|
||||
player_scores[username]["score"] += int(score)
|
||||
player_scores[username]["avatar"] = avatar # But avatar is based on latest play submission
|
||||
player_scores[username]["title"] = title
|
||||
else:
|
||||
player_scores[username] = {"score": int(score), "avatar": avatar, "title": title}
|
||||
for play in filtered_play_results:
|
||||
did = play[0]
|
||||
sid = play[1]
|
||||
avatar = play[3]
|
||||
score = play[4]
|
||||
username, title = None, None
|
||||
|
||||
sorted_players = sorted(player_scores.items(), key=lambda x: x[1]["score"], reverse=True)
|
||||
if sid:
|
||||
sid = int(sid)
|
||||
if sid in user_results:
|
||||
username = user_results[sid]["username"]
|
||||
did = user_results[sid]["device_id"]
|
||||
else: # Guest
|
||||
username = f"Guest({did[-6:]})"
|
||||
|
||||
# title is device-specific
|
||||
title = device_results.get(did, {}).get("title", "1")
|
||||
|
||||
if username in player_scores:
|
||||
player_scores[username]["score"] += int(score)
|
||||
player_scores[username]["avatar"] = avatar # But avatar is based on latest play submission
|
||||
player_scores[username]["title"] = title
|
||||
else:
|
||||
player_scores[username] = {"score": int(score), "avatar": avatar, "title": title}
|
||||
|
||||
sorted_players = sorted(player_scores.items(), key=lambda x: x[1]["score"], reverse=True)
|
||||
if USE_REDIS_CACHE:
|
||||
await api.database.redis.set(cache_key, json.dumps(sorted_players), ex=300)
|
||||
|
||||
username = cur_user[1] if cur_user else f"Guest({device_id[-6:]})"
|
||||
|
||||
@@ -214,15 +227,21 @@ async def ranking_detail(request: Request):
|
||||
"""
|
||||
|
||||
else:
|
||||
query = select(result).where((result.c.id == song_id) & (result.c.mode == mode))
|
||||
play_results = await database.fetch_all(query)
|
||||
play_results = sorted(play_results, key=lambda x: int(x[8]), reverse=True)
|
||||
if cached and USE_REDIS_CACHE:
|
||||
play_results = json.loads(cached)
|
||||
|
||||
query = select(user).where(user.c.device_id == device_id)
|
||||
user_result = await database.fetch_one(query)
|
||||
else:
|
||||
query = select(api.database.result).where((api.database.result.c.id == song_id) & (api.database.result.c.mode == mode))
|
||||
play_results = await api.database.database.fetch_all(query)
|
||||
play_results = sorted(play_results, key=lambda x: int(x[8]), reverse=True)
|
||||
if USE_REDIS_CACHE:
|
||||
await api.database.redis.set(cache_key, json.dumps(play_results), ex=300)
|
||||
|
||||
query = select(daily_reward).where(daily_reward.c.device_id == device_id)
|
||||
device_result = await database.fetch_one(query)
|
||||
query = select(api.database.user).where(api.database.user.c.device_id == device_id)
|
||||
user_result = await api.database.database.fetch_one(query)
|
||||
|
||||
query = select(api.database.daily_reward).where(api.database.daily_reward.c.device_id == device_id)
|
||||
device_result = await api.database.database.fetch_one(query)
|
||||
|
||||
user_id = user_result[0] if user_result else None
|
||||
username = user_result[1] if user_result else f"Guest({device_id[-6:]})"
|
||||
@@ -265,13 +284,13 @@ async def ranking_detail(request: Request):
|
||||
username = f"Guest({record[1][-6:]})"
|
||||
device_info = None
|
||||
if record[3]:
|
||||
query = select(user.c.username).where(user.c.id == record[3])
|
||||
user_data = await database.fetch_one(query)
|
||||
query = select(api.database.user.c.username).where(api.database.user.c.id == record[3])
|
||||
user_data = await api.database.database.fetch_one(query)
|
||||
if user_data:
|
||||
username = user_data["username"]
|
||||
|
||||
query = select(daily_reward.c.title).where(daily_reward.c.device_id == record[1])
|
||||
device_title = await database.fetch_one(query)
|
||||
query = select(api.database.daily_reward.c.title).where(api.database.daily_reward.c.device_id == record[1])
|
||||
device_title = await api.database.database.fetch_one(query)
|
||||
if device_title:
|
||||
device_info = device_title["title"]
|
||||
else:
|
||||
@@ -321,7 +340,7 @@ async def status(request: Request):
|
||||
|
||||
should_serve = True
|
||||
if AUTHORIZATION_NEEDED:
|
||||
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
|
||||
should_serve = await api.database.check_whitelist(decrypted_fields) and not await api.database.check_blacklist(decrypted_fields)
|
||||
|
||||
if should_serve:
|
||||
device_id = decrypted_fields[b'vid'][0].decode()
|
||||
@@ -330,19 +349,19 @@ async def status(request: Request):
|
||||
|
||||
if set_title:
|
||||
update_query = (
|
||||
update(daily_reward)
|
||||
.where(daily_reward.c.device_id == device_id)
|
||||
update(api.database.daily_reward)
|
||||
.where(api.database.daily_reward.c.device_id == device_id)
|
||||
.values(title=set_title)
|
||||
)
|
||||
await database.execute(update_query)
|
||||
await api.database.execute(update_query)
|
||||
|
||||
query = select(daily_reward).where(daily_reward.c.device_id == device_id)
|
||||
user_data = await database.fetch_one(query)
|
||||
query = select(api.database.daily_reward).where(api.database.daily_reward.c.device_id == device_id)
|
||||
user_data = await api.database.database.fetch_one(query)
|
||||
|
||||
user_name = f"Guest({device_id[-6:]})"
|
||||
if user_data:
|
||||
query = select(user.c.username).where(user.c.device_id == device_id)
|
||||
user_result = await database.fetch_one(query)
|
||||
query = select(api.database.user.c.username).where(api.database.user.c.device_id == device_id)
|
||||
user_result = await api.database.database.fetch_one(query)
|
||||
if user_result:
|
||||
user_name = user_result["username"]
|
||||
|
||||
@@ -416,7 +435,7 @@ async def set_title(request: Request):
|
||||
|
||||
should_serve = True
|
||||
if AUTHORIZATION_NEEDED:
|
||||
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
|
||||
should_serve = await api.database.check_whitelist(decrypted_fields) and not await api.database.check_blacklist(decrypted_fields)
|
||||
|
||||
if should_serve:
|
||||
device_id = decrypted_fields[b'vid'][0].decode()
|
||||
@@ -424,8 +443,8 @@ async def set_title(request: Request):
|
||||
title_id = decrypted_fields[b'title_id'][0].decode()
|
||||
current_title = 1
|
||||
|
||||
query = select(daily_reward.c.title).where(daily_reward.c.device_id == device_id)
|
||||
row = await database.fetch_one(query)
|
||||
query = select(api.database.daily_reward.c.title).where(api.database.daily_reward.c.device_id == device_id)
|
||||
row = await api.database.database.fetch_one(query)
|
||||
if row:
|
||||
current_title = row["title"]
|
||||
|
||||
@@ -458,7 +477,7 @@ async def mission(request: Request):
|
||||
|
||||
should_serve = True
|
||||
if AUTHORIZATION_NEEDED:
|
||||
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
|
||||
should_serve = await api.database.check_whitelist(decrypted_fields) and not await api.database.check_blacklist(decrypted_fields)
|
||||
|
||||
if should_serve:
|
||||
html = f"""<div class="f90 a_center pt50">Play Music to level up and unlock free songs!<br>Songs can only be unlocked when you play online.</div><div class='mission-list'>"""
|
||||
|
||||
@@ -279,7 +279,7 @@ async def load(request: Request):
|
||||
async def save(request: Request):
|
||||
decrypted_fields, _ = await decrypt_fields(request)
|
||||
if not decrypted_fields:
|
||||
return Response("""<response><code>10</code></response>""", media_type="application/xml")
|
||||
return Response("""<response><code>10</code><message><ja>この機能を使用するには、まずアカウントを登録する必要があります。</ja><en>You need to register an account first before this feature can be used.</en><fr>Vous devez d'abord créer un compte avant de pouvoir utiliser cette fonctionnalité.</fr><it>È necessario registrare un account prima di poter utilizzare questa funzione.</it></message></response>""", media_type="application/xml")
|
||||
|
||||
data = await request.body()
|
||||
data = data.decode("utf-8")
|
||||
@@ -306,7 +306,7 @@ async def save(request: Request):
|
||||
|
||||
return Response("""<response><code>0</code></response>""", media_type="application/xml")
|
||||
else:
|
||||
return Response("""<response><code>10</code></response>""", media_type="application/xml")
|
||||
return Response("""<response><code>10</code><message><ja>この機能を使用するには、まずアカウントを登録する必要があります。</ja><en>You need to register an account first before this feature can be used.</en><fr>Vous devez d'abord créer un compte avant de pouvoir utiliser cette fonctionnalité.</fr><it>È necessario registrare un account prima di poter utilizzare questa funzione.</it></message></response>""", media_type="application/xml")
|
||||
|
||||
async def start(request: Request):
|
||||
decrypted_fields, _ = await decrypt_fields(request)
|
||||
|
||||
18
config.env
18
config.env
@@ -1,18 +0,0 @@
|
||||
HOST = 192.168.0.106
|
||||
PORT = 9068
|
||||
ACTUAL_HOST = 192.168.0.106
|
||||
ACTUAL_PORT = 9068
|
||||
|
||||
MODEL = 202504125800
|
||||
TUNEFILE = 202504125800
|
||||
SKIN = 202404191149
|
||||
|
||||
STAGE_PRICE = 1
|
||||
AVATAR_PRICE = 1
|
||||
ITEM_PRICE = 2
|
||||
COIN_REWARD = 1
|
||||
START_COIN = 10
|
||||
|
||||
AUTHORIZATION_NEEDED = False
|
||||
|
||||
DEBUG = True
|
||||
53
config.py
53
config.py
@@ -1,63 +1,66 @@
|
||||
from starlette.config import Config
|
||||
import os
|
||||
|
||||
'''
|
||||
Do not change the name of this file.
|
||||
不要改动这个文件的名称。
|
||||
'''
|
||||
|
||||
config = Config("config.env")
|
||||
|
||||
'''
|
||||
IP and port of the server.
|
||||
服务器的IP和端口。
|
||||
'''
|
||||
|
||||
HOST = config("HOST", default="192.168.0.106")
|
||||
PORT = int(config("PORT", default=9070))
|
||||
HOST = "192.168.0.106"
|
||||
PORT = 9070
|
||||
|
||||
ACTUAL_HOST = config("ACTUAL_HOST", default="192.168.0.106")
|
||||
ACTUAL_PORT = int(config("ACTUAL_PORT", default=9070))
|
||||
ACTUAL_HOST = "192.168.0.106"
|
||||
ACTUAL_PORT = 9068
|
||||
|
||||
'''
|
||||
Redis leaderboard caching configuration.
|
||||
Redis排行榜缓存设置。
|
||||
'''
|
||||
|
||||
REDIS_ADDRESS = "localhost"
|
||||
USE_REDIS_CACHE = False
|
||||
|
||||
'''
|
||||
Datecode of the 3 pak files.
|
||||
三个pak文件的时间戳。
|
||||
'''
|
||||
|
||||
MODEL = config("MODEL", cast=str, default="202504125800")
|
||||
TUNEFILE = config("TUNEFILE", cast=str, default="202504125800")
|
||||
SKIN = config("SKIN", cast=str, default="202404191149")
|
||||
MODEL = "202504125800"
|
||||
TUNEFILE = "202504125800"
|
||||
SKIN = "202404191149"
|
||||
|
||||
'''
|
||||
Groove Coin-related settings.
|
||||
GCoin相关设定。
|
||||
'''
|
||||
|
||||
STAGE_PRICE = config("STAGE_PRICE", cast=int, default=1)
|
||||
AVATAR_PRICE = config("AVATAR_PRICE", cast=int, default=1)
|
||||
ITEM_PRICE = config("ITEM_PRICE", cast=int, default=2)
|
||||
COIN_REWARD = config("COIN_REWARD", cast=int, default=1)
|
||||
START_COIN = config("START_COIN", cast=int, default=10)
|
||||
STAGE_PRICE = 1
|
||||
AVATAR_PRICE = 1
|
||||
ITEM_PRICE = 2
|
||||
COIN_REWARD = 1
|
||||
START_COIN = 10
|
||||
|
||||
'''
|
||||
Only the whitelisted playerID can use the service. Blacklist has priority over whitelist.
|
||||
只有白名单的玩家ID才能使用服务。黑名单优先于白名单。
|
||||
'''
|
||||
|
||||
AUTHORIZATION_NEEDED = config("AUTHORIZATION_NEEDED", cast=bool, default=False)
|
||||
AUTHORIZATION_NEEDED = False
|
||||
|
||||
'''
|
||||
SSL证书路径 - 留空则使用HTTP
|
||||
SSL certificate path. If left blank, use HTTP.
|
||||
'''
|
||||
|
||||
SSL_CERT = config("SSL_CERT", default=None)
|
||||
SSL_KEY = config("SSL_KEY", default=None)
|
||||
SSL_CERT = None
|
||||
SSL_KEY = None
|
||||
|
||||
'''
|
||||
Flask default debug
|
||||
Flask内置Debug
|
||||
Starlette default debug
|
||||
Starlette内置Debug
|
||||
'''
|
||||
|
||||
DEBUG = config("DEBUG", cast=bool, default=False)
|
||||
DEBUG = False
|
||||
|
||||
ROOT_FOLDER = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_FOLDER = os.path.dirname(os.path.abspath(__file__))
|
||||
Reference in New Issue
Block a user