1
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""用户操作日志上报:仅特定账号(如 256kb@gmail.com)开启,用于排查 bug"""
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.deps import Identity, require_user
|
||||
from app.core import errors
|
||||
from app.core.response import BizException, ok
|
||||
from app.models.user import User
|
||||
from app.models.user_action_log import UserActionLog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
# 仅这些邮箱的用户允许上报操作日志(白名单,避免给所有用户上传)
|
||||
ALLOWED_EMAILS = {"256kb@gmail.com"}
|
||||
|
||||
# 单条 detail 最大长度(防御性截断)
|
||||
MAX_DETAIL_LEN = 800
|
||||
# 单次批量最大条数
|
||||
MAX_BATCH = 100
|
||||
|
||||
|
||||
class ActionLogItem(BaseModel):
|
||||
action: str = Field(..., max_length=50)
|
||||
page: str = Field("", max_length=100)
|
||||
detail: str = Field("", max_length=MAX_DETAIL_LEN)
|
||||
device_info: str = Field("", max_length=200)
|
||||
app_version: str = Field("", max_length=20)
|
||||
# 客户端本地时间戳(毫秒),用于在服务端时钟漂移时仍能排序
|
||||
client_ts: int = 0
|
||||
|
||||
|
||||
class ActionLogBatch(BaseModel):
|
||||
items: list[ActionLogItem] = Field(..., max_length=MAX_BATCH)
|
||||
|
||||
|
||||
@router.post("/logs/action")
|
||||
async def report_action_log(
|
||||
body: ActionLogBatch,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
identity: Identity = Depends(require_user),
|
||||
):
|
||||
"""接收 App 上报的用户操作日志。仅白名单邮箱可上报,其他账号返回 403 不上报"""
|
||||
user = (await db.execute(
|
||||
select(User).where(User.id == identity.user_id)
|
||||
)).scalar_one_or_none()
|
||||
if not user or user.email not in ALLOWED_EMAILS:
|
||||
# 静默拒绝:不报错,不写入,避免在普通用户端暴露此通道存在
|
||||
raise BizException(errors.FORBIDDEN, "forbidden")
|
||||
|
||||
rows = [
|
||||
UserActionLog(
|
||||
user_id=user.id,
|
||||
action=item.action[:50],
|
||||
page=item.page[:100],
|
||||
detail=item.detail[:MAX_DETAIL_LEN],
|
||||
device_info=item.device_info[:200],
|
||||
app_version=item.app_version[:20],
|
||||
)
|
||||
for item in body.items
|
||||
]
|
||||
db.add_all(rows)
|
||||
await db.commit()
|
||||
logger.info(
|
||||
"user_action_log user=%s count=%s ip=%s",
|
||||
user.id, len(rows), request.client.host if request.client else "-",
|
||||
)
|
||||
return ok({"received": len(rows)})
|
||||
Reference in New Issue
Block a user