rewrited logic structure
This commit is contained in:
142
group_system/storage/database.py
Normal file
142
group_system/storage/database.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""数据库连接、Schema 和系统级设置。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from group_system.config import GROUP_LIMIT, TEACHER_PASSWORD, TEACHER_USER
|
||||
from group_system.security import hash_password
|
||||
|
||||
|
||||
class DatabaseCore:
|
||||
|
||||
def __init__(self, path: Path):
|
||||
self.path = path
|
||||
self.lock = threading.RLock()
|
||||
self.connection = sqlite3.connect(str(path), check_same_thread=False)
|
||||
self.connection.row_factory = sqlite3.Row
|
||||
self.connection.execute("PRAGMA foreign_keys = ON")
|
||||
self.init_database()
|
||||
|
||||
def init_database(self) -> None:
|
||||
with self.lock, self.connection:
|
||||
self.connection.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS students (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
class_name TEXT NOT NULL,
|
||||
student_no TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
gender TEXT DEFAULT '',
|
||||
admission_no TEXT DEFAULT '',
|
||||
remark TEXT DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
must_change_password INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
class_name TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
leader_student_id INTEGER REFERENCES students(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
group_id INTEGER NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
||||
student_id INTEGER NOT NULL UNIQUE REFERENCES students(id) ON DELETE CASCADE,
|
||||
joined_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS class_controls (
|
||||
class_name TEXT PRIMARY KEY,
|
||||
locked INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
token_hash TEXT PRIMARY KEY,
|
||||
role TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
student_id INTEGER REFERENCES students(id) ON DELETE CASCADE,
|
||||
created_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
||||
self.connection.execute(
|
||||
"INSERT OR IGNORE INTO settings(key,value) VALUES('active_class','1')"
|
||||
)
|
||||
self.connection.execute(
|
||||
"INSERT OR IGNORE INTO settings(key,value) VALUES('teacher_username',?)",
|
||||
(TEACHER_USER,),
|
||||
)
|
||||
self.connection.execute(
|
||||
"INSERT OR IGNORE INTO settings(key,value) VALUES('teacher_password',?)",
|
||||
(hash_password(TEACHER_PASSWORD),),
|
||||
)
|
||||
self.connection.execute(
|
||||
"INSERT OR IGNORE INTO settings(key,value) VALUES('group_limit',?)",
|
||||
(str(GROUP_LIMIT),),
|
||||
)
|
||||
|
||||
def get_setting(self, key: str, conn: sqlite3.Connection | None = None) -> str | None:
|
||||
conn = conn or self.connection
|
||||
row = conn.execute("SELECT value FROM settings WHERE key=?", (key,)).fetchone()
|
||||
return None if row is None else row["value"]
|
||||
|
||||
def set_setting(self, key: str, value: str) -> None:
|
||||
with self.lock, self.connection:
|
||||
self.connection.execute(
|
||||
"INSERT INTO settings(key,value) VALUES(?,?) "
|
||||
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||
(key, value),
|
||||
)
|
||||
|
||||
def class_list(self) -> list[dict[str, Any]]:
|
||||
with self.lock:
|
||||
rows = self.connection.execute(
|
||||
"""SELECT class_name, COUNT(*) AS student_count
|
||||
FROM students GROUP BY class_name
|
||||
ORDER BY CAST(class_name AS REAL), class_name"""
|
||||
).fetchall()
|
||||
active = self.get_setting("active_class")
|
||||
locked = {
|
||||
row["class_name"]: bool(row["locked"])
|
||||
for row in self.connection.execute("SELECT class_name,locked FROM class_controls")
|
||||
}
|
||||
return [
|
||||
{
|
||||
"class_name": row["class_name"],
|
||||
"student_count": row["student_count"],
|
||||
"locked": bool(locked.get(row["class_name"], False)),
|
||||
"active": row["class_name"] == active,
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def active_class(self) -> str:
|
||||
return self.get_setting("active_class") or ""
|
||||
|
||||
def group_limit(self) -> int:
|
||||
value = self.get_setting("group_limit")
|
||||
try:
|
||||
limit = int(value or GROUP_LIMIT)
|
||||
except ValueError:
|
||||
limit = GROUP_LIMIT
|
||||
return max(2, limit)
|
||||
|
||||
def set_group_limit(self, limit: int) -> tuple[bool, str]:
|
||||
if limit < 2 or limit > 100:
|
||||
return False, "分组上限必须在 2 到 100 人之间"
|
||||
self.set_setting("group_limit", str(limit))
|
||||
return True, f"分组上限已调整为 {limit} 人"
|
||||
|
||||
def is_locked(self, class_name: str) -> bool:
|
||||
with self.lock:
|
||||
row = self.connection.execute(
|
||||
"SELECT locked FROM class_controls WHERE class_name=?", (class_name,)
|
||||
).fetchone()
|
||||
return bool(row and row["locked"])
|
||||
Reference in New Issue
Block a user