mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
feat(backend): harden task boundaries
This commit is contained in:
@@ -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,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,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)}"
|
||||
|
||||
@@ -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)}"
|
||||
|
||||
@@ -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)}"
|
||||
|
||||
@@ -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)}"
|
||||
|
||||
Reference in New Issue
Block a user