mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
refactor(structure): reorganize app layout
BREAKING CHANGE: root backend/frontend directories and old run/manage entrypoints were removed. Use apps/backend, apps/frontend, and python main.py commands instead.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
创建管理员用户的脚本
|
||||
|
||||
使用方法:
|
||||
PYTHONPATH=apps python apps/backend/scripts/create_admin.py
|
||||
|
||||
或使用虚拟环境:
|
||||
PYTHONPATH=apps ./venv/bin/python apps/backend/scripts/create_admin.py
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
APPS_DIR = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(APPS_DIR))
|
||||
|
||||
from backend.models import init_db, User
|
||||
from backend.models.database import SessionLocal
|
||||
from backend.services.auth_service import AuthService
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_admin_user(alias: str):
|
||||
"""
|
||||
将现有用户升级为管理员(或创建管理员占位符)
|
||||
|
||||
Args:
|
||||
alias: 用户别名
|
||||
"""
|
||||
# 初始化数据库
|
||||
init_db()
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 检查别名是否已存在
|
||||
existing_user = db.query(User).filter(User.alias == alias).first()
|
||||
|
||||
if existing_user:
|
||||
print(f"[OK] 找到用户:{alias}")
|
||||
print(f" 用户 ID: {existing_user.id}")
|
||||
print(f" QQ 标识 (jwt_sub): {existing_user.jwt_sub}")
|
||||
print(f" 当前角色: {existing_user.role}")
|
||||
print(f" 审批状态: {existing_user.is_approved}")
|
||||
|
||||
# 如果已经是管理员
|
||||
if existing_user.role == "admin":
|
||||
print("\n该用户已经是管理员")
|
||||
return
|
||||
|
||||
# 升级为管理员
|
||||
response = input("\n是否将该用户升级为管理员?(y/n): ")
|
||||
if response.lower() == 'y':
|
||||
existing_user.role = "admin"
|
||||
existing_user.is_approved = True # 确保已审批
|
||||
db.commit()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("[成功] 用户已升级为管理员!")
|
||||
print("=" * 60)
|
||||
print(f" 用户 ID: {existing_user.id}")
|
||||
print(f" 别名: {existing_user.alias}")
|
||||
print(f" QQ 标识: {existing_user.jwt_sub}")
|
||||
print(f" 角色: admin")
|
||||
print("=" * 60)
|
||||
else:
|
||||
print("操作已取消")
|
||||
else:
|
||||
print(f"\n[错误] 未找到别名为 '{alias}' 的用户")
|
||||
print("\n请先使用该别名进行 QQ 扫码注册,然后再运行此脚本升级为管理员")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[错误] 操作失败: {e}")
|
||||
db.rollback()
|
||||
raise
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("接龙自动打卡系统 - 设置管理员")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("[说明]")
|
||||
print(" 此脚本将已注册的用户升级为管理员")
|
||||
print(" 请先使用别名进行 QQ 扫码注册,然后运行此脚本")
|
||||
print()
|
||||
|
||||
# 获取用户别名
|
||||
alias = input("请输入要设置为管理员的用户别名 [admin]: ").strip() or "admin"
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"准备将用户 '{alias}' 设置为管理员")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
create_admin_user(alias)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
数据库迁移脚本:添加账户锁定相关字段
|
||||
|
||||
添加字段:
|
||||
- failed_login_attempts: 连续登录失败次数
|
||||
- locked_until: 账户锁定到期时间
|
||||
- last_failed_login: 最后一次登录失败时间
|
||||
|
||||
运行方式:
|
||||
PYTHONPATH=apps python -m backend.scripts.migrate_add_account_lockout
|
||||
python -m backend.scripts.migrate_add_account_lockout
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
APPS_DIR = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(APPS_DIR))
|
||||
|
||||
from sqlalchemy import text
|
||||
from backend.models.database import engine
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def migrate():
|
||||
"""执行迁移"""
|
||||
logger.info("开始迁移:添加账户锁定相关字段...")
|
||||
|
||||
with engine.connect() as conn:
|
||||
# 检查字段是否已存在
|
||||
result = conn.execute(text("PRAGMA table_info(users)"))
|
||||
columns = [row[1] for row in result]
|
||||
|
||||
# 添加 failed_login_attempts 字段
|
||||
if 'failed_login_attempts' not in columns:
|
||||
logger.info("添加 failed_login_attempts 字段...")
|
||||
conn.execute(text(
|
||||
"ALTER TABLE users ADD COLUMN failed_login_attempts INTEGER DEFAULT 0 NOT NULL"
|
||||
))
|
||||
conn.commit()
|
||||
logger.info("✓ failed_login_attempts 字段添加成功")
|
||||
else:
|
||||
logger.info("✓ failed_login_attempts 字段已存在,跳过")
|
||||
|
||||
# 添加 locked_until 字段
|
||||
if 'locked_until' not in columns:
|
||||
logger.info("添加 locked_until 字段...")
|
||||
conn.execute(text(
|
||||
"ALTER TABLE users ADD COLUMN locked_until DATETIME"
|
||||
))
|
||||
conn.commit()
|
||||
logger.info("✓ locked_until 字段添加成功")
|
||||
else:
|
||||
logger.info("✓ locked_until 字段已存在,跳过")
|
||||
|
||||
# 添加 last_failed_login 字段
|
||||
if 'last_failed_login' not in columns:
|
||||
logger.info("添加 last_failed_login 字段...")
|
||||
conn.execute(text(
|
||||
"ALTER TABLE users ADD COLUMN last_failed_login DATETIME"
|
||||
))
|
||||
conn.commit()
|
||||
logger.info("✓ last_failed_login 字段添加成功")
|
||||
else:
|
||||
logger.info("✓ last_failed_login 字段已存在,跳过")
|
||||
|
||||
logger.info("✅ 迁移完成!账户锁定功能已启用")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
migrate()
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 迁移失败: {e}")
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
测试新的异常处理系统
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
APPS_DIR = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(APPS_DIR))
|
||||
|
||||
from backend.exceptions import (
|
||||
ValidationError,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
ResourceNotFoundError,
|
||||
BusinessLogicError,
|
||||
)
|
||||
from backend.schemas.response import ErrorResponse, ErrorDetail
|
||||
|
||||
def test_exceptions():
|
||||
"""测试自定义异常"""
|
||||
print("=" * 60)
|
||||
print("测试自定义异常类")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试 ValidationError
|
||||
try:
|
||||
raise ValidationError("用户名长度必须在2-50之间")
|
||||
except ValidationError as e:
|
||||
print(f"✅ ValidationError: {e.message} (状态码: {e.status_code}, 代码: {e.error_code})")
|
||||
|
||||
# 测试 AuthenticationError
|
||||
try:
|
||||
raise AuthenticationError("Token已过期")
|
||||
except AuthenticationError as e:
|
||||
print(f"✅ AuthenticationError: {e.message} (状态码: {e.status_code}, 代码: {e.error_code})")
|
||||
|
||||
# 测试 AuthorizationError
|
||||
try:
|
||||
raise AuthorizationError("需要管理员权限")
|
||||
except AuthorizationError as e:
|
||||
print(f"✅ AuthorizationError: {e.message} (状态码: {e.status_code}, 代码: {e.error_code})")
|
||||
|
||||
# 测试 ResourceNotFoundError
|
||||
try:
|
||||
raise ResourceNotFoundError("用户不存在")
|
||||
except ResourceNotFoundError as e:
|
||||
print(f"✅ ResourceNotFoundError: {e.message} (状态码: {e.status_code}, 代码: {e.error_code})")
|
||||
|
||||
# 测试 BusinessLogicError
|
||||
try:
|
||||
raise BusinessLogicError("打卡任务已禁用")
|
||||
except BusinessLogicError as e:
|
||||
print(f"✅ BusinessLogicError: {e.message} (状态码: {e.status_code}, 代码: {e.error_code})")
|
||||
|
||||
|
||||
def test_response_schemas():
|
||||
"""测试响应 Schema"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试响应 Schema")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试 ErrorResponse
|
||||
error_response = ErrorResponse(
|
||||
error=ErrorDetail(
|
||||
code="VALIDATION_ERROR",
|
||||
message="邮箱格式不正确",
|
||||
field="email"
|
||||
)
|
||||
)
|
||||
|
||||
response_dict = error_response.model_dump()
|
||||
print(f"✅ ErrorResponse 序列化成功:")
|
||||
print(f" {response_dict}")
|
||||
|
||||
assert response_dict["success"] == False
|
||||
assert response_dict["error"]["code"] == "VALIDATION_ERROR"
|
||||
assert response_dict["error"]["message"] == "邮箱格式不正确"
|
||||
assert response_dict["error"]["field"] == "email"
|
||||
print("✅ 所有断言通过")
|
||||
|
||||
|
||||
def check_old_exception_patterns():
|
||||
"""检查旧的异常处理模式"""
|
||||
print("\n" + "=" * 60)
|
||||
print("检查需要更新的旧异常代码")
|
||||
print("=" * 60)
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
patterns = {
|
||||
"HTTPException with detail": r'raise HTTPException.*detail=f?".*{',
|
||||
"except Exception": r'except Exception as',
|
||||
}
|
||||
|
||||
results = {}
|
||||
for pattern_name, pattern in patterns.items():
|
||||
results[pattern_name] = []
|
||||
|
||||
for root, dirs, files in os.walk(APPS_DIR / 'backend' / 'api'):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
filepath = os.path.join(root, file)
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
matches = re.findall(pattern, content, re.MULTILINE)
|
||||
if matches:
|
||||
results[pattern_name].append((filepath, len(matches)))
|
||||
|
||||
for pattern_name, files in results.items():
|
||||
print(f"\n{pattern_name}:")
|
||||
if files:
|
||||
print(f" ⚠️ 发现 {sum(count for _, count in files)} 处使用")
|
||||
for filepath, count in files:
|
||||
print(f" - {filepath}: {count} 处")
|
||||
else:
|
||||
print(f" ✅ 未发现使用")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_exceptions()
|
||||
test_response_schemas()
|
||||
check_old_exception_patterns()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 所有测试通过!新的异常处理系统工作正常")
|
||||
print("=" * 60)
|
||||
Reference in New Issue
Block a user