添加素材列表

This commit is contained in:
18610128193
2026-06-24 10:02:16 +08:00
parent 4a1118a3ec
commit adb6b09094
3 changed files with 291 additions and 147 deletions
+94 -141
View File
@@ -1,4 +1,4 @@
from typing import Any, Optional from typing import Any, Optional, List
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Query, Depends, Body from fastapi import APIRouter, Query, Depends, Body
@@ -6,11 +6,13 @@ from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User from app.models.user import User
from app.models.resources_material import ResourcesMaterial
from app.models.material_cost import MaterialCost from app.models.material_cost import MaterialCost
from app.models.user_oauth import UserOAuth from app.models.user_oauth import UserOAuth
from app.models.user_oauth_account import UserOAuthAccount from app.models.user_oauth_account import UserOAuthAccount
from app.dependencies import get_current_user, get_db from app.dependencies import get_current_user, get_db, get_admin_user
from app.services.material_consumption_queue import sync_all_advertisers_consumption, _fetch_and_save_consumption from app.services.material_consumption_queue import sync_all_advertisers_consumption, _fetch_and_save_consumption
from app.services.material_consumption_service import get_consumption_list, format_consumption_response
router = APIRouter(prefix="/material-consumption", tags=["material-consumption"]) router = APIRouter(prefix="/material-consumption", tags=["material-consumption"])
@@ -20,146 +22,26 @@ router = APIRouter(prefix="/material-consumption", tags=["material-consumption"]
summary="查询素材消耗列表", summary="查询素材消耗列表",
description="查询当前用户的素材消耗列表", description="查询当前用户的素材消耗列表",
) )
async def get_consumption_list( async def user_consumption_list(
page: int = Query(1, ge=1, description="页码"), page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(20, ge=1, le=100, description="每页数量"), page_size: int = Query(20, ge=1, le=100, description="每页数量"),
advertiser_id: Optional[str] = Query(None, description="广告主ID筛选"), advertiser_id: Optional[str] = Query(None, description="广告主ID筛选"),
start_date: Optional[str] = Query(None, description="开始日期"), consume_date: Optional[List[str]] = Query(None, description="消耗日期范围,格式: ['开始日期','结束日期']"),
end_date: Optional[str] = Query(None, description="结束日期"),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> Any | dict: ) -> Any | dict:
offset = (page - 1) * page_size consumptions, total = await get_consumption_list(
db=db,
query = ( page=page,
select(MaterialCost) page_size=page_size,
.join( advertiser_id=advertiser_id,
UserOAuth, consume_date=consume_date,
MaterialCost.oauth_id == UserOAuth.id, current_user=current_user,
)
.where(
UserOAuth.user_id == current_user.id,
MaterialCost.deleted_at.is_(None),
UserOAuth.deleted_at.is_(None),
)
) )
if advertiser_id:
query = query.where(MaterialCost.advertiser_id == advertiser_id)
if start_date:
start_date_obj = datetime.strptime(start_date, "%Y-%m-%d").date()
query = query.where(MaterialCost.consume_date >= start_date_obj)
if end_date:
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d").date()
query = query.where(MaterialCost.consume_date <= end_date_obj)
result = await db.execute(
query.order_by(MaterialCost.consume_date.desc())
.offset(offset)
.limit(page_size)
)
consumptions = result.scalars().all()
count_query = (
select(func.count(MaterialCost.id))
.join(
UserOAuth,
MaterialCost.oauth_id == UserOAuth.id,
)
.where(
UserOAuth.user_id == current_user.id,
MaterialCost.deleted_at.is_(None),
UserOAuth.deleted_at.is_(None),
)
)
if advertiser_id:
count_query = count_query.where(MaterialCost.advertiser_id == advertiser_id)
if start_date:
start_date_obj = datetime.strptime(start_date, "%Y-%m-%d").date()
count_query = count_query.where(MaterialCost.consume_date >= start_date_obj)
if end_date:
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d").date()
count_query = count_query.where(MaterialCost.consume_date <= end_date_obj)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
return { return {
"code": 0, "code": 0,
"data": [ "data": format_consumption_response(consumptions),
{
"id": consumption.id,
"advertiser_id": consumption.advertiser_id,
"material_id": consumption.material_id,
"consume_date": consumption.consume_date.isoformat() if consumption.consume_date else None,
"stat_cost": consumption.stat_cost,
"show_cnt": consumption.show_cnt,
"cpm_platform": consumption.cpm_platform,
"click_cnt": consumption.click_cnt,
"ctr": consumption.ctr,
"cpc_platform": consumption.cpc_platform,
"convert_cnt": consumption.convert_cnt,
"conversion_cost": consumption.conversion_cost,
"conversion_rate": consumption.conversion_rate,
"deep_convert_cnt": consumption.deep_convert_cnt,
"deep_convert_cost": consumption.deep_convert_cost,
"deep_convert_rate": consumption.deep_convert_rate,
"active": consumption.active,
"active_cost": consumption.active_cost,
"active_rate": consumption.active_rate,
"active_register": consumption.active_register,
"active_register_cost": consumption.active_register_cost,
"active_register_rate": consumption.active_register_rate,
"attribution_next_day_open_cnt": consumption.attribution_next_day_open_cnt,
"attribution_next_day_open_cost": consumption.attribution_next_day_open_cost,
"attribution_next_day_open_rate": consumption.attribution_next_day_open_rate,
"active_pay": consumption.active_pay,
"active_pay_cost": consumption.active_pay_cost,
"active_pay_rate": consumption.active_pay_rate,
"phone": consumption.phone,
"form": consumption.form,
"download_start": consumption.download_start,
"form_submit": consumption.form_submit,
"button": consumption.button,
"view": consumption.view,
"message": consumption.message,
"consult": consumption.consult,
"consult_effective": consumption.consult_effective,
"shopping": consumption.shopping,
"customer_effective": consumption.customer_effective,
"attribution_game_in_app_ltv_1day": consumption.attribution_game_in_app_ltv_1day,
"attribution_game_in_app_roi_1day": consumption.attribution_game_in_app_roi_1day,
"loan_completion": consumption.loan_completion,
"loan_completion_cost": consumption.loan_completion_cost,
"loan_completion_rate": consumption.loan_completion_rate,
"loan_credit": consumption.loan_credit,
"loan_credit_cost": consumption.loan_credit_cost,
"loan_credit_rate": consumption.loan_credit_rate,
"in_app_order_gmv": consumption.in_app_order_gmv,
"in_app_order_roi": consumption.in_app_order_roi,
"in_app_pay_gmv": consumption.in_app_pay_gmv,
"in_app_pay_roi": consumption.in_app_pay_roi,
"total_play": consumption.total_play,
"valid_play": consumption.valid_play,
"valid_play_cost": consumption.valid_play_cost,
"valid_play_rate": consumption.valid_play_rate,
"valid_play_of_mille": consumption.valid_play_of_mille,
"valid_play_cost_of_mille": consumption.valid_play_cost_of_mille,
"average_play_time_per_play": consumption.average_play_time_per_play,
"play_over_rate": consumption.play_over_rate,
"dy_like": consumption.dy_like,
"dy_comment": consumption.dy_comment,
"dy_share": consumption.dy_share,
"report_cnt": consumption.report_cnt,
"created_at": consumption.created_at,
}
for consumption in consumptions
],
"pagination": { "pagination": {
"page": page, "page": page,
"page_size": page_size, "page_size": page_size,
@@ -171,27 +53,55 @@ async def get_consumption_list(
@router.get( @router.get(
"/sync", "/sync",
summary="手动同步素材消耗", summary="手动同步素材消耗",
description="手动触发素材消耗同步任务,加入队列顺序执行", description="手动触发当前用户授权的广告主的素材消耗同步任务,加入队列顺序执行",
) )
async def sync_consumption( async def sync_consumption(
date: str = Query(None, description="同步日期,默认为昨天"), date: str = Query(None, description="同步日期,默认为昨天"),
advertiser_id: Optional[str] = Query(None, description="指定广告主ID,不指定则同步所有授权的广告主"),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> Any | dict: ) -> Any | dict:
if date is None: if date is None:
date = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%d") date = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%d")
# 测试用,手动触发同步素材消耗任务 from app.services.material_consumption_queue import material_consumption_queue
result = await _fetch_and_save_consumption("0019eb9f130027c05b8", "1863675913228435", date)
return { query = (
"code": 0, select(UserOAuth.id, UserOAuthAccount.advertiser_id)
"message": result, .join(UserOAuthAccount, UserOAuth.id == UserOAuthAccount.oauth_id)
} .join(ResourcesMaterial, UserOAuthAccount.advertiser_id == ResourcesMaterial.advertiser_id)
result = await sync_all_advertisers_consumption(date) .where(
UserOAuth.user_id == current_user.id,
UserOAuth.deleted_at.is_(None),
UserOAuthAccount.deleted_at.is_(None),
ResourcesMaterial.deleted_at.is_(None),
ResourcesMaterial.material_id.is_not_(None),
)
.distinct()
)
if advertiser_id:
query = query.where(UserOAuthAccount.advertiser_id == advertiser_id)
result = await db.execute(query)
oauth_advertiser_pairs = result.all()
if not oauth_advertiser_pairs:
return {
"code": 0,
"message": "没有找到可同步的广告主",
}
for oauth_id, adv_id in oauth_advertiser_pairs:
await material_consumption_queue.enqueue({
"oauth_id": oauth_id,
"advertiser_id": adv_id,
"date": date,
})
return { return {
"code": 0, "code": 0,
"message": result["message"], "message": f"已将 {len(oauth_advertiser_pairs)} 个广告主的消耗更新任务加入队列",
} }
@@ -298,4 +208,47 @@ async def get_advertisers(
return { return {
"code": 0, "code": 0,
"data": [{"advertiser_id": str(aid)} for aid in advertisers], "data": [{"advertiser_id": str(aid)} for aid in advertisers],
}
@router.get(
"/admin/list",
summary="管理员查询所有素材消耗列表",
description="管理员可查看系统中所有用户的素材消耗数据",
)
async def admin_consumption_list(
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
advertiser_id: Optional[str] = Query(None, description="广告主ID筛选"),
user_id: Optional[str] = Query(None, description="用户ID筛选"),
consume_date: Optional[List[str]] = Query(None, description="消耗日期范围,格式: ['开始日期','结束日期']"),
current_user: User = Depends(get_admin_user),
db: AsyncSession = Depends(get_db),
) -> Any | dict:
#需要判断管理员吗
if not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="只有管理员才能查询所有素材消耗列表",
)
consumptions, total = await get_consumption_list(
db=db,
page=page,
page_size=page_size,
advertiser_id=advertiser_id,
user_id=user_id,
consume_date=consume_date,
current_user=None,
)
return {
"code": 0,
"data": format_consumption_response(consumptions, include_oauth_id=True),
"pagination": {
"page": page,
"page_size": page_size,
"total": total,
},
} }
+37 -6
View File
@@ -1,4 +1,4 @@
from typing import Any, Optional from typing import Any, Optional, List
from fastapi import APIRouter, Body, Depends, HTTPException, Query, status from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
from sqlalchemy import select, func from sqlalchemy import select, func
@@ -18,6 +18,37 @@ from app.utils.id_gen import generate_id
router = APIRouter(prefix="/open-type", tags=["open-type"]) router = APIRouter(prefix="/open-type", tags=["open-type"])
# 公共接口 - 无需登录
@router.get(
"/open_type_all",
summary="查询所有开户方式",
description="查询所有开户方式,前端使用select框选择,无需登录",
dependencies=[],
)
async def get_open_type_public(
db: AsyncSession = Depends(get_db),
) -> Any | dict:
result = await db.execute(
select(OpenType).where(OpenType.deleted_at.is_(None))
)
open_types = result.scalars().all()
return {
"code": 0,
"message": "查询成功",
"data": [
{
"id": ot.id,
"type_name": ot.type_name,
"open_type": ot.open_type,
"description": ot.description,
"thumb": ot.thumb,
}
for ot in open_types
],
}
@router.get( @router.get(
"/list", "/list",
summary="获取开户方式列表", summary="获取开户方式列表",
@@ -83,7 +114,7 @@ async def get_open_type_list(
@router.get( @router.get(
"/{id}", "/select/{id}",
summary="获取开户方式详情", summary="获取开户方式详情",
description="根据ID获取开户方式详情", description="根据ID获取开户方式详情",
response_model=OpenTypeResponse, response_model=OpenTypeResponse,
@@ -120,7 +151,7 @@ async def get_open_type_detail(
@router.post( @router.post(
"/", "/create",
summary="创建开户方式", summary="创建开户方式",
description="创建新的开户方式", description="创建新的开户方式",
response_model=OpenTypeResponse, response_model=OpenTypeResponse,
@@ -167,7 +198,7 @@ async def create_open_type(
@router.put( @router.put(
"/{id}", "/update/{id}",
summary="更新开户方式", summary="更新开户方式",
description="更新指定的开户方式", description="更新指定的开户方式",
response_model=OpenTypeResponse, response_model=OpenTypeResponse,
@@ -231,7 +262,7 @@ async def update_open_type(
@router.delete( @router.delete(
"/{id}", "/delete/{id}",
summary="删除开户方式", summary="删除开户方式",
description="软删除指定的开户方式", description="软删除指定的开户方式",
response_model=OpenTypeResponse, response_model=OpenTypeResponse,
@@ -259,4 +290,4 @@ async def delete_open_type(
"code": 0, "code": 0,
"message": "删除成功", "message": "删除成功",
"data": None, "data": None,
} }
@@ -0,0 +1,160 @@
from typing import Any, Optional, Tuple, List
from datetime import datetime
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.models.material_cost import MaterialCost
from app.models.user_oauth import UserOAuth
async def get_consumption_list(
db: AsyncSession,
page: int = 1,
page_size: int = 20,
advertiser_id: Optional[str] = None,
user_id: Optional[str] = None,
consume_date: Optional[List[str]] = None,
current_user: Optional[User] = None,
) -> Tuple[List[MaterialCost], int]:
offset = (page - 1) * page_size
query = select(MaterialCost).where(MaterialCost.deleted_at.is_(None))
if current_user:
query = query.join(
UserOAuth,
MaterialCost.oauth_id == UserOAuth.id,
).where(
UserOAuth.user_id == current_user.id,
UserOAuth.deleted_at.is_(None),
)
if user_id:
query = query.join(
UserOAuth,
MaterialCost.oauth_id == UserOAuth.id,
).where(UserOAuth.user_id == user_id)
if advertiser_id:
query = query.where(MaterialCost.advertiser_id == advertiser_id)
if consume_date and len(consume_date) >= 2:
start_date_obj = datetime.strptime(consume_date[0], "%Y-%m-%d").date()
end_date_obj = datetime.strptime(consume_date[1], "%Y-%m-%d").date()
query = query.where(MaterialCost.consume_date >= start_date_obj)
query = query.where(MaterialCost.consume_date <= end_date_obj)
result = await db.execute(
query.order_by(MaterialCost.consume_date.desc())
.offset(offset)
.limit(page_size)
)
consumptions = result.scalars().all()
count_query = select(func.count(MaterialCost.id)).where(MaterialCost.deleted_at.is_(None))
if current_user:
count_query = count_query.join(
UserOAuth,
MaterialCost.oauth_id == UserOAuth.id,
).where(
UserOAuth.user_id == current_user.id,
UserOAuth.deleted_at.is_(None),
)
if user_id:
count_query = count_query.join(
UserOAuth,
MaterialCost.oauth_id == UserOAuth.id,
).where(UserOAuth.user_id == user_id)
if advertiser_id:
count_query = count_query.where(MaterialCost.advertiser_id == advertiser_id)
if consume_date and len(consume_date) >= 2:
start_date_obj = datetime.strptime(consume_date[0], "%Y-%m-%d").date()
end_date_obj = datetime.strptime(consume_date[1], "%Y-%m-%d").date()
count_query = count_query.where(MaterialCost.consume_date >= start_date_obj)
count_query = count_query.where(MaterialCost.consume_date <= end_date_obj)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
return consumptions, total
def format_consumption_response(consumptions: List[MaterialCost], include_oauth_id: bool = False) -> List[dict]:
result = []
for consumption in consumptions:
item = {
"id": consumption.id,
"advertiser_id": consumption.advertiser_id,
"material_id": consumption.material_id,
"consume_date": consumption.consume_date.isoformat() if consumption.consume_date else None,
"stat_cost": consumption.stat_cost,
"show_cnt": consumption.show_cnt,
"cpm_platform": consumption.cpm_platform,
"click_cnt": consumption.click_cnt,
"ctr": consumption.ctr,
"cpc_platform": consumption.cpc_platform,
"convert_cnt": consumption.convert_cnt,
"conversion_cost": consumption.conversion_cost,
"conversion_rate": consumption.conversion_rate,
"deep_convert_cnt": consumption.deep_convert_cnt,
"deep_convert_cost": consumption.deep_convert_cost,
"deep_convert_rate": consumption.deep_convert_rate,
"active": consumption.active,
"active_cost": consumption.active_cost,
"active_rate": consumption.active_rate,
"active_register": consumption.active_register,
"active_register_cost": consumption.active_register_cost,
"active_register_rate": consumption.active_register_rate,
"attribution_next_day_open_cnt": consumption.attribution_next_day_open_cnt,
"attribution_next_day_open_cost": consumption.attribution_next_day_open_cost,
"attribution_next_day_open_rate": consumption.attribution_next_day_open_rate,
"active_pay": consumption.active_pay,
"active_pay_cost": consumption.active_pay_cost,
"active_pay_rate": consumption.active_pay_rate,
"phone": consumption.phone,
"form": consumption.form,
"download_start": consumption.download_start,
"form_submit": consumption.form_submit,
"button": consumption.button,
"view": consumption.view,
"message": consumption.message,
"consult": consumption.consult,
"consult_effective": consumption.consult_effective,
"shopping": consumption.shopping,
"customer_effective": consumption.customer_effective,
"attribution_game_in_app_ltv_1day": consumption.attribution_game_in_app_ltv_1day,
"attribution_game_in_app_roi_1day": consumption.attribution_game_in_app_roi_1day,
"loan_completion": consumption.loan_completion,
"loan_completion_cost": consumption.loan_completion_cost,
"loan_completion_rate": consumption.loan_completion_rate,
"loan_credit": consumption.loan_credit,
"loan_credit_cost": consumption.loan_credit_cost,
"loan_credit_rate": consumption.loan_credit_rate,
"in_app_order_gmv": consumption.in_app_order_gmv,
"in_app_order_roi": consumption.in_app_order_roi,
"in_app_pay_gmv": consumption.in_app_pay_gmv,
"in_app_pay_roi": consumption.in_app_pay_roi,
"total_play": consumption.total_play,
"valid_play": consumption.valid_play,
"valid_play_cost": consumption.valid_play_cost,
"valid_play_rate": consumption.valid_play_rate,
"valid_play_of_mille": consumption.valid_play_of_mille,
"valid_play_cost_of_mille": consumption.valid_play_cost_of_mille,
"average_play_time_per_play": consumption.average_play_time_per_play,
"play_over_rate": consumption.play_over_rate,
"dy_like": consumption.dy_like,
"dy_comment": consumption.dy_comment,
"dy_share": consumption.dy_share,
"report_cnt": consumption.report_cnt,
"created_at": consumption.created_at,
}
if include_oauth_id:
item["oauth_id"] = consumption.oauth_id
result.append(item)
return result