Files
group_fqcd_jr/app/service/risk_notification_service.py
T

164 lines
5.1 KiB
Python
Raw Normal View History

"""风控通知记录 Service。
本阶段只创建通知记录和提供分页查询,不执行 SMTP 外发。
"""
from __future__ import annotations
from datetime import UTC, datetime
from typing import Any
from uuid import uuid4
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.schemas.risk import RiskNotificationPageQuery
from app.core.contracts import RequestContext
from app.core.risk_cursor import decode_offset_cursor
from app.model.fund import FundRiskAlert, FundRiskNotification
from app.repository.fund_query_repository import PageRequest
from app.repository.risk_repository import RiskRepository
from app.service.authorization_service import AuthorizationService
from app.service.risk_query_service import RiskQueryService, scope_from_context
class RiskNotificationService:
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def list_notifications(
self,
context: RequestContext,
query: RiskNotificationPageQuery,
) -> dict[str, Any]:
await AuthorizationService.require(context, "risk:alert:read")
binding = RiskQueryService._binding(context, query, "/notifications")
page = await RiskRepository(
self.session,
scope=scope_from_context(context),
).list_notifications(
keyword=query.keyword,
send_status=query.send_status,
start_time=query.start_time,
end_time=query.end_time,
page=PageRequest(
limit=query.limit,
offset=decode_offset_cursor(query.cursor, binding=binding),
),
)
return RiskQueryService._page(page, binding=binding)
def create_in_app(
self,
alert: FundRiskAlert,
*,
receiver_user_id: int | None,
title: str,
content: str,
) -> FundRiskNotification:
notification = self._build(
alert=alert,
channel="站内提醒",
receiver_user_id=receiver_user_id,
receiver_email=None,
title=title,
content=content,
send_status="已发送",
sent_at=_now(),
fail_reason=None,
)
self.session.add(notification)
return notification
def create_mail_record(
self,
alert: FundRiskAlert,
*,
receiver_email: str,
title: str,
content: str,
mail_enabled: bool = False,
) -> FundRiskNotification:
notification = self._build(
alert=alert,
channel="邮件",
receiver_user_id=None,
receiver_email=receiver_email,
title=title,
content=content,
send_status="待发送" if mail_enabled else "未启用",
sent_at=None,
fail_reason=None if mail_enabled else "邮件发送功能未启用",
)
self.session.add(notification)
return notification
def create_high_risk_records(
self,
alerts: list[FundRiskAlert],
*,
receiver_user_id: int | None = None,
receiver_email: str | None = None,
mail_enabled: bool = False,
) -> list[FundRiskNotification]:
notifications: list[FundRiskNotification] = []
for alert in alerts:
if alert.alert_level != "高":
continue
title = f"高风险预警:{alert.alert_type}"
notifications.append(self.create_in_app(
alert,
receiver_user_id=receiver_user_id,
title=title,
content=alert.evidence_summary,
))
if receiver_email:
notifications.append(self.create_mail_record(
alert,
receiver_email=receiver_email,
title=title,
content=alert.evidence_summary,
mail_enabled=mail_enabled,
))
return notifications
@staticmethod
def _build(
*,
alert: FundRiskAlert,
channel: str,
receiver_user_id: int | None,
receiver_email: str | None,
title: str,
content: str,
send_status: str,
sent_at: datetime | None,
fail_reason: str | None,
) -> FundRiskNotification:
referenced_content = (
content
if f"预警编号:{alert.alert_no}" in content
else f"预警编号:{alert.alert_no};{content}"
)
return FundRiskNotification(
id=_new_notification_id(),
notification_no=f"N{uuid4().hex[:12].upper()}",
alert_id=alert.id,
channel=channel,
receiver_user_id=receiver_user_id,
receiver_email=receiver_email,
title=title,
content=referenced_content,
send_status=send_status,
sent_at=sent_at,
fail_reason=fail_reason,
created_at=_now(),
)
def _new_notification_id() -> int:
return uuid4().int & ((1 << 63) - 1) or 1
def _now() -> datetime:
return datetime.now(UTC).replace(tzinfo=None)