message.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. from pydantic import BaseModel, Field, model_validator
  2. from typing import Optional, Union, Any, List
  3. from datetime import datetime
  4. from enum import Enum
  5. class MessageType(str, Enum):
  6. MESSAGE = "MESSAGE"
  7. NOTIFICATION = "NOTIFICATION"
  8. class ContentType(str, Enum):
  9. TEXT = "TEXT"
  10. IMAGE = "IMAGE"
  11. VIDEO = "VIDEO"
  12. FILE = "FILE"
  13. # 用户在应用内发起的“申请通知”,用于在私信界面展示通知样式(携带 action_url/action_text)
  14. USER_NOTIFICATION = "USER_NOTIFICATION"
  15. class MessageBase(BaseModel):
  16. title: str = Field(..., max_length=255)
  17. content: Union[str, dict, Any] = Field(..., description="内容")
  18. content_type: ContentType = ContentType.TEXT
  19. type: MessageType = MessageType.MESSAGE
  20. app_id: Optional[str] = None # 改为字符串类型,支持开发者保存的字符串 app_id
  21. action_url: Optional[str] = None
  22. action_text: Optional[str] = None
  23. # SSO 扩展
  24. target_url: Optional[str] = None
  25. auto_sso: bool = False
  26. class MessageCreate(MessageBase):
  27. receiver_id: Optional[int] = None
  28. app_user_id: Optional[str] = None
  29. sender_app_user_id: Optional[str] = None # 应用发信时可选:帐内发起人,解析后写入 sender_id,用于审计和会话聚合
  30. is_broadcast: bool = False
  31. @model_validator(mode='after')
  32. def check_receiver(self):
  33. if self.is_broadcast:
  34. return self
  35. if not self.receiver_id and not (self.app_user_id and self.app_id):
  36. raise ValueError("非广播消息必须提供 receiver_id,或者同时提供 app_user_id 和 app_id")
  37. return self
  38. class MessageUpdate(BaseModel):
  39. is_read: Optional[bool] = None
  40. class MessageResponse(BaseModel):
  41. id: int
  42. sender_id: Optional[int]
  43. receiver_id: int
  44. app_id: Optional[int]
  45. app_name: Optional[str] = None # 应用名称,用于前端显示
  46. type: MessageType
  47. content_type: ContentType
  48. title: str
  49. content: str # DB 中存的是字符串
  50. action_url: Optional[str]
  51. action_text: Optional[str]
  52. is_read: bool
  53. created_at: datetime
  54. read_at: Optional[datetime]
  55. class Config:
  56. from_attributes = True
  57. class ConversationResponse(BaseModel):
  58. user_id: int
  59. username: str
  60. full_name: Optional[str]
  61. unread_count: int
  62. last_message: str
  63. last_message_type: ContentType
  64. updated_at: datetime
  65. # 系统会话标记及应用信息(用于拆分不同平台的系统通知会话)
  66. is_system: bool = False
  67. app_id: Optional[int] = None
  68. app_name: Optional[str] = None
  69. # 业务侧 Application.app_id 字符串(与 SSO / 客户端缓存键一致);仅按应用拆分的系统会话可能有值
  70. application_app_id: Optional[str] = None
  71. icon_url: Optional[str] = None
  72. icon_object_key: Optional[str] = None
  73. # 列表副文案:有 app 的应用通知为「应用通知」;无 app 的旧系统会话为空;用户会话为对端组织名,无组织为空
  74. remarks: Optional[str] = None
  75. class Config:
  76. from_attributes = True
  77. class AdminMessageItem(BaseModel):
  78. """运维消息查询返回的单条消息(含发送者/接收者/应用基础信息)。"""
  79. id: int
  80. type: MessageType
  81. content_type: ContentType
  82. title: str
  83. content: str
  84. action_url: Optional[str] = None
  85. action_text: Optional[str] = None
  86. is_read: bool
  87. created_at: datetime
  88. read_at: Optional[datetime] = None
  89. sender_id: Optional[int] = None
  90. sender_mobile: Optional[str] = None
  91. sender_name: Optional[str] = None
  92. receiver_id: int
  93. receiver_mobile: Optional[str] = None
  94. receiver_name: Optional[str] = None
  95. app_id: Optional[int] = None
  96. app_app_id: Optional[str] = None
  97. app_name: Optional[str] = None
  98. class Config:
  99. from_attributes = True
  100. class AdminMessageList(BaseModel):
  101. total: int
  102. items: List[AdminMessageItem]
  103. # Device Schema
  104. class DeviceRegister(BaseModel):
  105. device_token: str
  106. platform: str
  107. device_name: Optional[str] = None