import json from typing import Any, Optional, Dict from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.models.pre_test_template import PreTestTemplate from app.utils.douyinApi import DouyinApi from app.utils.area import parse_district_data, get_area_by_level, get_cached_area_data, fetch_and_cache_area_data from app.dependencies import get_current_user, get_db from app.models.user import User from app.schemas.pre_test_template import ( PreTestTemplateCreate, PreTestTemplateListResponse, PreTestTemplateOut, PreTestTemplateUpdate, ) from app.services.pre_test_template_service import ( create_pre_test_template, delete_pre_test_template, get_default_template, get_pre_test_template, get_pre_test_template_list, update_pre_test_template, ) router = APIRouter(prefix="/pre-test-template", tags=["前测模板"]) def _template_to_dict(template): return { "id": template.id, "name": template.name, "note": template.note, "platform": template.platform, "external_action": template.external_action, "cpa_bid": template.cpa_bid, "audience_gender": template.audience_gender, "audience_age": json.loads(template.audience_age) if template.audience_age else None, "audience_region": json.loads(template.audience_region) if template.audience_region else None, "audience_network": json.loads(template.audience_network) if template.audience_network else None, "cus_name": template.cus_name, "pricing_type": template.pricing_type, "cost_cap": template.cost_cap, "target_cost": template.target_cost, "nobid": template.nobid, "cpc_bid": template.cpc_bid, "budget": template.budget, "is_default": template.is_default, "user_id": template.user_id, "created_at": template.created_at, "updated_at": template.updated_at, } @router.post( "/create", summary="创建前测模板", description="创建一个新的前测模板", ) async def create_template( req: PreTestTemplateCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: try: #新增一个判断,如果模板名称重复,提示用户修改 result = await db.execute( select(PreTestTemplate).where( PreTestTemplate.name == req.name, PreTestTemplate.user_id == current_user.id, PreTestTemplate.deleted_at.is_(None), ) ) existing_template = result.scalar() if existing_template: raise ValueError("模板名称已存在") template = await create_pre_test_template( user_id=current_user.id, db=db, name=req.name, note=req.note, platform=req.platform, external_action=req.external_action, cpa_bid=req.cpa_bid, audience_gender=req.audience_gender, audience_age=req.audience_age, audience_region=req.audience_region, audience_network=req.audience_network, cus_name=req.cus_name, pricing_type=req.pricing_type, cost_cap=req.cost_cap, target_cost=req.target_cost, nobid=req.nobid, cpc_bid=req.cpc_bid, budget=req.budget, is_default=req.is_default, ) return { "code": 0, "message": "创建成功", } except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"创建失败: {str(e)}", ) @router.get( "/list", summary="获取前测模板列表", description="获取当前用户的前测模板列表,支持按平台筛选和分页", response_model=PreTestTemplateListResponse, ) async def list_templates( platform: Optional[str] = Query(None, description="投放平台筛选(AD/QIANCHUAN/LOCAL)"), page: int = Query(1, description="页码,默认1"), page_size: int = Query(10, description="每页数量,默认10,最大100"), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: try: result = await get_pre_test_template_list( user_id=current_user.id, db=db, platform=platform, page=page, page_size=page_size, ) return { "code": 0, "message": "查询成功", "data": [_template_to_dict(t) for t in result["data"]], "pagination": { "page": result["page"], "page_size": result["page_size"], "total": result["total"], "total_pages": (result["total"] + result["page_size"] - 1) // result["page_size"], }, } except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"查询失败: {str(e)}", ) @router.get( "/default", summary="获取默认前测模板", description="获取当前用户的默认前测模板", ) async def get_default( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: try: template = await get_default_template(current_user.id, db) if not template: return { "code": 0, "message": "未设置默认模板", "data": None, } return { "code": 0, "message": "查询成功", "data": _template_to_dict(template), } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"查询失败: {str(e)}", ) @router.get( "/select/{template_id}", summary="获取前测模板详情", description="根据模板id获取前测模板详情", ) async def get_template( template_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: try: template = await get_pre_test_template(template_id, current_user.id, db) if not template: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="模板不存在", ) return { "code": 0, "message": "查询成功", "data": _template_to_dict(template), } except HTTPException: raise except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"查询失败: {str(e)}", ) @router.post( "/update/{template_id}", summary="更新前测模板", description="更新指定的前测模板", ) async def update_template( template_id: str, req: PreTestTemplateUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: try: update_data = req.dict(exclude_none=True) template = await update_pre_test_template(template_id, current_user.id, db, **update_data) if not template: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="模板不存在", ) return { "code": 0, "message": "更新成功", "data": _template_to_dict(template), } except HTTPException: raise except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"更新失败: {str(e)}", ) @router.get( "/delete/{template_id}", summary="删除前测模板", description="软删除指定的前测模板", ) async def delete_template( template_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: try: success = await delete_pre_test_template(template_id, current_user.id, db) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="模板不存在", ) return { "code": 0, "message": "删除成功", } except HTTPException: raise except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"删除失败: {str(e)}", ) @router.get( "/getArea", summary="获取行政区域信息", description="获取指定级别的行政区域信息,支持一级、二级、三级区域,如果需要更新地区,执行:/api/pre-test-template/getArea?oauth_id=0019ecab9b8bc57d964&advertiser_id=1836693172153543", ) async def get_template_area( oauth_id: str = Query(None, description="授权ID选填,更新地区必填"), advertiser_id: str = Query(default="1836693172153543", description="授权ID选填,更新地区必填"), code: Optional[str] = Query("CN", description="行政区域编码,默认中国CN,选填"), level: Optional[str] = Query("ONE_LEVEL", description="行政区域层级,可选值:ONE_LEVEL(获取省份)、TWO_LEVEL(市级)、THREE_LEVEL(区级)"), parent_code: Optional[str] = Query(None, description="父级区域编码,获取二级时传一级编码,获取三级时传二级编码"), ) -> Any: try: # 1. 先检查缓存是否存在 area_list = get_cached_area_data() # 2. 如果缓存不存在,调用接口获取数据并保存到缓存 if not area_list: area_list = await fetch_and_cache_area_data(oauth_id, advertiser_id, code) # 3. 根据 level 参数过滤区域 if level == "ONE_LEVEL": result = get_area_by_level(area_list, "ONE_LEVEL") elif level == "TWO_LEVEL": result = get_area_by_level(area_list, "TWO_LEVEL", parent_code) elif level == "THREE_LEVEL": if not parent_code: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="获取三级区域需要提供二级区域编码(parent_code)", ) result = get_area_by_level(area_list, "THREE_LEVEL", parent_code) else: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"不支持的级别: {level}", ) # 4. 转换为字典格式返回 result_dict = [area.to_dict() for area in result] return { "code": 0, "message": "成功", "data": result_dict, } except HTTPException: raise except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取区域信息失败: {str(e)}", )