Initial backend repository commit.

Set up project files and add .gitignore to exclude local build/runtime artifacts.

Made-with: Cursor
This commit is contained in:
2026-03-09 21:28:58 +08:00
commit 4d8f2ec997
102 changed files with 25022 additions and 0 deletions

View File

@@ -0,0 +1,866 @@
package handler
import (
"context"
"encoding/json"
"log"
"net/http"
"strconv"
"strings"
"time"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model"
ws "carrot_bbs/internal/pkg/websocket"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源,生产环境应该限制
},
}
// WebSocketHandler WebSocket处理器
type WebSocketHandler struct {
jwtService *service.JWTService
chatService service.ChatService
groupService service.GroupService
groupRepo repository.GroupRepository
userRepo *repository.UserRepository
wsManager *ws.WebSocketManager
}
// NewWebSocketHandler 创建WebSocket处理器
func NewWebSocketHandler(
jwtService *service.JWTService,
chatService service.ChatService,
groupService service.GroupService,
groupRepo repository.GroupRepository,
userRepo *repository.UserRepository,
wsManager *ws.WebSocketManager,
) *WebSocketHandler {
return &WebSocketHandler{
jwtService: jwtService,
chatService: chatService,
groupService: groupService,
groupRepo: groupRepo,
userRepo: userRepo,
wsManager: wsManager,
}
}
// HandleWebSocket 处理WebSocket连接
func (h *WebSocketHandler) HandleWebSocket(c *gin.Context) {
// 调试:打印请求头信息
log.Printf("[WebSocket] 收到请求: Method=%s, Path=%s", c.Request.Method, c.Request.URL.Path)
log.Printf("[WebSocket] 请求头: Connection=%s, Upgrade=%s",
c.GetHeader("Connection"),
c.GetHeader("Upgrade"))
log.Printf("[WebSocket] Sec-WebSocket-Key=%s, Sec-WebSocket-Version=%s",
c.GetHeader("Sec-WebSocket-Key"),
c.GetHeader("Sec-WebSocket-Version"))
// 从query参数获取token
token := c.Query("token")
if token == "" {
// 尝试从header获取
authHeader := c.GetHeader("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
token = strings.TrimPrefix(authHeader, "Bearer ")
}
}
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
return
}
// 验证token
claims, err := h.jwtService.ParseToken(token)
if err != nil {
log.Printf("Invalid token: %v", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
userID := claims.UserID
if userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token claims"})
return
}
// 升级HTTP连接为WebSocket连接
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("Failed to upgrade connection: %v", err)
log.Printf("[WebSocket] 请求详情 - User-Agent: %s, Content-Type: %s",
c.GetHeader("User-Agent"),
c.GetHeader("Content-Type"))
return
}
// 如果用户已在线,先注销旧连接
if h.wsManager.IsUserOnline(userID) {
log.Printf("[DEBUG] 用户 %s 已有在线连接,复用该连接", userID)
} else {
log.Printf("[DEBUG] 用户 %s 当前不在线,创建新连接", userID)
}
// 创建客户端
client := &ws.Client{
ID: userID,
UserID: userID,
Conn: conn,
Send: make(chan []byte, 256),
Manager: h.wsManager,
}
// 注册客户端
h.wsManager.Register(client)
// 启动读写协程
go client.WritePump()
go h.handleMessages(client)
log.Printf("[DEBUG] WebSocket连接建立: userID=%s, 当前在线=%v", userID, h.wsManager.IsUserOnline(userID))
}
// handleMessages 处理客户端消息
// 针对移动端优化:增加超时时间到 120 秒,配合客户端 55 秒心跳
func (h *WebSocketHandler) handleMessages(client *ws.Client) {
defer func() {
h.wsManager.Unregister(client)
client.Conn.Close()
}()
client.Conn.SetReadLimit(512 * 1024) // 512KB
client.Conn.SetReadDeadline(time.Now().Add(120 * time.Second)) // 增加到 120 秒
client.Conn.SetPongHandler(func(string) error {
client.Conn.SetReadDeadline(time.Now().Add(120 * time.Second)) // 增加到 120 秒
return nil
})
// 心跳定时器 - 服务端主动 ping 间隔保持 30 秒
pingTicker := time.NewTicker(30 * time.Second)
defer pingTicker.Stop()
for {
select {
case <-pingTicker.C:
// 发送心跳
if err := client.SendPing(); err != nil {
log.Printf("Failed to send ping: %v", err)
return
}
default:
_, message, err := client.Conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err)
}
return
}
var wsMsg ws.WSMessage
if err := json.Unmarshal(message, &wsMsg); err != nil {
log.Printf("Failed to unmarshal message: %v", err)
continue
}
h.processMessage(client, &wsMsg)
}
}
}
// processMessage 处理消息
func (h *WebSocketHandler) processMessage(client *ws.Client, msg *ws.WSMessage) {
switch msg.Type {
case ws.MessageTypePing:
// 响应心跳
if err := client.SendPong(); err != nil {
log.Printf("Failed to send pong: %v", err)
}
case ws.MessageTypePong:
// 客户端响应心跳
case ws.MessageTypeMessage:
// 处理聊天消息
h.handleChatMessage(client, msg)
case ws.MessageTypeTyping:
// 处理正在输入状态
h.handleTyping(client, msg)
case ws.MessageTypeRead:
// 处理已读回执
h.handleReadReceipt(client, msg)
// 群组消息处理
case ws.MessageTypeGroupMessage:
// 处理群消息
h.handleGroupMessage(client, msg)
case ws.MessageTypeGroupTyping:
// 处理群输入状态
h.handleGroupTyping(client, msg)
case ws.MessageTypeGroupRead:
// 处理群消息已读
h.handleGroupReadReceipt(client, msg)
case ws.MessageTypeGroupRecall:
// 处理群消息撤回
h.handleGroupRecall(client, msg)
default:
log.Printf("Unknown message type: %s", msg.Type)
}
}
// handleChatMessage 处理聊天消息
func (h *WebSocketHandler) handleChatMessage(client *ws.Client, msg *ws.WSMessage) {
data, ok := msg.Data.(map[string]interface{})
if !ok {
log.Printf("Invalid message data format")
return
}
log.Printf("[DEBUG handleChatMessage] 完整data: %+v", data)
conversationIDStr, _ := data["conversationId"].(string)
if conversationIDStr == "" {
log.Printf("Missing conversationId")
return
}
// 解析会话ID
conversationID, err := service.ParseConversationID(conversationIDStr)
if err != nil {
log.Printf("Invalid conversation ID: %v", err)
return
}
// 解析 segments
var segments model.MessageSegments
if data["segments"] != nil {
segmentsBytes, err := json.Marshal(data["segments"])
if err == nil {
json.Unmarshal(segmentsBytes, &segments)
}
}
// 从 segments 中提取回复消息ID
replyToID := dto.GetReplyMessageID(segments)
var replyToIDPtr *string
if replyToID != "" {
replyToIDPtr = &replyToID
}
// 发送消息 - 使用 segments
message, err := h.chatService.SendMessage(context.Background(), client.UserID, conversationID, segments, replyToIDPtr)
if err != nil {
log.Printf("Failed to send message: %v", err)
// 发送错误消息
errorMsg := ws.CreateWSMessage(ws.MessageTypeError, map[string]string{
"error": "Failed to send message",
})
if client.Send != nil {
msgBytes, _ := json.Marshal(errorMsg)
client.Send <- msgBytes
}
return
}
// 发送确认消息(使用 meta 事件格式,包含完整的消息内容)
metaAckMsg := ws.CreateWSMessage("meta", map[string]interface{}{
"detail_type": ws.MetaDetailTypeAck,
"conversation_id": conversationID,
"id": message.ID,
"user_id": client.UserID,
"sender_id": client.UserID,
"seq": message.Seq,
"segments": message.Segments,
"created_at": message.CreatedAt.UnixMilli(),
})
if client.Send != nil {
msgBytes, _ := json.Marshal(metaAckMsg)
log.Printf("[DEBUG handleChatMessage] 私聊 ack 消息: %s", string(msgBytes))
log.Printf("[DEBUG handleChatMessage] message.Segments 类型: %T, 值: %+v", message.Segments, message.Segments)
client.Send <- msgBytes
}
}
// handleTyping 处理正在输入状态
func (h *WebSocketHandler) handleTyping(client *ws.Client, msg *ws.WSMessage) {
data, ok := msg.Data.(map[string]interface{})
if !ok {
return
}
conversationIDStr, _ := data["conversationId"].(string)
if conversationIDStr == "" {
return
}
conversationID, err := service.ParseConversationID(conversationIDStr)
if err != nil {
return
}
// 直接使用 string 类型的 userID
h.chatService.SendTyping(context.Background(), client.UserID, conversationID)
}
// handleReadReceipt 处理已读回执
func (h *WebSocketHandler) handleReadReceipt(client *ws.Client, msg *ws.WSMessage) {
data, ok := msg.Data.(map[string]interface{})
if !ok {
return
}
conversationIDStr, _ := data["conversationId"].(string)
if conversationIDStr == "" {
return
}
conversationID, err := service.ParseConversationID(conversationIDStr)
if err != nil {
return
}
// 获取lastReadSeq
lastReadSeq, _ := data["lastReadSeq"].(float64)
if lastReadSeq == 0 {
return
}
// 直接使用 string 类型的 userID 和 conversationID
if err := h.chatService.MarkAsRead(context.Background(), conversationID, client.UserID, int64(lastReadSeq)); err != nil {
log.Printf("Failed to mark as read: %v", err)
}
}
// ==================== 群组消息处理 ====================
// handleGroupMessage 处理群消息
func (h *WebSocketHandler) handleGroupMessage(client *ws.Client, msg *ws.WSMessage) {
// 打印接收到的消息类型和数据,用于调试
log.Printf("[handleGroupMessage] Received message type: %s", msg.Type)
log.Printf("[handleGroupMessage] Message data: %+v", msg.Data)
data, ok := msg.Data.(map[string]interface{})
if !ok {
log.Printf("Invalid group message data format: data is not map[string]interface{}")
return
}
// 解析群组ID支持 camelCase 和 snake_case
var groupIDFloat float64
groupID := "" // 使用 groupID 作为最终变量名
if val, ok := data["groupId"].(float64); ok {
groupIDFloat = val
groupID = strconv.FormatFloat(groupIDFloat, 'f', 0, 64)
} else if val, ok := data["group_id"].(string); ok {
groupID = val
}
if groupID == "" {
log.Printf("Missing groupId in group message")
return
}
// 解析会话ID支持 camelCase 和 snake_case
var conversationID string
if val, ok := data["conversationId"].(string); ok {
conversationID = val
} else if val, ok := data["conversation_id"].(string); ok {
conversationID = val
}
if conversationID == "" {
log.Printf("Missing conversationId in group message")
return
}
// 解析 segments
var segments model.MessageSegments
if data["segments"] != nil {
segmentsBytes, err := json.Marshal(data["segments"])
if err == nil {
json.Unmarshal(segmentsBytes, &segments)
}
}
// 解析@用户列表(支持 camelCase 和 snake_case
var mentionUsers []uint64
var mentionUsersInterface []interface{}
if val, ok := data["mentionUsers"].([]interface{}); ok {
mentionUsersInterface = val
} else if val, ok := data["mention_users"].([]interface{}); ok {
mentionUsersInterface = val
}
if len(mentionUsersInterface) > 0 {
for _, uid := range mentionUsersInterface {
if uidFloat, ok := uid.(float64); ok {
mentionUsers = append(mentionUsers, uint64(uidFloat))
} else if uidStr, ok := uid.(string); ok {
// 处理字符串格式的用户ID
if uidInt, err := strconv.ParseUint(uidStr, 10, 64); err == nil {
mentionUsers = append(mentionUsers, uidInt)
}
}
}
}
// 解析@所有人(支持 camelCase 和 snake_case
var mentionAll bool
if val, ok := data["mentionAll"].(bool); ok {
mentionAll = val
} else if val, ok := data["mention_all"].(bool); ok {
mentionAll = val
}
// 检查用户是否可以发送群消息(验证成员身份和禁言状态)
// client.UserID 已经是 string 格式的 UUID
if err := h.groupService.CanSendGroupMessage(client.UserID, groupID); err != nil {
log.Printf("User cannot send group message: %v", err)
// 发送错误消息
errorMsg := ws.CreateWSMessage(ws.MessageTypeError, map[string]string{
"error": "Cannot send group message",
"reason": err.Error(),
"type": "group_message_error",
"groupId": groupID,
})
if client.Send != nil {
msgBytes, _ := json.Marshal(errorMsg)
client.Send <- msgBytes
}
return
}
// 检查@所有人权限(只有群主和管理员可以@所有人)
if mentionAll {
if !h.groupService.IsGroupAdmin(client.UserID, groupID) {
log.Printf("User %s has no permission to mention all in group %s", client.UserID, groupID)
mentionAll = false // 取消@所有人标记
}
}
// 创建消息
message := &model.Message{
ConversationID: conversationID,
SenderID: client.UserID,
Segments: segments,
Status: model.MessageStatusNormal,
MentionAll: mentionAll,
}
// 序列化mentionUsers为JSON
if len(mentionUsers) > 0 {
mentionUsersJSON, _ := json.Marshal(mentionUsers)
message.MentionUsers = string(mentionUsersJSON)
}
// 保存消息到数据库(只存库,不发私聊 WebSocket 帧,群消息通过 BroadcastGroupMessage 单独广播)
savedMessage, err := h.chatService.SaveMessage(context.Background(), client.UserID, conversationID, segments, nil)
if err != nil {
log.Printf("Failed to save group message: %v", err)
errorMsg := ws.CreateWSMessage(ws.MessageTypeError, map[string]string{
"error": "Failed to save group message",
})
if client.Send != nil {
msgBytes, _ := json.Marshal(errorMsg)
client.Send <- msgBytes
}
return
}
// 更新消息的mention信息
if len(mentionUsers) > 0 || mentionAll {
message.ID = savedMessage.ID
message.Seq = savedMessage.Seq
}
// 构造群消息响应
groupMsg := &ws.GroupMessage{
ID: savedMessage.ID,
ConversationID: conversationID,
GroupID: groupID,
SenderID: client.UserID,
Seq: savedMessage.Seq,
Segments: segments,
MentionUsers: mentionUsers,
MentionAll: mentionAll,
CreatedAt: savedMessage.CreatedAt.UnixMilli(),
}
// 广播消息给群组所有成员(排除发送者)
h.BroadcastGroupMessage(groupID, groupMsg, client.UserID)
// 发送确认消息给发送者作为meta事件
// 使用 meta 事件格式发送 ack
log.Printf("[DEBUG HandleGroupMessageSend] 准备发送 ack 消息, userID=%s, messageID=%s, seq=%d",
client.UserID, savedMessage.ID, savedMessage.Seq)
metaAckMsg := ws.CreateWSMessage("meta", map[string]interface{}{
"detail_type": ws.MetaDetailTypeAck,
"conversation_id": conversationID,
"group_id": groupID,
"id": savedMessage.ID,
"user_id": client.UserID,
"sender_id": client.UserID,
"seq": savedMessage.Seq,
"segments": segments,
"created_at": savedMessage.CreatedAt.UnixMilli(),
})
if client.Send != nil {
msgBytes, _ := json.Marshal(metaAckMsg)
log.Printf("[DEBUG HandleGroupMessageSend] 发送 ack 消息到 channel, userID=%s, msg=%s",
client.UserID, string(msgBytes))
client.Send <- msgBytes
} else {
log.Printf("[ERROR HandleGroupMessageSend] client.Send 为 nil, userID=%s", client.UserID)
}
// 处理@提及通知
if len(mentionUsers) > 0 || mentionAll {
// 提取文本正文(不含 @ 部分)
textContent := dto.ExtractTextContentFromModel(segments)
// 在通知内容前拼接被@的真实昵称,通过群成员列表查找
mentionContent := h.buildMentionContent(groupID, mentionUsers, mentionAll, textContent)
h.handleGroupMention(groupID, savedMessage.ID, client.UserID, mentionContent, mentionUsers, mentionAll)
}
}
// handleGroupTyping 处理群输入状态
func (h *WebSocketHandler) handleGroupTyping(client *ws.Client, msg *ws.WSMessage) {
data, ok := msg.Data.(map[string]interface{})
if !ok {
return
}
groupIDFloat, _ := data["groupId"].(float64)
if groupIDFloat == 0 {
return
}
groupID := strconv.FormatFloat(groupIDFloat, 'f', 0, 64)
isTyping, _ := data["isTyping"].(bool)
// 验证用户是否是群成员
// client.UserID 已经是 string 格式的 UUID
isMember, err := h.groupRepo.IsMember(groupID, client.UserID)
if err != nil || !isMember {
return
}
// 获取用户信息
user, err := h.userRepo.GetByID(client.UserID)
if err != nil {
return
}
// 构造输入状态消息
typingMsg := &ws.GroupTypingMessage{
GroupID: groupID,
UserID: client.UserID,
Username: user.Username,
IsTyping: isTyping,
}
// 广播给群组其他成员
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupTyping, typingMsg)
h.BroadcastGroupNoticeExclude(groupID, wsMsg, client.UserID)
}
// handleGroupReadReceipt 处理群消息已读回执
func (h *WebSocketHandler) handleGroupReadReceipt(client *ws.Client, msg *ws.WSMessage) {
data, ok := msg.Data.(map[string]interface{})
if !ok {
return
}
conversationID, _ := data["conversationId"].(string)
if conversationID == "" {
return
}
lastReadSeq, _ := data["lastReadSeq"].(float64)
if lastReadSeq == 0 {
return
}
// 标记已读
if err := h.chatService.MarkAsRead(context.Background(), conversationID, client.UserID, int64(lastReadSeq)); err != nil {
log.Printf("Failed to mark group message as read: %v", err)
}
}
// handleGroupRecall 处理群消息撤回
func (h *WebSocketHandler) handleGroupRecall(client *ws.Client, msg *ws.WSMessage) {
data, ok := msg.Data.(map[string]interface{})
if !ok {
return
}
messageID, _ := data["messageId"].(string)
if messageID == "" {
return
}
groupIDFloat, _ := data["groupId"].(float64)
if groupIDFloat == 0 {
return
}
groupID := strconv.FormatFloat(groupIDFloat, 'f', 0, 64)
// 撤回消息
if err := h.chatService.RecallMessage(context.Background(), messageID, client.UserID); err != nil {
log.Printf("Failed to recall group message: %v", err)
errorMsg := ws.CreateWSMessage(ws.MessageTypeError, map[string]string{
"error": "Failed to recall message",
})
if client.Send != nil {
msgBytes, _ := json.Marshal(errorMsg)
client.Send <- msgBytes
}
return
}
// 广播撤回通知给群组所有成员
recallNotice := ws.CreateWSMessage(ws.MessageTypeGroupRecall, map[string]interface{}{
"messageId": messageID,
"groupId": groupID,
"userId": client.UserID,
"timestamp": time.Now().UnixMilli(),
})
h.BroadcastGroupNotice(groupID, recallNotice)
}
// handleGroupMention 处理群消息@提及通知
func (h *WebSocketHandler) handleGroupMention(groupID string, messageID, senderID, content string, mentionUsers []uint64, mentionAll bool) {
// 如果@所有人,获取所有群成员
if mentionAll {
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err != nil {
log.Printf("Failed to get group members for mention all: %v", err)
return
}
for _, member := range members {
// 不通知发送者自己
memberIDStr := member.UserID
if memberIDStr == senderID {
continue
}
// 发送@提及通知
mentionMsg := &ws.GroupMentionMessage{
GroupID: groupID,
MessageID: messageID,
FromUserID: senderID,
Content: truncateContent(content, 50),
MentionAll: true,
CreatedAt: time.Now().UnixMilli(),
}
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupMention, mentionMsg)
h.wsManager.SendToUser(memberIDStr, wsMsg)
}
return
}
// 处理特定用户的@提及
for _, userID := range mentionUsers {
// userID 是 uint64转换为 string
userIDStr := strconv.FormatUint(userID, 10)
if userIDStr == senderID {
continue // 不通知发送者自己
}
mentionMsg := &ws.GroupMentionMessage{
GroupID: groupID,
MessageID: messageID,
FromUserID: senderID,
Content: truncateContent(content, 50),
MentionAll: false,
CreatedAt: time.Now().UnixMilli(),
}
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupMention, mentionMsg)
h.wsManager.SendToUser(userIDStr, wsMsg)
}
}
// buildMentionContent 构建@提及通知的内容,通过群成员列表查找被@用户的真实昵称
func (h *WebSocketHandler) buildMentionContent(groupID string, mentionUsers []uint64, mentionAll bool, textBody string) string {
var prefix string
if mentionAll {
prefix = "@所有人 "
} else if len(mentionUsers) > 0 {
// 查询群成员列表,找到被@用户的昵称
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err == nil {
memberNickMap := make(map[string]string, len(members))
for _, m := range members {
displayName := m.Nickname
if displayName == "" {
displayName = m.UserID
}
memberNickMap[m.UserID] = displayName
}
for _, uid := range mentionUsers {
uidStr := strconv.FormatUint(uid, 10)
if name, ok := memberNickMap[uidStr]; ok {
prefix += "@" + name + " "
} else {
prefix += "@某人 "
}
}
} else {
for range mentionUsers {
prefix += "@某人 "
}
}
}
return prefix + textBody
}
// BroadcastGroupMessage 向群组所有成员广播消息
func (h *WebSocketHandler) BroadcastGroupMessage(groupID string, message *ws.GroupMessage, excludeUserID string) {
// 获取群组所有成员
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err != nil {
log.Printf("Failed to get group members for broadcast: %v", err)
return
}
// 创建WebSocket消息
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupMessage, message)
// 遍历成员,如果在线则发送消息
for _, member := range members {
memberIDStr := member.UserID
// 排除发送者
if memberIDStr == excludeUserID {
continue
}
// 发送消息
h.wsManager.SendToUser(memberIDStr, wsMsg)
}
}
// BroadcastGroupNotice 广播群组通知给所有成员
func (h *WebSocketHandler) BroadcastGroupNotice(groupID string, notice *ws.WSMessage) {
// 获取群组所有成员
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err != nil {
log.Printf("Failed to get group members for notice broadcast: %v", err)
return
}
// 遍历成员,如果在线则发送通知
for _, member := range members {
memberIDStr := member.UserID
h.wsManager.SendToUser(memberIDStr, notice)
}
}
// BroadcastGroupNoticeExclude 广播群组通知给所有成员(排除指定用户)
func (h *WebSocketHandler) BroadcastGroupNoticeExclude(groupID string, notice *ws.WSMessage, excludeUserID string) {
// 获取群组所有成员
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err != nil {
log.Printf("Failed to get group members for notice broadcast: %v", err)
return
}
// 遍历成员,如果在线则发送通知
for _, member := range members {
memberIDStr := member.UserID
if memberIDStr == excludeUserID {
continue
}
h.wsManager.SendToUser(memberIDStr, notice)
}
}
// SendGroupMemberNotice 发送群成员变动通知
func (h *WebSocketHandler) SendGroupMemberNotice(noticeType string, groupID string, data *ws.GroupNoticeData) {
notice := &ws.GroupNoticeMessage{
NoticeType: noticeType,
GroupID: groupID,
Data: data,
Timestamp: time.Now().UnixMilli(),
}
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupNotice, notice)
h.BroadcastGroupNotice(groupID, wsMsg)
}
// truncateContent 截断内容
func truncateContent(content string, maxLen int) string {
if len(content) <= maxLen {
return content
}
return content[:maxLen] + "..."
}
// BroadcastGroupTyping 向群组所有成员广播输入状态
func (h *WebSocketHandler) BroadcastGroupTyping(groupID string, typingMsg *ws.GroupTypingMessage, excludeUserID string) {
// 获取群组所有成员
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err != nil {
log.Printf("Failed to get group members for typing broadcast: %v", err)
return
}
// 创建WebSocket消息
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupTyping, typingMsg)
// 遍历成员,如果在线则发送消息
for _, member := range members {
memberIDStr := member.UserID
// 排除指定用户
if memberIDStr == excludeUserID {
continue
}
// 发送消息
h.wsManager.SendToUser(memberIDStr, wsMsg)
}
}
// BroadcastGroupRead 向群组所有成员广播已读状态
func (h *WebSocketHandler) BroadcastGroupRead(groupID string, readMsg map[string]interface{}, excludeUserID string) {
// 获取群组所有成员
members, _, err := h.groupRepo.GetMembers(groupID, 1, 1000)
if err != nil {
log.Printf("Failed to get group members for read broadcast: %v", err)
return
}
// 创建WebSocket消息
wsMsg := ws.CreateWSMessage(ws.MessageTypeGroupRead, readMsg)
// 遍历成员,如果在线则发送消息
for _, member := range members {
memberIDStr := member.UserID
// 排除指定用户
if memberIDStr == excludeUserID {
continue
}
// 发送消息
h.wsManager.SendToUser(memberIDStr, wsMsg)
}
}