163 lines
5.0 KiB
Python
163 lines
5.0 KiB
Python
"""风控通知记录 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")
|
||
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),
|
||
),
|
||
)
|
||
return RiskQueryService._page(page)
|
||
|
||
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)
|