feat(backend): harden task boundaries

This commit is contained in:
2026-05-05 00:55:29 +08:00
parent 817540f8a0
commit e243dccfd7
15 changed files with 694 additions and 147 deletions
+18 -2
View File
@@ -11,9 +11,11 @@ from backend.services.check_in_service import CheckInService
from backend.services.admin_service import AdminService
from backend.dependencies import get_current_admin_user
from backend.config import settings
from backend.exceptions import BaseAPIException
logger = logging.getLogger(__name__)
router = APIRouter()
EXPECTED_API_EXCEPTIONS = (BaseAPIException, HTTPException)
class BatchToggleTasksRequest(BaseModel):
@@ -43,13 +45,21 @@ async def batch_toggle_tasks(
task.is_active = request.is_active
count += 1
from backend.services.scheduler_service import sync_scheduled_task
db.commit()
for task_id in request.task_ids:
task = db.query(CheckInTask).filter(CheckInTask.id == task_id).first()
if task:
sync_scheduled_task(task)
return {
"success": True,
"message": f"{'启用' if request.is_active else '禁用'} {count} 个任务",
"count": count,
}
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"批量操作失败: {str(e)}"
@@ -72,6 +82,8 @@ async def batch_check_in(
try:
result = CheckInService.batch_check_in_tasks(request.task_ids, db)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"批量打卡失败: {str(e)}"
@@ -235,6 +247,8 @@ async def get_system_stats(
"tokens": {"expiring_soon": expiring_users},
}
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取统计失败: {str(e)}"
@@ -251,6 +265,8 @@ async def get_pending_users(
try:
users = AdminService.get_pending_users(db)
return users
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -274,7 +290,7 @@ async def approve_user(
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=result["message"])
return result
except HTTPException:
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
@@ -298,7 +314,7 @@ async def reject_user(
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=result["message"])
return result
except HTTPException:
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
+12 -1
View File
@@ -12,10 +12,11 @@ from backend.schemas.auth import (
AliasLoginResponse,
)
from backend.services.auth_service import AuthService
from backend.exceptions import BusinessLogicError
from backend.exceptions import BaseAPIException, BusinessLogicError
from backend.limiter import limiter
router = APIRouter()
EXPECTED_API_EXCEPTIONS = (BaseAPIException, HTTPException)
@router.post("/request_qrcode", response_model=dict, summary="请求 QQ 扫码二维码")
@@ -68,6 +69,8 @@ async def request_qrcode(
)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"创建扫码会话失败: {str(e)}"
@@ -95,6 +98,8 @@ async def get_qrcode_status(session_id: str, db: Session = Depends(get_db)):
try:
result = AuthService.get_qrcode_status(session_id, db)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"查询扫码状态失败: {str(e)}"
@@ -113,6 +118,8 @@ async def cancel_qrcode_session(session_id: str):
try:
result = AuthService.cancel_qrcode_session(session_id)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"取消会话失败: {str(e)}"
@@ -136,6 +143,8 @@ async def verify_token(request: TokenVerifyRequest, db: Session = Depends(get_db
try:
result = AuthService.verify_token(request.authorization, db)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"验证 Token 失败: {str(e)}"
@@ -170,6 +179,8 @@ async def alias_login(
try:
result = AuthService.alias_login(login_data.alias, login_data.password, db)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"别名登录失败: {str(e)}"
+12
View File
@@ -12,8 +12,10 @@ from backend.schemas.check_in import (
from backend.services.check_in_service import CheckInService
from backend.services.task_service import TaskService
from backend.dependencies import get_current_user, get_current_admin_user
from backend.exceptions import BaseAPIException
router = APIRouter()
EXPECTED_API_EXCEPTIONS = (BaseAPIException, HTTPException)
@router.post("/manual/{task_id}", summary="手动触发打卡(异步)")
@@ -38,6 +40,8 @@ async def manual_check_in(
try:
result = CheckInService.start_async_check_in(task, "manual", db)
return result
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"启动打卡任务失败: {str(e)}"
@@ -111,6 +115,8 @@ async def get_task_check_in_records(
task_id, db, skip, limit, status_filter, trigger_type
)
return PaginatedResponse(records=records, total=total, skip=skip, limit=limit)
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取打卡记录失败: {str(e)}"
@@ -145,6 +151,8 @@ async def get_my_check_in_records(
current_user.id, db, skip, limit, status_filter, trigger_type
)
return PaginatedResponse(records=records, total=total, skip=skip, limit=limit)
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取打卡记录失败: {str(e)}"
@@ -181,6 +189,8 @@ async def get_all_check_in_records(
CheckInService.enrich_record_with_user_task_info(record, db) for record in records
]
return PaginatedResponse(records=enriched_records, total=total, skip=skip, limit=limit)
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取打卡记录失败: {str(e)}"
@@ -213,6 +223,8 @@ async def get_check_in_records_count(
total = query.count()
return {"total": total}
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取统计失败: {str(e)}"
+5
View File
@@ -4,6 +4,7 @@ from typing import List
from datetime import datetime, timedelta
from pydantic import BaseModel, Field
from backend.exceptions import BaseAPIException
from backend.models import get_db, User
from backend.schemas.task import TaskUpdate, TaskResponse
from backend.services.task_service import TaskService
@@ -37,6 +38,8 @@ async def get_tasks(
# 为每个任务添加额外信息
enriched_tasks = [TaskService.enrich_task_with_check_in_info(task, db) for task in tasks]
return enriched_tasks
except (BaseAPIException, HTTPException):
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取任务列表失败: {str(e)}"
@@ -173,6 +176,8 @@ async def validate_cron_expression(request: CronValidateRequest):
"next_times": next_times,
"description": generate_cron_description(cron_expr),
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=f"无效的 Crontab 表达式: {str(e)}"
+10
View File
@@ -4,6 +4,7 @@ from sqlalchemy.orm import Session
from backend.models import User
from backend.dependencies import get_db, get_current_user, get_current_admin_user
from backend.exceptions import BaseAPIException
from backend.schemas.template import (
TemplateCreate,
TemplateUpdate,
@@ -17,6 +18,9 @@ from backend.services.template_service import TemplateService
router = APIRouter()
EXPECTED_API_EXCEPTIONS = (BaseAPIException, HTTPException)
@router.get("/", response_model=List[TemplateResponse], summary="获取所有模板列表")
async def get_all_templates(
skip: int = Query(0, ge=0, description="跳过记录数"),
@@ -35,6 +39,8 @@ async def get_all_templates(
try:
templates = TemplateService.get_all_templates(db, skip, limit, is_active)
return templates
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取模板列表失败: {str(e)}"
@@ -57,6 +63,8 @@ async def get_active_templates(
try:
templates = TemplateService.get_all_templates(db, skip, limit, is_active=True)
return templates
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取模板列表失败: {str(e)}"
@@ -115,6 +123,8 @@ async def preview_template(
"preview_payload": preview_payload,
"field_config": merged_config,
}
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"生成预览失败: {str(e)}"
+19 -1
View File
@@ -14,9 +14,15 @@ from backend.schemas.task import TaskResponse
from backend.services.user_service import UserService
from backend.services.task_service import TaskService
from backend.dependencies import get_current_user, get_current_admin_user
from backend.exceptions import ValidationError, AuthorizationError, ResourceNotFoundError
from backend.exceptions import (
AuthorizationError,
BaseAPIException,
ResourceNotFoundError,
ValidationError,
)
router = APIRouter()
EXPECTED_API_EXCEPTIONS = (BaseAPIException, HTTPException)
@router.post(
@@ -42,6 +48,8 @@ async def create_user(
return user
except ValueError as e:
raise ValidationError(str(e))
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"创建用户失败: {str(e)}"
@@ -103,6 +111,8 @@ async def update_current_user_profile(
return user
except ValueError as e:
raise ValidationError(str(e))
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"更新个人信息失败: {str(e)}"
@@ -144,6 +154,8 @@ async def get_current_user_tasks(
try:
tasks = TaskService.get_user_tasks(current_user.id, db, include_inactive)
return tasks
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取任务列表失败: {str(e)}"
@@ -170,6 +182,8 @@ async def get_all_users(
try:
users = UserService.get_all_users(db, skip, limit, search, role)
return users
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取用户列表失败: {str(e)}"
@@ -252,6 +266,8 @@ async def update_user(
return user
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"更新用户失败: {str(e)}"
@@ -272,6 +288,8 @@ async def delete_user(
return None
except ValueError as e:
raise ResourceNotFoundError(str(e))
except EXPECTED_API_EXCEPTIONS:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"删除用户失败: {str(e)}"