feat:聊天信息支持 文件、图片、语音发送,增加一键启停脚本

This commit is contained in:
bujinyuan
2026-04-20 11:53:27 +08:00
parent 1a5aac9998
commit 0c1540bdee
116 changed files with 20277 additions and 144 deletions

View File

@@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
@@ -82,13 +83,19 @@ func (s *IMService) SendMessage(ctx context.Context, senderID int64, req *dto.Se
zap.Int64("conversation_id", req.ConversationID),
zap.Int64("target_user_id", req.TargetUserID))
if req.Content == "" {
return nil, ErrEmptyContent
}
if req.Type == 0 {
req.Type = constants.MessageTypeText
}
if req.Type != constants.MessageTypeText {
switch req.Type {
case constants.MessageTypeText:
if req.Content == "" {
return nil, ErrEmptyContent
}
case constants.MessageTypeImage, constants.MessageTypeVoice, constants.MessageTypeFile:
if req.Extra == "" {
return nil, ErrEmptyContent
}
default:
return nil, ErrInvalidMsgType
}
@@ -171,12 +178,16 @@ func (s *IMService) writeAndPushPrivateMessage(ctx context.Context, senderID, co
Status: constants.MessageStatusNormal,
ClientMsgID: req.ClientMsgID,
}
if req.Extra != "" {
msg.Extra = &req.Extra
}
if err := s.msgDAO.Create(ctx, msg); err != nil {
return nil, err
}
now := time.Now()
if err := s.convDAO.UpdateLastMessage(ctx, convID, msg.ID, truncateContent(req.Content, 100), senderID, now); err != nil {
preview := generateLastMsgPreview(req.Type, req.Content, msg.Extra)
if err := s.convDAO.UpdateLastMessage(ctx, convID, msg.ID, preview, senderID, now); err != nil {
logs.Error(ctx, funcName, "更新最后消息失败", zap.Error(err))
}
@@ -244,6 +255,9 @@ func (s *IMService) sendGroupMessage(ctx context.Context, senderID int64, req *d
Status: constants.MessageStatusNormal,
ClientMsgID: req.ClientMsgID,
}
if req.Extra != "" {
msg.Extra = &req.Extra
}
if len(req.AtUserIDs) > 0 {
msg.AtUserIDs = req.AtUserIDs
}
@@ -252,7 +266,8 @@ func (s *IMService) sendGroupMessage(ctx context.Context, senderID int64, req *d
}
now := time.Now()
if err := s.convDAO.UpdateLastMessage(ctx, convID, msg.ID, truncateContent(req.Content, 100), senderID, now); err != nil {
preview := generateLastMsgPreview(req.Type, req.Content, msg.Extra)
if err := s.convDAO.UpdateLastMessage(ctx, convID, msg.ID, preview, senderID, now); err != nil {
logs.Error(ctx, funcName, "更新最后消息失败", zap.Error(err))
}
@@ -944,6 +959,7 @@ func (s *IMService) toMessageDTO(m *model.Message) *dto.MessageDTO {
SenderID: m.SenderID,
Type: m.Type,
Content: m.Content,
Extra: m.Extra,
Status: m.Status,
ClientMsgID: m.ClientMsgID,
CreatedAt: m.CreatedAt.Format("2006-01-02 15:04:05"),
@@ -965,6 +981,9 @@ func (s *IMService) buildMessagePushData(ctx context.Context, msg *model.Message
"client_msg_id": msg.ClientMsgID,
"created_at": msg.CreatedAt.Format("2006-01-02 15:04:05"),
}
if msg.Extra != nil && *msg.Extra != "" {
pushData["extra"] = *msg.Extra
}
if senderUsers, sErr := s.userInfoGetter.GetUsersByIDs(ctx, []int64{senderID}); sErr == nil && len(senderUsers) > 0 {
pushData["sender_name"] = senderUsers[0].Nickname
pushData["sender_avatar"] = senderUsers[0].Avatar
@@ -978,6 +997,60 @@ type userBrief struct {
Avatar string
}
// generateLastMsgPreview 根据消息类型生成会话列表预览文案
func generateLastMsgPreview(msgType int, content string, extra *string) string {
switch msgType {
case constants.MessageTypeImage:
count := 1
if extra != nil && *extra != "" {
var parsed struct {
Images []json.RawMessage `json:"images"`
}
if json.Unmarshal([]byte(*extra), &parsed) == nil && len(parsed.Images) > 1 {
count = len(parsed.Images)
}
}
if count > 1 {
return fmt.Sprintf("[图片x%d]", count)
}
return "[图片]"
case constants.MessageTypeVoice:
duration := 0
if extra != nil && *extra != "" {
var parsed struct {
Voice struct {
Duration int `json:"duration"`
} `json:"voice"`
}
if json.Unmarshal([]byte(*extra), &parsed) == nil {
duration = parsed.Voice.Duration
}
}
if duration > 0 {
return fmt.Sprintf("[语音 %d\"]", duration)
}
return "[语音]"
case constants.MessageTypeFile:
fileName := ""
if extra != nil && *extra != "" {
var parsed struct {
File struct {
FileName string `json:"file_name"`
} `json:"file"`
}
if json.Unmarshal([]byte(*extra), &parsed) == nil {
fileName = parsed.File.FileName
}
}
if fileName != "" {
return fmt.Sprintf("[文件] %s", fileName)
}
return "[文件]"
default:
return truncateContent(content, 100)
}
}
// truncateContent 截断消息内容用于预览
func truncateContent(s string, maxRunes int) string {
runes := []rune(s)