This commit is contained in:
2026-07-06 13:39:08 +08:00
parent 16b3d223c1
commit c68215c177
33 changed files with 3250 additions and 732 deletions
+1 -1
View File
@@ -159,4 +159,4 @@ def extract_error_message(exc: Exception, service_type: str = "video") -> str:
return f"{service_type}生成失败: {code}"
except (json.JSONDecodeError, TypeError, ValueError):
pass
return raw[:200] if len(raw) > 200 else raw
return raw[:5000] if len(raw) > 5000 else raw
+2 -2
View File
@@ -30,7 +30,7 @@ def _log_image_request(engine: ProviderImageEngineLike, record_id: str, request_
today = datetime.now().strftime(LOG_DATE_FORMAT)
log_file = os.path.join(LOG_DIR, f"{today}.log")
request_str = json.dumps(request_data, ensure_ascii=False)
request_encrypted = encrypt_data(request_data)
request_encrypted = encrypt_data(request_data, True)
entry = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"type": "image_gen_request",
@@ -54,7 +54,7 @@ def _log_image_response(record_id: str, response_data: dict, error: str | None =
os.makedirs(LOG_DIR, exist_ok=True)
today = datetime.now().strftime(LOG_DATE_FORMAT)
log_file = os.path.join(LOG_DIR, f"{today}.log")
response_encrypted = encrypt_data(response_data) if response_data else ""
response_encrypted = encrypt_data(response_data, True) if response_data else ""
entry = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"type": "image_gen_response",
+2 -2
View File
@@ -37,8 +37,8 @@ def _log_ai_request_response(config, request_data: dict, response_data: dict | N
os.makedirs(LOG_DIR, exist_ok=True)
today = datetime.now().strftime(LOG_DATE_FORMAT)
log_file = os.path.join(LOG_DIR, f"{today}.log")
request_encrypted = encrypt_data(_sanitize_for_log(request_data))
response_encrypted = encrypt_data(_sanitize_for_log(response_data)) if response_data else ""
request_encrypted = encrypt_data(_sanitize_for_log(request_data), True)
response_encrypted = encrypt_data(_sanitize_for_log(response_data), True) if response_data else ""
entry = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"model_name": config.name,
+5 -2
View File
@@ -18,10 +18,13 @@ ENCRYPTION_KEY = b'videogen@202605!'
def encrypt_data(data: dict) -> str:
def encrypt_data(data: dict, is_encrypt: bool = False) -> str:
data_str = json.dumps(data, ensure_ascii=False, sort_keys=True)
if is_encrypt:
return data_str
data_bytes = data_str.encode("utf-8")
padder = padding.PKCS7(128).padder()
padded_data = padder.update(data_bytes) + padder.finalize()
@@ -0,0 +1,379 @@
from typing import Optional, List, Tuple
from datetime import datetime
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.models.user_oauth import UserOAuth
from app.models.user_oauth_account import UserOAuthAccount
from app.models.resources_material import ResourcesMaterial
from app.models.pre_test_template import PreTestTemplate
from app.models.upload_task import UploadTask
from app.utils.datetime_util import BEIJING_TZ, datetime_to_db_tz_str, db_tz_str_to_datetime, parse_date_range
async def get_oauth_list(
id: Optional[str] = None,
db: AsyncSession = None,
phone: Optional[str] = None,
account_id: Optional[str] = None,
account_userid: Optional[str] = None,
created_at: Optional[datetime] = None,
appid: Optional[str] = None,
open_type: Optional[int] = None,
page: int = 1,
page_size: int = 10,
) -> Tuple[List[UserOAuth], int]:
offset = (page - 1) * page_size
query = select(UserOAuth).where(UserOAuth.deleted_at.is_(None))
if id:
query = query.where(UserOAuth.id == id)
if phone:
query = query.join(User, User.id == UserOAuth.user_id).where(User.phone == phone)
if account_id:
query = query.where(UserOAuth.account_id == account_id)
if account_userid:
query = query.where(UserOAuth.account_userid == account_userid)
# 【改动3】适配入参 created_at=["2025-01-01","2026-01-01"] 字符串日期场景
if created_at and len(created_at) == 2:
try:
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
query = query.where(
UserOAuth.created_at >= start_dt,
UserOAuth.created_at <= end_dt
)
except ValueError:
# 捕获日期格式错误,非法时间直接不附加该查询条件
pass
if appid:
query = query.where(UserOAuth.appid == appid)
if open_type:
query = query.where(UserOAuth.open_type == open_type)
count_query = select(func.count(UserOAuth.id)).where(UserOAuth.deleted_at.is_(None))
if phone:
count_query = count_query.join(User, User.id == UserOAuth.user_id).where(User.phone == phone)
if account_id:
count_query = count_query.where(UserOAuth.account_id == account_id)
if account_userid:
count_query = count_query.where(UserOAuth.account_userid == account_userid)
if created_at:
try:
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
count_query = count_query.where(
UserOAuth.created_at >= start_dt,
UserOAuth.created_at <= end_dt
)
except ValueError:
# 捕获日期格式错误,非法时间直接不附加该查询条件
pass
if appid:
count_query = count_query.where(UserOAuth.appid == appid)
if open_type:
count_query = count_query.where(UserOAuth.open_type == open_type)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
query = query.order_by(UserOAuth.id.desc()).offset(offset).limit(page_size)
result = await db.execute(query)
list = [];
for oauth in result.scalars().all():
user_query = select(User).where(User.id == oauth.user_id)
user_result = await db.execute(user_query)
user = user_result.scalar_one()
if user:
phone = user.phone
else:
phone = None
item = {
"id": oauth.id,
"account_id": oauth.account_id,
"account_name": oauth.account_name,
"account_role": oauth.account_role,
"account_username": oauth.account_username,
"account_userid": oauth.account_userid,
"user_id": oauth.user_id,
"user_phone": phone,
"open_type": oauth.open_type,
"port_type": oauth.port_type,
"appid": oauth.appid,
"access_token": oauth.access_token,
"refresh_token": oauth.refresh_token,
"access_token_expired": oauth.access_token_expired,
"refresh_token_expired": oauth.refresh_token_expired,
"created_at": oauth.created_at,
"updated_at": oauth.updated_at,
}
list.append(item)
return list, total
async def get_material_list(
db: AsyncSession,
phone: Optional[str] = None,
id: Optional[str] = None,
resource_type: Optional[str] = None,
advertiser_id: Optional[str] = None,
material_id: Optional[str] = None,
upload_id: Optional[str] = None,
created_at: Optional[datetime] = None,
status: Optional[str] = None,
page: int = 1,
page_size: int = 20,
) -> Tuple[List[ResourcesMaterial], int]:
offset = (page - 1) * page_size
query = select(ResourcesMaterial).where(ResourcesMaterial.deleted_at.is_(None))
if phone:
query = query.join(UserOAuth, UserOAuth.id == ResourcesMaterial.oauth_id).join(User, User.id == UserOAuth.user_id).where(User.phone == phone)
if id:
query = query.where(ResourcesMaterial.id == id)
if resource_type:
query = query.where(ResourcesMaterial.resource_type == resource_type)
if advertiser_id:
query = query.where(ResourcesMaterial.advertiser_id == advertiser_id)
if material_id:
query = query.where(ResourcesMaterial.material_id == material_id)
if upload_id:
query = query.where(ResourcesMaterial.upload_id == upload_id)
if created_at:
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
query = query.where(
ResourcesMaterial.created_at >= start_dt,
ResourcesMaterial.created_at <= end_dt
)
if status:
query = query.where(ResourcesMaterial.status == status)
count_query = select(func.count(ResourcesMaterial.id)).where(ResourcesMaterial.deleted_at.is_(None))
if phone:
count_query = count_query.join(UserOAuth, UserOAuth.id == ResourcesMaterial.oauth_id).join(User, User.id == UserOAuth.user_id).where(User.phone == phone)
if id:
count_query = count_query.where(ResourcesMaterial.id == id)
if resource_type:
count_query = count_query.where(ResourcesMaterial.resource_type == resource_type)
if advertiser_id:
count_query = count_query.where(ResourcesMaterial.advertiser_id == advertiser_id)
if material_id:
count_query = count_query.where(ResourcesMaterial.material_id == material_id)
if upload_id:
count_query = count_query.where(ResourcesMaterial.upload_id == upload_id)
if created_at:
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
count_query = count_query.where(
ResourcesMaterial.created_at >= start_dt,
ResourcesMaterial.created_at <= end_dt
)
if status:
count_query = count_query.where(ResourcesMaterial.status == status)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
query = query.order_by(ResourcesMaterial.id.desc()).offset(offset).limit(page_size)
result = await db.execute(query)
list = []
for item in result.scalars().all():
user_query = select(User).where(User.id == item.user_id)
user_result = await db.execute(user_query)
user = user_result.scalar_one()
if user:
phone = user.phone
else:
phone = None
item = {
"id": item.id,
"oauth_id": item.oauth_id,
"user_phone": phone,
"advertiser_id": item.advertiser_id,
"target_table": item.target_table,
"target_id": item.target_id,
"material_id": item.material_id,
"upload_id": item.upload_id,
"resource_type": item.resource_type,
"user_id": item.user_id,
"created_at": item.created_at,
"updated_at": item.updated_at,
"task_id": item.task_id,
"note": item.note,
"status": item.status,
"pre_result": item.pre_result,
"pre_test_template_id": item.pre_test_template_id,
}
list.append(item)
return list, total
async def get_pre_test_template_list(
db: AsyncSession,
phone: Optional[str] = None,
id: Optional[str] = None,
created_at: Optional[datetime] = None,
page: int = 1,
page_size: int = 10,
) -> Tuple[List[PreTestTemplate], int]:
offset = (page - 1) * page_size
query = select(PreTestTemplate).where(PreTestTemplate.deleted_at.is_(None))
if phone:
query = query.join(User, User.id == PreTestTemplate.user_id).where(User.phone == phone)
if id:
query = query.where(PreTestTemplate.id == id)
if created_at:
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
query = query.where(
PreTestTemplate.created_at >= start_dt,
PreTestTemplate.created_at <= end_dt
)
count_query = select(func.count(PreTestTemplate.id)).where(PreTestTemplate.deleted_at.is_(None))
if phone:
count_query = count_query.join(User, User.id == PreTestTemplate.user_id).where(User.phone == phone)
if id:
count_query = count_query.where(PreTestTemplate.id == id)
if created_at:
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
count_query = count_query.where(
PreTestTemplate.created_at >= start_dt,
PreTestTemplate.created_at <= end_dt
)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
query = query.order_by(PreTestTemplate.id.desc()).offset(offset).limit(page_size)
result = await db.execute(query)
list = []
for item in result.scalars().all():
user_query = select(User).where(User.id == item.user_id)
user_result = await db.execute(user_query)
user = user_result.scalar_one()
if user:
phone = user.phone
else:
phone = None
item = {
"id": item.id,
"name": item.name,
"user_phone": phone,
"user_id": item.user_id,
"platform": item.platform,
"external_action": item.external_action,
"cpa_bid": item.cpa_bid,
"audience_gender": item.audience_gender,
"audience_age": item.audience_age,
"audience_region": item.audience_region,
"audience_network": item.audience_network,
"cus_name": item.cus_name,
"pricing_type": item.pricing_type,
"cost_cap": item.cost_cap,
"target_cost": item.target_cost,
"nobid": item.nobid,
"cpc_bid": item.cpc_bid,
"budget": item.budget,
"is_default": item.is_default,
"created_at": item.created_at,
"updated_at": item.updated_at,
"note": item.note,
}
list.append(item)
return list, total
async def get_upload_task_list(
db: AsyncSession,
phone: Optional[str] = None,
id: Optional[str] = None,
created_at: Optional[List[str]] = None,
advertiser_id: Optional[str] = None,
status: Optional[int] = None,
resource_id: Optional[str] = None,
page: int = 1,
page_size: int = 10,
) -> Tuple[List[UploadTask], int]:
offset = (page - 1) * page_size
query = select(UploadTask).where(UploadTask.deleted_at.is_(None))
if phone:
query = query.join(User, User.id == UploadTask.user_id).where(User.phone == phone)
if id:
query = query.where(UploadTask.id == id)
if created_at and len(created_at) >= 2:
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
query = query.where(
UploadTask.created_at >= start_dt,
UploadTask.created_at <= end_dt
)
if advertiser_id:
query = query.where(UploadTask.advertiser_id == advertiser_id)
if status:
query = query.where(UploadTask.status == status)
if resource_id:
query = query.where(UploadTask.resource_id == resource_id)
count_query = select(func.count(UploadTask.id)).where(UploadTask.deleted_at.is_(None))
if phone:
count_query = count_query.join(User, User.id == UploadTask.user_id).where(User.phone == phone)
if id:
count_query = count_query.where(UploadTask.id == id)
if created_at and len(created_at) >= 2:
# 直接用datetime范围匹配,不走func.date,保留时分秒精度,命中索引
start_dt, end_dt = parse_date_range(created_at, is_timezone=True)
count_query = count_query.where(
UploadTask.created_at >= start_dt,
UploadTask.created_at <= end_dt
)
if advertiser_id:
count_query = count_query.where(UploadTask.advertiser_id == advertiser_id)
if status:
count_query = count_query.where(UploadTask.status == status)
if resource_id:
count_query = count_query.where(UploadTask.resource_id == resource_id)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
query = query.order_by(UploadTask.id.desc()).offset(offset).limit(page_size)
result = await db.execute(query)
list = []
for item in result.scalars().all():
user_query = select(User).where(User.id == item.user_id)
user_result = await db.execute(user_query)
user = user_result.scalar_one()
if user:
phone = user.phone
else:
phone = None
item = {
"id": item.id,
"user_phone": phone,
"user_id": item.user_id,
"advertiser_id": item.advertiser_id,
"resource_id": item.resource_id,
"status": item.status,
"note": item.note,
"oauth_id": item.oauth_id,
"created_at": item.created_at,
"updated_at": item.updated_at,
"other_info": item.other_info,
}
list.append(item)
return list, total
+208 -203
View File
@@ -1,20 +1,22 @@
import random
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
import httpx
from sqlalchemy import select, func
from sqlalchemy import select, func, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
# 引入项目统一北京时间时区
from app.utils.datetime_util import BEIJING_TZ
from app.models.user_oauth import UserOAuth
from app.models.user_oauth_app import UserOAuthApp
from app.utils.id_gen import generate_id
from app.tasks.token_refresh_task import _update_redis_token
from app.tasks.token_refresh_task import _update_redis_token, _delete_redis_token
#随机获取一个可用的应用配置
# 随机获取一个可用的应用配置
async def get_available_app(open_type: int, db: AsyncSession) -> dict:
# 从 user_oauth_app 表查询可用应用
# 过滤条件:open_type 匹配、status=1(正常)、deleted_at is None
if not isinstance(open_type, int):
raise ValueError("open_type必须为整数类型")
result = await db.execute(
select(UserOAuthApp).where(
UserOAuthApp.open_type == open_type,
@@ -30,8 +32,6 @@ async def get_available_app(open_type: int, db: AsyncSession) -> dict:
available_apps = []
for app in apps:
app_id = app.app_id
# 查询该应用当前授权数量
count_result = await db.execute(
select(func.count(UserOAuth.id)).where(
UserOAuth.appid == app_id,
@@ -39,8 +39,6 @@ async def get_available_app(open_type: int, db: AsyncSession) -> dict:
)
)
count = count_result.scalar() or 0
# 检查是否达到最大授权数
max_users = app.max_count
if count < max_users:
available_apps.append({
@@ -59,24 +57,21 @@ async def get_available_app(open_type: int, db: AsyncSession) -> dict:
async def build_oauth_url(open_type: int, user_id: str, db: AsyncSession) -> str:
if open_type == 1:
#千川
return await _build_jl_oauth_url(open_type, user_id, db)
elif open_type in [2, 3]:
#广告,本地推
return await _build_jl_oauth_url(2, user_id, db)
elif open_type == 5:
#快手代理商
return "无配置"
elif open_type == 9:
#腾讯营销K2
return "无配置"
elif open_type == 10:
#腾讯营销K3
return "无配置"
else:
raise ValueError(f"不支持的应用类型: {app_type}")
# 修复BUG:变量名错误 app_type -> open_type
raise ValueError(f"不支持的应用类型: {open_type}")
#千川授权链接构建
# 千川授权链接构建
async def _build_jl_oauth_url(open_type: int, user_id: str, db: AsyncSession) -> str:
app = await get_available_app(open_type, db)
app_id = app.get("app_id")
@@ -92,221 +87,232 @@ async def _build_jl_oauth_url(open_type: int, user_id: str, db: AsyncSession) ->
async def get_token(code: str, user_id: str, app_id: str, db: AsyncSession) -> dict:
#1.根据app_id查询应用配置
if not all([code, user_id, app_id]):
raise ValueError("code、user_id、app_id不能为空")
result = await db.execute(
select(UserOAuthApp).where(UserOAuthApp.app_id == app_id)
select(UserOAuthApp).where(
UserOAuthApp.app_id == app_id,
UserOAuthApp.deleted_at.is_(None),
UserOAuthApp.status == 1
)
)
app = result.scalar_one_or_none()
if not app:
raise ValueError("应用配置不存在")
raise ValueError("应用配置不存在或已禁用")
open_type = app.open_type
secret = app.secret
if open_type == 1 or open_type == 2:
#千川或广告
if open_type in (1, 2):
return await get_juliang_token(app_id, secret, code, open_type, user_id, db)
elif open_type == 5:
#快手代理商
return await get_kuaishou_token(app_id, secret, code, open_type, user_id, db)
elif open_type == 9 or open_type == 10:
#腾讯营销K2或腾讯营销K3
elif open_type in (9, 10):
return await get_tencent_token(app_id, secret, code, open_type, user_id, db)
else:
raise ValueError(f"不支持的应用类型: {open_type}")
async def get_juliang_token(app_id: str, secret: str, code: str, open_type: int, user_id: str, db: AsyncSession) -> dict:
async with httpx.AsyncClient() as client:
#1.请求token
url = "https://api.oceanengine.com/open_api/oauth2/access_token/"
response = await client.post(
url,
json={
"app_id": app_id,
"secret": secret,
"auth_code": code,
},
)
response.raise_for_status()
data = response.json()
if data.get("code") != 0:
raise ValueError(data.get("message", "获取token失败")+f",错误信息:{data.get('message', '')}")
data = data.get("data", {})
access_token = data.get("access_token", "")
refresh_token = data.get("refresh_token", "")
expires_in = datetime.now(timezone.utc) + timedelta(seconds=data.get("expires_in", 0))
refresh_token_expires_in = datetime.now(timezone.utc) + timedelta(seconds=data.get("refresh_token_expires_in", 0))
#2.获取已授权角色账户,一个授权可能有多个角色账户
url = "https://api.oceanengine.com/open_api/oauth2/advertiser/get/"
response = await client.get(
url,
headers={"Access-Token": access_token},
)
response.raise_for_status()
data = response.json()
if data.get("code") != 0:
raise ValueError(data.get("message", "获取授权账户失败")+f",错误信息:{data.get('message', '')}")
data = data.get("data", {})
account_list = data.get("list", [])
#3.获取已授权登录信息
url = "https://api.oceanengine.com/open_api/2/user/info/"
response = await client.get(
url,
headers={"Access-Token": access_token},
)
response.raise_for_status()
data = response.json()
if data.get("code") != 0:
raise ValueError(data.get("message", "获取已授权登录信息失败")+f",错误信息:{data.get('message', '')}")
data = data.get("data", {})
account_username = data.get("email", "")
account_userid = str(data.get("id", ""))
material_auth_status = data.get("material_auth_status", False)
#4.根据登录信息判断是否新增token或更新token,不同的登录信息对应不同的token,然后更新数据库
#查询emailappiduser_id是否存在已授权记录
oauth = await db.execute(
select(UserOAuth)
.where(UserOAuth.account_username == account_username, UserOAuth.account_userid == account_userid, UserOAuth.appid == app_id, UserOAuth.user_id == user_id)
.limit(1)
)
oauth = oauth.scalar_one_or_none()
if not oauth:
new_oauth_ids = []
for account in account_list:
oauth_id = generate_id()
new_oauth_ids.append(oauth_id)
#新增授权记录
db.add(UserOAuth(
id=oauth_id,
account_id = str(account.get("account_id", "")),
account_name = account.get("account_name", ""),
account_role = account.get("account_role", ""),
account_username = account_username,
account_userid = account_userid,
user_id = user_id,
appid = app_id,
open_type = open_type,
port_type = 1,
access_token = access_token,
access_token_expired = expires_in,
refresh_token = refresh_token,
refresh_token_expired = refresh_token_expires_in,
material_auth_status = material_auth_status,
))
await db.commit()
for oauth_id in new_oauth_ids:
await _update_redis_token(oauth_id, access_token, expires_in)
else:
# 查询现有授权记录(未删除的)
existing_accounts = await db.execute(
select(UserOAuth).where(
timeout = httpx.Timeout(30.0)
try:
async with httpx.AsyncClient(timeout=timeout) as client:
# 1. 获取token
url = "https://api.oceanengine.com/open_api/oauth2/access_token/"
resp = await client.post(
url,
json={
"app_id": app_id,
"secret": secret,
"auth_code": code,
},
)
resp.raise_for_status()
data = resp.json()
if data.get("code") != 0:
msg = data.get("message", "获取token失败")
raise ValueError(f"获取token失败:{msg}")
resp_data = data.get("data", {})
access_token = resp_data.get("access_token", "")
refresh_token = resp_data.get("refresh_token", "")
expires_sec = resp_data.get("expires_in", 7200)
refresh_expires_sec = resp_data.get("refresh_token_expires_in", 30 * 24 * 3600)
# 【修复:统一使用北京时间计算过期时间】
now_beijing = datetime.now(tz=BEIJING_TZ)
access_expired = now_beijing + timedelta(seconds=expires_sec)
refresh_expired = now_beijing + timedelta(seconds=refresh_expires_sec)
# 2. 获取授权广告账户列表
resp = await client.get(
"https://api.oceanengine.com/open_api/oauth2/advertiser/get/",
headers={"Access-Token": access_token},
)
resp.raise_for_status()
data = resp.json()
if data.get("code") != 0:
raise ValueError(f"获取已授权账户失败:{data.get('message')}")
account_list = data.get("data", {}).get("list", [])
# 3. 获取登录用户信息
resp = await client.get(
"https://api.oceanengine.com/open_api/2/user/info/",
headers={"Access-Token": access_token},
)
resp.raise_for_status()
data = resp.json()
if data.get("code") != 0:
raise ValueError(f"获取登录信息失败:{data.get('message')}")
user_info = data.get("data", {})
account_username = user_info.get("email", "")
account_userid = str(user_info.get("id", ""))
material_auth_status = user_info.get("material_auth_status", False)
# 4. 查询当前用户+应用+登录账号下的授权记录
oauth_query = select(UserOAuth).where(
UserOAuth.account_username == account_username,
UserOAuth.account_userid == account_userid,
UserOAuth.appid == app_id,
UserOAuth.user_id == user_id,
UserOAuth.deleted_at.is_(None)
)
oauth_exist = await db.execute(oauth_query.limit(1))
oauth_exist = oauth_exist.scalar_one_or_none()
if not oauth_exist:
# 新增授权
new_oauth_ids = []
for account in account_list:
oauth_id = generate_id()
new_oauth_ids.append(oauth_id)
db.add(UserOAuth(
id=oauth_id,
account_id=str(account.get("account_id", "")),
account_name=account.get("account_name", ""),
account_role=account.get("account_role", ""),
account_username=account_username,
account_userid=account_userid,
user_id=user_id,
appid=app_id,
open_type=open_type,
port_type=1,
access_token=access_token,
access_token_expired=access_expired,
refresh_token=refresh_token,
refresh_token_expired=refresh_expired,
material_auth_status=material_auth_status,
))
await db.commit()
# 批量写入Redis
for oauth_id in new_oauth_ids:
await _update_redis_token(oauth_id, access_token, access_expired)
else:
# 查询当前所有有效授权账户
exist_query = select(UserOAuth).where(
UserOAuth.account_username == account_username,
UserOAuth.account_userid == account_userid,
UserOAuth.appid == app_id,
UserOAuth.user_id == user_id,
UserOAuth.deleted_at.is_(None),
)
)
existing_accounts = {acc.account_id: acc for acc in existing_accounts.scalars().all()}
exist_res = await db.execute(exist_query)
exist_map = {item.account_id: item for item in exist_res.scalars().all()}
# 新的账户列表
new_account_ids = {str(account.get("account_id")) for account in account_list}
old_account_ids = set(existing_accounts.keys())
new_account_ids = {str(acc.get("account_id")) for acc in account_list}
old_account_ids = set(exist_map.keys())
update_redis_ids = []
# 需要更新Redis的oauth_id列表
update_redis_ids = []
# 下线的账户:软删除 + 清理Redis脏缓存
for del_account_id in old_account_ids - new_account_ids:
del_oauth = exist_map[del_account_id]
del_oauth.deleted_at = now_beijing
await _delete_redis_token(del_oauth.id)
# 1. 软删除已消失的账户
for account_id in old_account_ids - new_account_ids:
existing_accounts[account_id].deleted_at = datetime.now(timezone.utc)
# 2. 更新或新增账户
for account in account_list:
account_id = str(account.get("account_id", ""))
if account_id in existing_accounts:
# 更新现有记录
existing_oauth = existing_accounts[account_id]
existing_oauth.account_name = account.get("account_name", "")
existing_oauth.account_role = account.get("account_role", "")
existing_oauth.access_token = access_token
existing_oauth.access_token_expired = expires_in
existing_oauth.refresh_token = refresh_token
existing_oauth.refresh_token_expired = refresh_token_expires_in
existing_oauth.material_auth_status = material_auth_status
update_redis_ids.append(existing_oauth.id)
else:
# 新增记录
oauth_id = generate_id()
update_redis_ids.append(oauth_id)
db.add(UserOAuth(
# 更新/新增当前授权账户
for account in account_list:
aid = str(account.get("account_id", ""))
if aid in exist_map:
item = exist_map[aid]
item.account_name = account.get("account_name", "")
item.account_role = account.get("account_role", "")
item.access_token = access_token
item.access_token_expired = access_expired
item.refresh_token = refresh_token
item.refresh_token_expired = refresh_expired
item.material_auth_status = material_auth_status
update_redis_ids.append(item.id)
else:
oauth_id = generate_id()
update_redis_ids.append(oauth_id)
db.add(UserOAuth(
id=oauth_id,
account_id=str(account_id),
account_id=aid,
account_name=account.get("account_name", ""),
account_role=account.get("account_role", ""),
account_username=account_username,
account_userid = account_userid,
account_userid=account_userid,
user_id=user_id,
appid=app_id,
open_type=open_type,
port_type=1,
access_token = access_token,
access_token_expired=expires_in,
access_token=access_token,
access_token_expired=access_expired,
refresh_token=refresh_token,
refresh_token_expired=refresh_token_expires_in,
refresh_token_expired=refresh_expired,
material_auth_status=material_auth_status,
))
await db.commit()
for oauth_id in update_redis_ids:
await _update_redis_token(oauth_id, access_token, expires_in)
await db.commit()
# 更新有效账号缓存
for oid in update_redis_ids:
await _update_redis_token(oid, access_token, access_expired)
#5.本次更新成功以后,判断是否有其他同一个appid,同一个授权登录账号的授权记录,如果有,则更新token信息
from sqlalchemy import update
related_oauths = await db.execute(
select(UserOAuth).where(
# 5. 同登录账号、同应用、其他用户下的授权批量同步最新token
related_query = select(UserOAuth).where(
UserOAuth.account_username == account_username,
UserOAuth.account_userid == account_userid,
UserOAuth.appid == app_id,
UserOAuth.user_id != user_id,
UserOAuth.deleted_at.is_(None),
)
)
related_oauths = related_oauths.scalars().all()
if related_oauths:
await db.execute(
update(UserOAuth).where(
UserOAuth.account_username == account_username,
UserOAuth.account_userid == account_userid,
UserOAuth.appid == app_id,
UserOAuth.user_id != user_id,
UserOAuth.deleted_at.is_(None),
).values(
access_token=access_token,
access_token_expired=expires_in,
refresh_token=refresh_token,
refresh_token_expired=refresh_token_expires_in,
material_auth_status=material_auth_status,
related_res = await db.execute(related_query)
related_list = related_res.scalars().all()
if related_list:
await db.execute(
update(UserOAuth).where(
UserOAuth.account_username == account_username,
UserOAuth.account_userid == account_userid,
UserOAuth.appid == app_id,
UserOAuth.user_id != user_id,
UserOAuth.deleted_at.is_(None),
).values(
access_token=access_token,
access_token_expired=access_expired,
refresh_token=refresh_token,
refresh_token_expired=refresh_expired,
material_auth_status=material_auth_status,
)
)
)
await db.commit()
for related_oauth in related_oauths:
await _update_redis_token(related_oauth.id, access_token, expires_in)
await db.commit()
for item in related_list:
await _update_redis_token(item.id, access_token, access_expired)
return {"message": "授权成功"}
except httpx.HTTPError as e:
raise ValueError(f"第三方接口请求异常:{str(e)}")
except Exception as e:
raise ValueError(f"授权处理异常:{str(e)}")
return {"message": "授权成功"}
async def get_kuaishou_token(app_id: str, secret: str, code: str, oauth_type: int, user_id: str, db: AsyncSession) -> dict:
return "未配置"
return {"message": "快手渠道暂未实现授权逻辑"}
async def get_tencent_token(app_id: str, secret: str, code: str, oauth_type: int, user_id: str, db: AsyncSession) -> str:
return "未配置"
return "腾讯营销渠道暂未实现授权逻辑"
async def get_oauth_list(
@@ -318,36 +324,35 @@ async def get_oauth_list(
page: int = 1,
page_size: int = 10,
) -> dict:
if page < 1:
page = 1
if page_size < 1:
page_size = 10
# 分页参数容错
page = max(page, 1)
page_size = max(min(page_size, 100), 1)
query = select(UserOAuth).where(
base_where = [
UserOAuth.user_id == user_id,
UserOAuth.deleted_at.is_(None),
)
]
if account_userid:
query = query.where(UserOAuth.account_userid == account_userid)
base_where.append(UserOAuth.account_userid == account_userid)
if open_type:
query = query.where(UserOAuth.open_type == open_type)
base_where.append(UserOAuth.open_type == open_type)
if account_id:
query = query.where(UserOAuth.account_id == account_id)
base_where.append(UserOAuth.account_id == account_id)
query = query.order_by(UserOAuth.created_at.desc())
# 统计总条数(优化:使用count,避免全量查询)
count_stmt = select(func.count(UserOAuth.id)).where(*base_where)
total = await db.scalar(count_stmt) or 0
total_result = await db.execute(query.with_only_columns(UserOAuth.id))
total = len(total_result.scalars().all())
offset = (page - 1) * page_size
query = query.offset(offset).limit(page_size)
result = await db.execute(query)
oauth_list = result.scalars().all()
# 分页查询数据
data_stmt = select(UserOAuth).where(*base_where)\
.order_by(UserOAuth.created_at.desc())\
.offset((page - 1) * page_size)\
.limit(page_size)
result = await db.execute(data_stmt)
data_list = result.scalars().all()
return {
"data": oauth_list,
"data": data_list,
"total": total,
"page": page,
"page_size": page_size,
+2 -2
View File
@@ -30,7 +30,7 @@ def _log_video_request(engine: ProviderVideoEngineLike, record_id: str, request_
today = datetime.now().strftime(LOG_DATE_FORMAT)
log_file = os.path.join(LOG_DIR, f"{today}.log")
request_str = json.dumps(request_data, ensure_ascii=False)
request_encrypted = encrypt_data(request_data)
request_encrypted = encrypt_data(request_data, True)
entry = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"type": "video_gen_request",
@@ -54,7 +54,7 @@ def _log_video_response(record_id: str, response_data: dict, error: str | None =
os.makedirs(LOG_DIR, exist_ok=True)
today = datetime.now().strftime(LOG_DATE_FORMAT)
log_file = os.path.join(LOG_DIR, f"{today}.log")
response_encrypted = encrypt_data(response_data) if response_data else ""
response_encrypted = encrypt_data(response_data, True) if response_data else ""
entry = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),