message_controller.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package controller
  2. import (
  3. "log"
  4. "net/http"
  5. "path/filepath"
  6. "strconv"
  7. "strings"
  8. "github.com/2930134478/AI-CS/backend/infra"
  9. "github.com/2930134478/AI-CS/backend/service"
  10. "github.com/gin-gonic/gin"
  11. )
  12. // MessageController 负责处理消息相关的 HTTP 请求。
  13. type MessageController struct {
  14. messageService *service.MessageService
  15. storageService infra.StorageService
  16. }
  17. // NewMessageController 创建 MessageController 实例。
  18. func NewMessageController(messageService *service.MessageService, storageService infra.StorageService) *MessageController {
  19. return &MessageController{
  20. messageService: messageService,
  21. storageService: storageService,
  22. }
  23. }
  24. type createMessageRequest struct {
  25. ConversationID uint `json:"conversation_id"`
  26. Content string `json:"content"`
  27. SenderIsAgent bool `json:"sender_is_agent"`
  28. SenderID uint `json:"sender_id"`
  29. // 文件相关字段(可选)
  30. FileURL *string `json:"file_url"`
  31. FileType *string `json:"file_type"`
  32. FileName *string `json:"file_name"`
  33. FileSize *int64 `json:"file_size"`
  34. MimeType *string `json:"mime_type"`
  35. }
  36. // CreateMessage 处理发送消息的请求。
  37. func (mc *MessageController) CreateMessage(c *gin.Context) {
  38. var req createMessageRequest
  39. if err := c.ShouldBindJSON(&req); err != nil || req.ConversationID == 0 {
  40. c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
  41. return
  42. }
  43. // 验证:必须有内容或文件
  44. if req.Content == "" && req.FileURL == nil {
  45. c.JSON(http.StatusBadRequest, gin.H{"error": "消息内容或文件不能同时为空"})
  46. return
  47. }
  48. _, err := mc.messageService.CreateMessage(service.CreateMessageInput{
  49. ConversationID: req.ConversationID,
  50. Content: req.Content,
  51. SenderID: req.SenderID,
  52. SenderIsAgent: req.SenderIsAgent,
  53. FileURL: req.FileURL,
  54. FileType: req.FileType,
  55. FileName: req.FileName,
  56. FileSize: req.FileSize,
  57. MimeType: req.MimeType,
  58. })
  59. if err != nil {
  60. log.Printf("❌ 创建消息失败: 对话ID=%d, 错误=%v", req.ConversationID, err)
  61. switch err {
  62. case service.ErrConversationClosed:
  63. c.JSON(http.StatusBadRequest, gin.H{"error": "会话已关闭"})
  64. case service.ErrConversationNotFound:
  65. c.JSON(http.StatusBadRequest, gin.H{"error": "会话不存在"})
  66. default:
  67. c.JSON(http.StatusInternalServerError, gin.H{"error": "创建消息失败"})
  68. }
  69. return
  70. }
  71. c.JSON(http.StatusOK, gin.H{"message": "创建消息成功"})
  72. }
  73. // ListMessages 返回指定会话的消息列表。
  74. // 查询参数:
  75. // - conversation_id: 会话ID(必需)
  76. // - include_ai_messages: 是否包含 AI 消息(可选,默认 false)
  77. func (mc *MessageController) ListMessages(c *gin.Context) {
  78. conversationIDStr := c.Query("conversation_id")
  79. if conversationIDStr == "" {
  80. c.JSON(http.StatusBadRequest, gin.H{"error": "会话ID不能为空"})
  81. return
  82. }
  83. conversationID, err := strconv.ParseUint(conversationIDStr, 10, 64)
  84. if err != nil || conversationID == 0 {
  85. c.JSON(http.StatusBadRequest, gin.H{"error": "会话ID不合法"})
  86. return
  87. }
  88. // 解析 include_ai_messages 参数(默认 false)
  89. includeAIMessages := c.DefaultQuery("include_ai_messages", "false") == "true"
  90. messages, err := mc.messageService.ListMessages(uint(conversationID), includeAIMessages)
  91. if err != nil {
  92. c.JSON(http.StatusInternalServerError, gin.H{"error": "查询消息失败"})
  93. return
  94. }
  95. c.JSON(http.StatusOK, messages)
  96. }
  97. type markMessagesReadRequest struct {
  98. ConversationID uint `json:"conversation_id"`
  99. ReaderIsAgent bool `json:"reader_is_agent"`
  100. }
  101. // MarkMessagesRead 将指定会话的消息标记为已读。
  102. func (mc *MessageController) MarkMessagesRead(c *gin.Context) {
  103. var req markMessagesReadRequest
  104. if err := c.ShouldBindJSON(&req); err != nil || req.ConversationID == 0 {
  105. c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误"})
  106. return
  107. }
  108. result, err := mc.messageService.MarkMessagesRead(req.ConversationID, req.ReaderIsAgent)
  109. if err != nil {
  110. c.JSON(http.StatusInternalServerError, gin.H{"error": "更新消息状态失败"})
  111. return
  112. }
  113. c.JSON(http.StatusOK, gin.H{
  114. "updated": len(result.MessageIDs),
  115. "message_ids": result.MessageIDs,
  116. "conversation_id": result.ConversationID,
  117. "unread_count": result.UnreadCount,
  118. "read_at": formatTimeValue(result.ReadAt),
  119. })
  120. }
  121. // UploadFile 处理文件上传请求。
  122. // 请求格式:multipart/form-data
  123. // - file: 文件内容(必需)
  124. // - conversation_id: 对话ID(可选,用于组织目录)
  125. func (mc *MessageController) UploadFile(c *gin.Context) {
  126. // 解析文件
  127. file, err := c.FormFile("file")
  128. if err != nil {
  129. c.JSON(http.StatusBadRequest, gin.H{"error": "文件不能为空"})
  130. return
  131. }
  132. // 验证文件大小(10MB)
  133. const maxFileSize = 10 * 1024 * 1024 // 10MB
  134. if file.Size > maxFileSize {
  135. c.JSON(http.StatusBadRequest, gin.H{"error": "文件大小超过限制(最大10MB)"})
  136. return
  137. }
  138. // 验证文件类型
  139. ext := strings.ToLower(filepath.Ext(file.Filename))
  140. allowedExts := map[string]bool{
  141. ".jpg": true,
  142. ".jpeg": true,
  143. ".png": true,
  144. ".gif": true,
  145. ".webp": true,
  146. ".pdf": true,
  147. ".doc": true,
  148. ".docx": true,
  149. ".txt": true,
  150. }
  151. if !allowedExts[ext] {
  152. c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的文件类型"})
  153. return
  154. }
  155. // 获取对话ID(可选)
  156. var conversationID uint
  157. if conversationIDStr := c.PostForm("conversation_id"); conversationIDStr != "" {
  158. if id, err := strconv.ParseUint(conversationIDStr, 10, 64); err == nil {
  159. conversationID = uint(id)
  160. }
  161. }
  162. // 打开文件
  163. src, err := file.Open()
  164. if err != nil {
  165. log.Printf("❌ 打开文件失败: %v", err)
  166. c.JSON(http.StatusInternalServerError, gin.H{"error": "打开文件失败"})
  167. return
  168. }
  169. defer src.Close()
  170. // 保存文件
  171. fileURL, err := mc.storageService.SaveMessageFile(conversationID, src, file.Filename)
  172. if err != nil {
  173. log.Printf("❌ 保存文件失败: %v", err)
  174. c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"})
  175. return
  176. }
  177. // 判断文件类型
  178. fileType := "document"
  179. mimeType := file.Header.Get("Content-Type")
  180. if strings.HasPrefix(mimeType, "image/") {
  181. fileType = "image"
  182. }
  183. // 返回文件信息
  184. c.JSON(http.StatusOK, gin.H{
  185. "success": true,
  186. "data": gin.H{
  187. "file_url": fileURL,
  188. "file_type": fileType,
  189. "file_name": file.Filename,
  190. "file_size": file.Size,
  191. "mime_type": mimeType,
  192. },
  193. })
  194. }