Files
group_fqcd_jr/app/service/risk_notification_service.py
T

163 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""风控通知记录 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)