""" 消息通知服务层 """ from typing import Optional, List, Dict, Any from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.crud.notification import notification_crud from app.models.notification import NotificationTemplate from app.models.user import User from app.schemas.notification import ( NotificationCreate, NotificationBatchCreate, NotificationSendFromTemplate ) import json class NotificationService: """消息通知服务类""" async def get_notification(self, db: AsyncSession, notification_id: int) -> Optional[Dict[str, Any]]: """ 获取消息通知详情 Args: db: 数据库会话 notification_id: 通知ID Returns: 通知信息 """ notification = await notification_crud.get(db, notification_id) if not notification: return None return { "id": notification.id, "recipient_id": notification.recipient_id, "recipient_name": notification.recipient_name, "title": notification.title, "content": notification.content, "notification_type": notification.notification_type, "priority": notification.priority, "is_read": notification.is_read, "read_at": notification.read_at, "related_entity_type": notification.related_entity_type, "related_entity_id": notification.related_entity_id, "action_url": notification.action_url, "extra_data": notification.extra_data, "sent_via_email": notification.sent_via_email, "sent_via_sms": notification.sent_via_sms, "created_at": notification.created_at, "expire_at": notification.expire_at, } async def get_notifications( self, db: AsyncSession, *, skip: int = 0, limit: int = 20, recipient_id: Optional[int] = None, notification_type: Optional[str] = None, priority: Optional[str] = None, is_read: Optional[bool] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, keyword: Optional[str] = None ) -> Dict[str, Any]: """ 获取消息通知列表 Args: db: 数据库会话 skip: 跳过条数 limit: 返回条数 recipient_id: 接收人ID notification_type: 通知类型 priority: 优先级 is_read: 是否已读 start_time: 开始时间 end_time: 结束时间 keyword: 关键词 Returns: 通知列表和总数 """ items, total = await notification_crud.get_multi( db, skip=skip, limit=limit, recipient_id=recipient_id, notification_type=notification_type, priority=priority, is_read=is_read, start_time=start_time, end_time=end_time, keyword=keyword ) return { "items": [ { "id": item.id, "recipient_id": item.recipient_id, "recipient_name": item.recipient_name, "title": item.title, "content": item.content, "notification_type": item.notification_type, "priority": item.priority, "is_read": item.is_read, "read_at": item.read_at, "action_url": item.action_url, "created_at": item.created_at, } for item in items ], "total": total } async def create_notification( self, db: AsyncSession, obj_in: NotificationCreate ) -> Dict[str, Any]: """ 创建消息通知 Args: db: 数据库会话 obj_in: 创建数据 Returns: 创建的通知信息 """ # 获取接收人信息 user_result = await db.execute( select(User).where(User.id == obj_in.recipient_id) ) user = user_result.scalar_one_or_none() if not user: raise ValueError("接收人不存在") # 转换为字典 obj_in_data = obj_in.model_dump() obj_in_data["recipient_name"] = user.real_name # 处理复杂类型 if obj_in_data.get("extra_data"): obj_in_data["extra_data"] = json.loads(obj_in.extra_data.model_dump_json()) if isinstance(obj_in.extra_data, dict) else obj_in.extra_data # 设置邮件和短信发送标记 obj_in_data["sent_via_email"] = obj_in_data.pop("send_email", False) obj_in_data["sent_via_sms"] = obj_in_data.pop("send_sms", False) notification = await notification_crud.create(db, obj_in=obj_in_data) # TODO: 发送邮件和短信 # if notification.sent_via_email: # await self._send_email(notification) # if notification.sent_via_sms: # await self._send_sms(notification) return { "id": notification.id, "recipient_id": notification.recipient_id, "title": notification.title, } async def batch_create_notifications( self, db: AsyncSession, batch_in: NotificationBatchCreate ) -> Dict[str, Any]: """ 批量创建消息通知 Args: db: 数据库会话 batch_in: 批量创建数据 Returns: 创建结果 """ # 获取接收人信息 user_results = await db.execute( select(User).where(User.id.in_(batch_in.recipient_ids)) ) users = {user.id: user.real_name for user in user_results.scalars()} # 准备通知数据 notification_data = { "title": batch_in.title, "content": batch_in.content, "notification_type": batch_in.notification_type.value, "priority": batch_in.priority.value, "action_url": batch_in.action_url, "extra_data": json.loads(batch_in.extra_data.model_dump_json()) if batch_in.extra_data else {}, } # 批量创建 notifications = await notification_crud.batch_create( db, recipient_ids=batch_in.recipient_ids, notification_data=notification_data ) # 更新接收人姓名 for notification in notifications: notification.recipient_name = users.get(notification.recipient_id, "") await db.flush() return { "count": len(notifications), "notification_ids": [n.id for n in notifications] } async def mark_as_read( self, db: AsyncSession, notification_id: int ) -> Dict[str, Any]: """ 标记为已读 Args: db: 数据库会话 notification_id: 通知ID Returns: 更新结果 """ notification = await notification_crud.mark_as_read( db, notification_id=notification_id, read_at=datetime.utcnow() ) if not notification: raise ValueError("通知不存在") return { "id": notification.id, "is_read": notification.is_read, "read_at": notification.read_at } async def mark_all_as_read( self, db: AsyncSession, recipient_id: int ) -> Dict[str, Any]: """ 标记所有未读为已读 Args: db: 数据库会话 recipient_id: 接收人ID Returns: 更新结果 """ count = await notification_crud.mark_all_as_read( db, recipient_id=recipient_id, read_at=datetime.utcnow() ) return { "count": count, "message": f"已标记 {count} 条通知为已读" } async def delete_notification(self, db: AsyncSession, notification_id: int) -> None: """ 删除消息通知 Args: db: 数据库会话 notification_id: 通知ID """ await notification_crud.delete(db, notification_id=notification_id) async def batch_delete_notifications( self, db: AsyncSession, notification_ids: List[int] ) -> Dict[str, Any]: """ 批量删除通知 Args: db: 数据库会话 notification_ids: 通知ID列表 Returns: 删除结果 """ count = await notification_crud.batch_delete(db, notification_ids=notification_ids) return { "count": count, "message": f"已删除 {count} 条通知" } async def get_unread_count(self, db: AsyncSession, recipient_id: int) -> Dict[str, Any]: """ 获取未读通知数量 Args: db: 数据库会话 recipient_id: 接收人ID Returns: 未读数量 """ count = await notification_crud.get_unread_count(db, recipient_id) return {"unread_count": count} async def get_statistics(self, db: AsyncSession, recipient_id: int) -> Dict[str, Any]: """ 获取通知统计信息 Args: db: 数据库会话 recipient_id: 接收人ID Returns: 统计信息 """ return await notification_crud.get_statistics(db, recipient_id) async def send_from_template( self, db: AsyncSession, template_in: NotificationSendFromTemplate ) -> Dict[str, Any]: """ 从模板发送通知 Args: db: 数据库会话 template_in: 模板发送数据 Returns: 发送结果 """ # 获取模板 result = await db.execute( select(NotificationTemplate).where( and_( NotificationTemplate.template_code == template_in.template_code, NotificationTemplate.is_active == True ) ) ) template = result.scalar_one_or_none() if not template: raise ValueError(f"通知模板 {template_in.template_code} 不存在或未启用") # 渲染标题和内容 title = self._render_template(template.title_template, template_in.variables) content = self._render_template(template.content_template, template_in.variables) # 创建批量通知数据 batch_data = NotificationBatchCreate( recipient_ids=template_in.recipient_ids, title=title, content=content, notification_type=template.notification_type, priority=template.priority, action_url=template_in.action_url, extra_data={ "template_code": template.template_code, "variables": template_in.variables } ) return await self.batch_create_notifications(db, batch_data) def _render_template(self, template: str, variables: Dict[str, Any]) -> str: """ 渲染模板 Args: template: 模板字符串 variables: 变量字典 Returns: 渲染后的字符串 """ try: return template.format(**variables) except KeyError as e: raise ValueError(f"模板变量缺失: {e}") # 创建全局实例 notification_service = NotificationService()