mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-13 03:27:26 +08:00
[Enhance][Bug fix] Add log db & log bug
- Add a log database to record all playing scores - Fix a bug that if an exception is raised before flask app runs, logger will not work well.
This commit is contained in:
@@ -77,6 +77,7 @@ class Config:
|
|||||||
SQLITE_DATABASE_PATH = './database/arcaea_database.db'
|
SQLITE_DATABASE_PATH = './database/arcaea_database.db'
|
||||||
SQLITE_DATABASE_BACKUP_FOLDER_PATH = './database/backup/'
|
SQLITE_DATABASE_BACKUP_FOLDER_PATH = './database/backup/'
|
||||||
DATABASE_INIT_PATH = './database/init/'
|
DATABASE_INIT_PATH = './database/init/'
|
||||||
|
SQLITE_LOG_DATABASE_PATH = './database/arcaea_log.db'
|
||||||
|
|
||||||
GAME_LOGIN_RATE_LIMIT = '30/5 minutes'
|
GAME_LOGIN_RATE_LIMIT = '30/5 minutes'
|
||||||
API_LOGIN_RATE_LIMIT = '10/5 minutes'
|
API_LOGIN_RATE_LIMIT = '10/5 minutes'
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class Constant:
|
|||||||
SONG_FILE_FOLDER_PATH = Config.SONG_FILE_FOLDER_PATH
|
SONG_FILE_FOLDER_PATH = Config.SONG_FILE_FOLDER_PATH
|
||||||
SONGLIST_FILE_PATH = Config.SONGLIST_FILE_PATH
|
SONGLIST_FILE_PATH = Config.SONGLIST_FILE_PATH
|
||||||
SQLITE_DATABASE_PATH = Config.SQLITE_DATABASE_PATH
|
SQLITE_DATABASE_PATH = Config.SQLITE_DATABASE_PATH
|
||||||
|
SQLITE_LOG_DATABASE_PATH = Config.SQLITE_LOG_DATABASE_PATH
|
||||||
|
|
||||||
DOWNLOAD_TIMES_LIMIT = Config.DOWNLOAD_TIMES_LIMIT
|
DOWNLOAD_TIMES_LIMIT = Config.DOWNLOAD_TIMES_LIMIT
|
||||||
DOWNLOAD_TIME_GAP_LIMIT = Config.DOWNLOAD_TIME_GAP_LIMIT
|
DOWNLOAD_TIME_GAP_LIMIT = Config.DOWNLOAD_TIME_GAP_LIMIT
|
||||||
|
|||||||
@@ -157,35 +157,70 @@ class DatabaseInit:
|
|||||||
self.admin_init()
|
self.admin_init()
|
||||||
|
|
||||||
|
|
||||||
|
class LogDatabaseInit:
|
||||||
|
def __init__(self, db_path: str = Config.SQLITE_LOG_DATABASE_PATH, init_folder_path: str = Config.DATABASE_INIT_PATH) -> None:
|
||||||
|
self.db_path = db_path
|
||||||
|
self.init_folder_path = init_folder_path
|
||||||
|
self.c = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sql_path(self) -> str:
|
||||||
|
return os.path.join(self.init_folder_path, 'log_tables.sql')
|
||||||
|
|
||||||
|
def table_init(self) -> None:
|
||||||
|
'''初始化数据库结构'''
|
||||||
|
with open(self.sql_path, 'r') as f:
|
||||||
|
self.c.executescript(f.read())
|
||||||
|
self.c.execute(
|
||||||
|
'''insert into cache values("version", :a, -1);''', {'a': ARCAEA_SERVER_VERSION})
|
||||||
|
|
||||||
|
def init(self) -> None:
|
||||||
|
with Connect(self.db_path) as c:
|
||||||
|
self.c = c
|
||||||
|
self.table_init()
|
||||||
|
|
||||||
|
|
||||||
class FileChecker:
|
class FileChecker:
|
||||||
|
|
||||||
def __init__(self, app=None):
|
def __init__(self, logger=None):
|
||||||
self.app = app
|
self.logger = logger
|
||||||
|
|
||||||
def check_file(self, file_path: str) -> bool:
|
def check_file(self, file_path: str) -> bool:
|
||||||
f = os.path.isfile(file_path)
|
f = os.path.isfile(file_path)
|
||||||
if not f:
|
if not f:
|
||||||
self.app.logger.warning('File `%s` is missing.' % file_path)
|
self.logger.warning('File `%s` is missing.' % file_path)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def check_folder(self, folder_path: str) -> bool:
|
def check_folder(self, folder_path: str) -> bool:
|
||||||
f = os.path.isdir(folder_path)
|
f = os.path.isdir(folder_path)
|
||||||
if not f:
|
if not f:
|
||||||
self.app.logger.warning('Folder `%s` is missing.' % folder_path)
|
self.logger.warning('Folder `%s` is missing.' % folder_path)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def check_update_database(self) -> bool:
|
def check_update_database(self) -> bool:
|
||||||
|
if not self.check_file(Config.SQLITE_LOG_DATABASE_PATH):
|
||||||
|
# 新建日志数据库
|
||||||
|
try:
|
||||||
|
self.logger.info(
|
||||||
|
f'Try to new the file {Config.SQLITE_LOG_DATABASE_PATH}')
|
||||||
|
LogDatabaseInit().init()
|
||||||
|
self.logger.info(
|
||||||
|
f'Success to new the file {Config.SQLITE_LOG_DATABASE_PATH}')
|
||||||
|
except:
|
||||||
|
self.logger.error(
|
||||||
|
f'Failed to new the file {Config.SQLITE_LOG_DATABASE_PATH}')
|
||||||
|
return False
|
||||||
if not self.check_file(Config.SQLITE_DATABASE_PATH):
|
if not self.check_file(Config.SQLITE_DATABASE_PATH):
|
||||||
# 新建数据库
|
# 新建数据库
|
||||||
try:
|
try:
|
||||||
self.app.logger.info(
|
self.logger.info(
|
||||||
'Try to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
'Try to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
||||||
DatabaseInit().init()
|
DatabaseInit().init()
|
||||||
self.app.logger.info(
|
self.logger.info(
|
||||||
'Success to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
'Success to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
||||||
except:
|
except:
|
||||||
self.app.logger.warning(
|
self.logger.warning(
|
||||||
'Fail to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
'Failed to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# 检查更新
|
# 检查更新
|
||||||
@@ -197,10 +232,10 @@ class FileChecker:
|
|||||||
x = None
|
x = None
|
||||||
# 数据库自动更新,不强求
|
# 数据库自动更新,不强求
|
||||||
if not x or x[0] != ARCAEA_SERVER_VERSION:
|
if not x or x[0] != ARCAEA_SERVER_VERSION:
|
||||||
self.app.logger.warning(
|
self.logger.warning(
|
||||||
'Maybe the file `%s` is an old version.' % Config.SQLITE_DATABASE_PATH)
|
'Maybe the file `%s` is an old version.' % Config.SQLITE_DATABASE_PATH)
|
||||||
try:
|
try:
|
||||||
self.app.logger.info(
|
self.logger.info(
|
||||||
'Try to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
'Try to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
||||||
|
|
||||||
if not os.path.isdir(Config.SQLITE_DATABASE_BACKUP_FOLDER_PATH):
|
if not os.path.isdir(Config.SQLITE_DATABASE_BACKUP_FOLDER_PATH):
|
||||||
@@ -224,11 +259,11 @@ class FileChecker:
|
|||||||
DatabaseInit().init()
|
DatabaseInit().init()
|
||||||
self.update_database(temp_path)
|
self.update_database(temp_path)
|
||||||
|
|
||||||
self.app.logger.info(
|
self.logger.info(
|
||||||
'Success to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
'Success to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.app.logger.warning(
|
self.logger.warning(
|
||||||
'Fail to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
'Fail to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class RefreshAllScoreRating(BaseOperation):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# 追求效率,不用Song类,尽量不用对象
|
# 追求效率,不用Song类,尽量不用对象
|
||||||
|
# 但其实还是很慢
|
||||||
with Connect() as c:
|
with Connect() as c:
|
||||||
c.execute(
|
c.execute(
|
||||||
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
|
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from .course import CoursePlay
|
|||||||
from .error import NoData, StaminaNotEnough
|
from .error import NoData, StaminaNotEnough
|
||||||
from .item import ItemCore
|
from .item import ItemCore
|
||||||
from .song import Chart
|
from .song import Chart
|
||||||
from .sql import Query, Sql
|
from .sql import Connect, Query, Sql
|
||||||
from .util import md5
|
from .util import md5
|
||||||
from .world import WorldPlay
|
from .world import WorldPlay
|
||||||
|
|
||||||
@@ -408,6 +408,12 @@ class UserPlay(UserScore):
|
|||||||
|
|
||||||
self.ptt.insert_recent_30()
|
self.ptt.insert_recent_30()
|
||||||
|
|
||||||
|
def record_score(self) -> None:
|
||||||
|
'''向log数据库记录分数,请注意列名不同'''
|
||||||
|
with Connect(Constant.SQLITE_LOG_DATABASE_PATH) as c2:
|
||||||
|
c2.execute('''insert into user_score values(?,?,?,?,?,?,?,?,?,?,?,?,?)''', (self.user.user_id, self.song.song_id, self.song.difficulty, self.time_played,
|
||||||
|
self.score, self.shiny_perfect_count, self.perfect_count, self.near_count, self.miss_count, self.health, self.modifier, self.clear_type, self.rating))
|
||||||
|
|
||||||
def upload_score(self) -> None:
|
def upload_score(self) -> None:
|
||||||
'''上传分数,包括user的recent更新,best更新,recent30更新,世界模式计算'''
|
'''上传分数,包括user的recent更新,best更新,recent30更新,世界模式计算'''
|
||||||
self.get_play_state()
|
self.get_play_state()
|
||||||
@@ -420,6 +426,9 @@ class UserPlay(UserScore):
|
|||||||
|
|
||||||
self.time_played = int(time())
|
self.time_played = int(time())
|
||||||
|
|
||||||
|
# 记录分数
|
||||||
|
self.record_score()
|
||||||
|
|
||||||
# recent更新
|
# recent更新
|
||||||
self.c.execute('''update user set song_id = :b, difficulty = :c, score = :d, shiny_perfect_count = :e, perfect_count = :f, near_count = :g, miss_count = :h, health = :i, modifier = :j, clear_type = :k, rating = :l, time_played = :m where user_id = :a''', {
|
self.c.execute('''update user set song_id = :b, difficulty = :c, score = :d, shiny_perfect_count = :e, perfect_count = :f, near_count = :g, miss_count = :h, health = :i, modifier = :j, clear_type = :k, rating = :l, time_played = :m where user_id = :a''', {
|
||||||
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.score, 'e': self.shiny_perfect_count, 'f': self.perfect_count, 'g': self.near_count, 'h': self.miss_count, 'i': self.health, 'j': self.modifier, 'k': self.clear_type, 'l': self.rating, 'm': self.time_played * 1000})
|
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.score, 'e': self.shiny_perfect_count, 'f': self.perfect_count, 'g': self.near_count, 'h': self.miss_count, 'i': self.health, 'j': self.modifier, 'k': self.clear_type, 'l': self.rating, 'm': self.time_played * 1000})
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ import sqlite3
|
|||||||
import traceback
|
import traceback
|
||||||
from atexit import register
|
from atexit import register
|
||||||
|
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from .constant import Constant
|
from .constant import Constant
|
||||||
from .error import ArcError, InputError
|
from .error import ArcError, InputError
|
||||||
|
|
||||||
|
|
||||||
class Connect:
|
class Connect:
|
||||||
# 数据库连接类,上下文管理
|
# 数据库连接类,上下文管理
|
||||||
|
logger = None
|
||||||
|
|
||||||
def __init__(self, file_path: str = Constant.SQLITE_DATABASE_PATH, in_memory: bool = False):
|
def __init__(self, file_path: str = Constant.SQLITE_DATABASE_PATH, in_memory: bool = False, logger=None) -> None:
|
||||||
"""
|
"""
|
||||||
数据库连接,默认连接arcaea_database.db\
|
数据库连接,默认连接arcaea_database.db\
|
||||||
接受:文件路径\
|
接受:文件路径\
|
||||||
@@ -19,6 +18,8 @@ class Connect:
|
|||||||
"""
|
"""
|
||||||
self.file_path = file_path
|
self.file_path = file_path
|
||||||
self.in_memory: bool = in_memory
|
self.in_memory: bool = in_memory
|
||||||
|
if logger is not None:
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
def __enter__(self) -> sqlite3.Cursor:
|
def __enter__(self) -> sqlite3.Cursor:
|
||||||
if self.in_memory:
|
if self.in_memory:
|
||||||
@@ -37,7 +38,7 @@ class Connect:
|
|||||||
else:
|
else:
|
||||||
self.conn.rollback()
|
self.conn.rollback()
|
||||||
|
|
||||||
current_app.logger.error(
|
self.logger.error(
|
||||||
traceback.format_exception(exc_type, exc_val, exc_tb))
|
traceback.format_exception(exc_type, exc_val, exc_tb))
|
||||||
|
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|||||||
25
latest version/database/init/log_tables.sql
Normal file
25
latest version/database/init/log_tables.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
create table if not exists cache(key text primary key,
|
||||||
|
value text,
|
||||||
|
expire_time int
|
||||||
|
);
|
||||||
|
create table if not exists user_score(user_id int,
|
||||||
|
song_id text,
|
||||||
|
difficulty int,
|
||||||
|
time_played int,
|
||||||
|
score int,
|
||||||
|
shiny_perfect_count int,
|
||||||
|
perfect_count int,
|
||||||
|
near_count int,
|
||||||
|
miss_count int,
|
||||||
|
health int,
|
||||||
|
modifier int,
|
||||||
|
clear_type int,
|
||||||
|
rating real,
|
||||||
|
primary key(user_id, song_id, difficulty, time_played)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists user_score_1 on user_score (song_id, difficulty);
|
||||||
|
create index if not exists user_score_2 on user_score (time_played);
|
||||||
|
|
||||||
|
PRAGMA journal_mode = WAL;
|
||||||
|
PRAGMA default_cache_size = 4000;
|
||||||
@@ -182,7 +182,8 @@ def main():
|
|||||||
|
|
||||||
dictConfig(log_dict)
|
dictConfig(log_dict)
|
||||||
|
|
||||||
if not FileChecker(app).check_before_run():
|
Connect.logger = app.logger
|
||||||
|
if not FileChecker(app.logger).check_before_run():
|
||||||
app.logger.error('Something wrong. The server will not run.')
|
app.logger.error('Something wrong. The server will not run.')
|
||||||
input('Press ENTER key to exit.')
|
input('Press ENTER key to exit.')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|||||||
Reference in New Issue
Block a user