first commit

This commit is contained in:
lihansani
2026-07-20 13:16:17 +08:00
commit 7affeaa253
235 changed files with 42351 additions and 0 deletions

570
lib/db.py Normal file
View File

@@ -0,0 +1,570 @@
import sqlite3
import os
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
_active_db = None
_mysql_pool = None
def _get_project_root():
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def _get_sqlite_db_path():
from lib.config import get_sqlite_config
cfg = get_sqlite_config()
path = cfg['path']
if not os.path.isabs(path):
path = os.path.join(_get_project_root(), path)
return path
def _try_mysql_connect():
try:
import pymysql
except ImportError:
logger.warning("pymysql 未安装, 无法使用 MySQL")
return None
from lib.config import get_mysql_config
cfg = get_mysql_config()
try:
conn = pymysql.connect(
host=cfg['host'],
port=cfg['port'],
user=cfg['user'],
password=cfg['password'],
database=cfg['database'],
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
connect_timeout=5,
)
return conn
except Exception as e:
logger.warning("MySQL 连接失败: %s", str(e))
return None
def _ensure_mysql_database():
try:
import pymysql
except ImportError:
return False
from lib.config import get_mysql_config
cfg = get_mysql_config()
try:
conn = pymysql.connect(
host=cfg['host'],
port=cfg['port'],
user=cfg['user'],
password=cfg['password'],
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
connect_timeout=5,
)
with conn.cursor() as cursor:
cursor.execute(
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = %s",
(cfg['database'],)
)
if not cursor.fetchone():
cursor.execute(
"CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" % cfg['database']
)
conn.commit()
logger.info("MySQL 数据库 '%s' 创建成功", cfg['database'])
conn.close()
return True
except Exception as e:
logger.warning("MySQL 确保数据库存在失败: %s", str(e))
return False
def _get_mysql_connection():
global _mysql_pool
if _mysql_pool is not None:
try:
_mysql_pool.ping(reconnect=True)
return _mysql_pool
except Exception:
_mysql_pool = None
conn = _try_mysql_connect()
if conn:
_mysql_pool = conn
return conn
def _get_sqlite_connection():
db_path = _get_sqlite_db_path()
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
return conn
def get_connection():
if _active_db == 'mysql':
conn = _get_mysql_connection()
if conn:
return conn
if _active_db == 'sqlite':
return _get_sqlite_connection()
return None
def init_database(strict_mysql=False):
"""初始化数据库。
参数:
- ``strict_mysql``: True 表示严格按 SRS 15.3.4 — 配置了 mysql 但不可用时返回失败,
不静默回退 SQLite。False启动期默认允许回退以便开发模式可用。
"""
global _active_db
from lib.config import get_db_type
db_type = get_db_type()
if db_type == 'mysql':
ok = _ensure_mysql_database()
conn = _try_mysql_connect() if ok else None
if conn:
_active_db = 'mysql'
_init_mysql_tables(conn)
conn.close()
logger.info("数据库初始化完成 (MySQL)")
return True
else:
if strict_mysql:
logger.error("MySQL 不可用且 strict_mysql=True拒绝服务")
return False
logger.warning("MySQL 不可用, 回退到 SQLite")
_active_db = 'sqlite'
elif db_type == 'sqlite':
_active_db = 'sqlite'
else:
conn = _try_mysql_connect()
if conn:
_active_db = 'mysql'
_ensure_mysql_database()
_init_mysql_tables(conn)
conn.close()
logger.info("数据库初始化完成 (MySQL, 自动检测)")
return True
else:
if strict_mysql:
logger.error("MySQL 不可用且 strict_mysql=True拒绝服务")
return False
logger.warning("MySQL 不可用, 回退到 SQLite")
_active_db = 'sqlite'
if _active_db == 'sqlite':
_init_sqlite_tables()
logger.info("数据库初始化完成 (SQLite): %s", _get_sqlite_db_path())
return True
def ensure_mysql_available():
"""SRS 15.3.4API 路径在配置 type=mysql 时必须保证 MySQL 可用。
返回 True 表示可用False 表示应返回 5xx。
"""
from lib.config import get_db_type
db_type = get_db_type()
if db_type == 'sqlite':
return True
global _active_db
if _active_db == 'mysql':
conn = _get_mysql_connection()
return conn is not None
return False
def _init_mysql_tables(conn):
try:
with conn.cursor() as cursor:
cursor.execute("""
CREATE TABLE IF NOT EXISTS user_sign (
user_id VARCHAR(50) PRIMARY KEY,
user_name VARCHAR(100) NOT NULL,
sign_image LONGBLOB NOT NULL,
is_signature TINYINT(1) NOT NULL DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")
conn.commit()
logger.info("MySQL 数据表初始化完成")
except Exception as e:
logger.error("MySQL 数据表初始化失败: %s", str(e))
raise
def _init_sqlite_tables():
conn = _get_sqlite_connection()
try:
conn.execute("""
CREATE TABLE IF NOT EXISTS user_sign (
user_id TEXT PRIMARY KEY,
user_name TEXT NOT NULL,
sign_image BLOB NOT NULL,
is_signature INTEGER NOT NULL DEFAULT 0,
create_time TEXT DEFAULT (datetime('now','localtime')),
update_time TEXT DEFAULT (datetime('now','localtime'))
)
""")
conn.commit()
except Exception as e:
conn.rollback()
logger.error("SQLite 数据表初始化失败: %s", str(e))
raise
finally:
conn.close()
def query_by_user_id(user_id):
if _active_db == 'mysql':
return _mysql_query_by_user_id(user_id)
return _sqlite_query_by_user_id(user_id)
def _mysql_query_by_user_id(user_id):
conn = _get_mysql_connection()
if not conn:
return _sqlite_query_by_user_id(user_id)
try:
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM user_sign WHERE user_id = %s", (user_id,))
row = cursor.fetchone()
return row if row else None
except Exception as e:
logger.error("MySQL 查询失败: %s", str(e))
return _sqlite_query_by_user_id(user_id)
def _sqlite_query_by_user_id(user_id):
conn = _get_sqlite_connection()
try:
cursor = conn.execute("SELECT * FROM user_sign WHERE user_id = ?", (user_id,))
row = cursor.fetchone()
return dict(row) if row else None
finally:
conn.close()
def query_by_user_name(user_name):
if _active_db == 'mysql':
return _mysql_query_by_user_name(user_name)
return _sqlite_query_by_user_name(user_name)
def _mysql_query_by_user_name(user_name):
conn = _get_mysql_connection()
if not conn:
return _sqlite_query_by_user_name(user_name)
try:
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM user_sign WHERE user_name = %s", (user_name,))
return cursor.fetchall()
except Exception as e:
logger.error("MySQL 查询失败: %s", str(e))
return _sqlite_query_by_user_name(user_name)
def _sqlite_query_by_user_name(user_name):
conn = _get_sqlite_connection()
try:
cursor = conn.execute("SELECT * FROM user_sign WHERE user_name = ?", (user_name,))
return [dict(r) for r in cursor.fetchall()]
finally:
conn.close()
def query_all(page=1, page_size=20, keyword=None):
if _active_db == 'mysql':
return _mysql_query_all(page, page_size, keyword)
return _sqlite_query_all(page, page_size, keyword)
def _mysql_query_all(page=1, page_size=20, keyword=None):
conn = _get_mysql_connection()
if not conn:
return _sqlite_query_all(page, page_size, keyword)
try:
offset = (page - 1) * page_size
with conn.cursor() as cursor:
if keyword:
cursor.execute(
"SELECT COUNT(*) as total FROM user_sign WHERE user_id LIKE %s OR user_name LIKE %s",
(f'%{keyword}%', f'%{keyword}%')
)
total = cursor.fetchone()['total']
cursor.execute(
"SELECT user_id, user_name, is_signature, create_time, update_time FROM user_sign WHERE user_id LIKE %s OR user_name LIKE %s ORDER BY create_time DESC LIMIT %s OFFSET %s",
(f'%{keyword}%', f'%{keyword}%', page_size, offset)
)
else:
cursor.execute("SELECT COUNT(*) as total FROM user_sign")
total = cursor.fetchone()['total']
cursor.execute(
"SELECT user_id, user_name, is_signature, create_time, update_time FROM user_sign ORDER BY create_time DESC LIMIT %s OFFSET %s",
(page_size, offset)
)
rows = cursor.fetchall()
return {'total': total, 'page': page, 'page_size': page_size, 'data': rows}
except Exception as e:
logger.error("MySQL 查询失败: %s", str(e))
return _sqlite_query_all(page, page_size, keyword)
def _sqlite_query_all(page=1, page_size=20, keyword=None):
conn = _get_sqlite_connection()
try:
offset = (page - 1) * page_size
if keyword:
count_row = conn.execute(
"SELECT COUNT(*) as total FROM user_sign WHERE user_id LIKE ? OR user_name LIKE ?",
(f'%{keyword}%', f'%{keyword}%')
).fetchone()
total = count_row['total']
cursor = conn.execute(
"SELECT user_id, user_name, is_signature, create_time, update_time FROM user_sign WHERE user_id LIKE ? OR user_name LIKE ? ORDER BY create_time DESC LIMIT ? OFFSET ?",
(f'%{keyword}%', f'%{keyword}%', page_size, offset)
)
else:
count_row = conn.execute("SELECT COUNT(*) as total FROM user_sign").fetchone()
total = count_row['total']
cursor = conn.execute(
"SELECT user_id, user_name, is_signature, create_time, update_time FROM user_sign ORDER BY create_time DESC LIMIT ? OFFSET ?",
(page_size, offset)
)
rows = [dict(r) for r in cursor.fetchall()]
return {'total': total, 'page': page, 'page_size': page_size, 'data': rows}
finally:
conn.close()
def insert_record(user_id, user_name, sign_image, is_signature=0):
if _active_db == 'mysql':
return _mysql_insert_record(user_id, user_name, sign_image, is_signature)
return _sqlite_insert_record(user_id, user_name, sign_image, is_signature)
def _mysql_insert_record(user_id, user_name, sign_image, is_signature=0):
conn = _get_mysql_connection()
if not conn:
return _sqlite_insert_record(user_id, user_name, sign_image, is_signature)
try:
with conn.cursor() as cursor:
cursor.execute(
"INSERT INTO user_sign (user_id, user_name, sign_image, is_signature) VALUES (%s, %s, %s, %s)",
(user_id, user_name, sign_image, is_signature)
)
conn.commit()
logger.info("新增记录(MySQL): user_id=%s, user_name=%s", user_id, user_name)
return True
except Exception as e:
conn.rollback()
if 'Duplicate entry' in str(e) or 'PRIMARY' in str(e):
logger.error("新增记录失败(主键冲突): user_id=%s, %s", user_id, str(e))
raise ValueError(f"人员ID '{user_id}' 已存在")
logger.error("MySQL 新增记录失败: %s", str(e))
raise
except:
conn.rollback()
raise
def _sqlite_insert_record(user_id, user_name, sign_image, is_signature=0):
conn = _get_sqlite_connection()
try:
conn.execute(
"INSERT INTO user_sign (user_id, user_name, sign_image, is_signature) VALUES (?, ?, ?, ?)",
(user_id, user_name, sign_image, is_signature)
)
conn.commit()
logger.info("新增记录(SQLite): user_id=%s, user_name=%s", user_id, user_name)
return True
except sqlite3.IntegrityError as e:
conn.rollback()
logger.error("新增记录失败(主键冲突): user_id=%s, %s", user_id, str(e))
raise ValueError(f"人员ID '{user_id}' 已存在")
except Exception as e:
conn.rollback()
logger.error("新增记录失败: %s", str(e))
raise
finally:
conn.close()
def update_record(user_id, user_name=None, sign_image=None, is_signature=None):
if _active_db == 'mysql':
return _mysql_update_record(user_id, user_name, sign_image, is_signature)
return _sqlite_update_record(user_id, user_name, sign_image, is_signature)
def _mysql_update_record(user_id, user_name=None, sign_image=None, is_signature=None):
conn = _get_mysql_connection()
if not conn:
return _sqlite_update_record(user_id, user_name, sign_image, is_signature)
try:
sets = []
params = []
if user_name is not None:
sets.append("user_name = %s")
params.append(user_name)
if sign_image is not None:
sets.append("sign_image = %s")
params.append(sign_image)
if is_signature is not None:
sets.append("is_signature = %s")
params.append(is_signature)
if not sets:
return True
params.append(user_id)
with conn.cursor() as cursor:
cursor.execute(f"UPDATE user_sign SET {', '.join(sets)} WHERE user_id = %s", params)
conn.commit()
logger.info("更新记录(MySQL): user_id=%s", user_id)
return True
except Exception as e:
conn.rollback()
logger.error("MySQL 更新记录失败: %s", str(e))
raise
def _sqlite_update_record(user_id, user_name=None, sign_image=None, is_signature=None):
conn = _get_sqlite_connection()
try:
sets = []
params = []
if user_name is not None:
sets.append("user_name = ?")
params.append(user_name)
if sign_image is not None:
sets.append("sign_image = ?")
params.append(sign_image)
if is_signature is not None:
sets.append("is_signature = ?")
params.append(is_signature)
if not sets:
return True
sets.append("update_time = datetime('now','localtime')")
params.append(user_id)
conn.execute(f"UPDATE user_sign SET {', '.join(sets)} WHERE user_id = ?", params)
conn.commit()
logger.info("更新记录(SQLite): user_id=%s", user_id)
return True
except Exception as e:
conn.rollback()
logger.error("更新记录失败: %s", str(e))
raise
finally:
conn.close()
def delete_record(user_id):
if _active_db == 'mysql':
return _mysql_delete_record(user_id)
return _sqlite_delete_record(user_id)
def _mysql_delete_record(user_id):
conn = _get_mysql_connection()
if not conn:
return _sqlite_delete_record(user_id)
try:
with conn.cursor() as cursor:
cursor.execute("DELETE FROM user_sign WHERE user_id = %s", (user_id,))
conn.commit()
logger.info("删除记录(MySQL): user_id=%s", user_id)
return True
except Exception as e:
conn.rollback()
logger.error("MySQL 删除记录失败: %s", str(e))
raise
def _sqlite_delete_record(user_id):
conn = _get_sqlite_connection()
try:
conn.execute("DELETE FROM user_sign WHERE user_id = ?", (user_id,))
conn.commit()
logger.info("删除记录(SQLite): user_id=%s", user_id)
return True
except Exception as e:
conn.rollback()
logger.error("删除记录失败: %s", str(e))
raise
finally:
conn.close()
def get_image_by_user_id(user_id):
if _active_db == 'mysql':
return _mysql_get_image_by_user_id(user_id)
return _sqlite_get_image_by_user_id(user_id)
def _mysql_get_image_by_user_id(user_id):
conn = _get_mysql_connection()
if not conn:
return _sqlite_get_image_by_user_id(user_id)
try:
with conn.cursor() as cursor:
cursor.execute("SELECT sign_image, is_signature FROM user_sign WHERE user_id = %s", (user_id,))
row = cursor.fetchone()
return row if row else None
except Exception as e:
logger.error("MySQL 查询图片失败: %s", str(e))
return _sqlite_get_image_by_user_id(user_id)
def _sqlite_get_image_by_user_id(user_id):
conn = _get_sqlite_connection()
try:
cursor = conn.execute("SELECT sign_image, is_signature FROM user_sign WHERE user_id = ?", (user_id,))
row = cursor.fetchone()
return dict(row) if row else None
finally:
conn.close()
def get_image_by_user_name(user_name):
if _active_db == 'mysql':
return _mysql_get_image_by_user_name(user_name)
return _sqlite_get_image_by_user_name(user_name)
def _mysql_get_image_by_user_name(user_name):
conn = _get_mysql_connection()
if not conn:
return _sqlite_get_image_by_user_name(user_name)
try:
with conn.cursor() as cursor:
cursor.execute("SELECT sign_image, is_signature, user_id, user_name FROM user_sign WHERE user_name = %s", (user_name,))
return cursor.fetchall()
except Exception as e:
logger.error("MySQL 查询图片失败: %s", str(e))
return _sqlite_get_image_by_user_name(user_name)
def _sqlite_get_image_by_user_name(user_name):
conn = _get_sqlite_connection()
try:
cursor = conn.execute("SELECT sign_image, is_signature, user_id, user_name FROM user_sign WHERE user_name = ?", (user_name,))
return [dict(r) for r in cursor.fetchall()]
finally:
conn.close()
def get_active_db_type():
return _active_db or 'unknown'