import hashlib import string from sqlalchemy import select from sqlalchemy.orm import Session from app.models import ExaminationExamExaminee _ALPHABET = string.digits + string.ascii_uppercase # 0-9A-Z, 36 chars def _to_base36(num: int, length: int) -> str: """将非负整数编码为定长 base36 字符串。""" chars: list[str] = [] for _ in range(length): num, rem = divmod(num, 36) chars.append(_ALPHABET[rem]) return "".join(reversed(chars)) def generate_student_no(examinee_id: int, exam_id: int) -> str: """根据考生ID + 考试ID 生成 10 位字母数字编码。""" raw = f"{examinee_id}:{exam_id}".encode() digest = hashlib.sha256(raw).digest() num = int.from_bytes(digest[:8], "big") return _to_base36(num, 10) def ensure_student_no(db: Session, attempt: ExaminationExamExaminee) -> str: """获取或生成 attempt 的 student_no(幂等,已有则直接返回)。""" if attempt.student_no: return attempt.student_no code = generate_student_no(attempt.examinee_id, attempt.exam_id) # 极小概率碰撞时追加区分 existing = db.scalar( select(ExaminationExamExaminee.id).where( ExaminationExamExaminee.student_no == code, ExaminationExamExaminee.id != attempt.id, ) ) if existing is not None: digest = hashlib.sha256( f"{attempt.examinee_id}:{attempt.exam_id}:{attempt.id}".encode() ).digest() code = _to_base36(int.from_bytes(digest[:8], "big"), 10) attempt.student_no = code db.flush() return code def find_attempt_by_student_no( db: Session, student_no: str ) -> ExaminationExamExaminee | None: """通过编码反查答卷记录。""" return db.scalar( select(ExaminationExamExaminee).where( ExaminationExamExaminee.student_no == student_no.strip().upper() ) )