聊天增加图片功能
This commit is contained in:
@@ -52,7 +52,8 @@ type MessageDTO struct {
|
||||
}
|
||||
|
||||
type SendMessageRequest struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
Content string `json:"content"`
|
||||
AttachmentURLS []string `json:"attachment_urls"`
|
||||
}
|
||||
|
||||
type TransferRequest struct {
|
||||
|
||||
@@ -340,7 +340,7 @@ func (h *Handler) send(c *gin.Context, principal Principal) {
|
||||
}
|
||||
var req SendMessageRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "消息内容不能为空")
|
||||
response.BadRequest(c, "消息格式不正确")
|
||||
return
|
||||
}
|
||||
message, err := h.service.SendMessage(principal, id, req)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
@@ -312,13 +313,13 @@ func (r *Repository) SendMessage(principal Principal, conversationID uint64, req
|
||||
SenderRole: participant.Role,
|
||||
ContentType: "text",
|
||||
Content: req.Content,
|
||||
AttachmentURLS: emptyJSONList(),
|
||||
AttachmentURLS: encodeStringList(req.AttachmentURLS),
|
||||
}
|
||||
if err := tx.Create(&message).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
conversation.LastMessageID = &message.ID
|
||||
conversation.LastMessagePreview = truncatePreview(message.Content)
|
||||
conversation.LastMessagePreview = messagePreview(message.Content, req.AttachmentURLS)
|
||||
conversation.LastMessageAt = &message.CreatedAt
|
||||
if err := tx.Save(&conversation).Error; err != nil {
|
||||
return err
|
||||
@@ -708,6 +709,17 @@ func truncatePreview(content string) string {
|
||||
return string(runes[:80])
|
||||
}
|
||||
|
||||
func messagePreview(content string, attachments []string) string {
|
||||
content = strings.TrimSpace(content)
|
||||
if content != "" {
|
||||
return truncatePreview(content)
|
||||
}
|
||||
if len(attachments) > 0 {
|
||||
return "[图片]"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TransferConversation 转接会话给其他客服
|
||||
func (r *Repository) TransferConversation(principal Principal, conversationID uint64, toAdminID uint64) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -908,6 +920,11 @@ func emptyJSONList() datatypes.JSON {
|
||||
return datatypes.JSON(raw)
|
||||
}
|
||||
|
||||
func encodeStringList(items []string) datatypes.JSON {
|
||||
raw, _ := json.Marshal(items)
|
||||
return datatypes.JSON(raw)
|
||||
}
|
||||
|
||||
// UpdateRemark 更新会话备注
|
||||
func (r *Repository) UpdateRemark(principal Principal, conversationID uint64, remark string) error {
|
||||
return r.db.Model(&model.ChatParticipant{}).
|
||||
|
||||
@@ -2,6 +2,7 @@ package chat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -63,15 +64,54 @@ func (s *Service) SendMessage(principal Principal, conversationID uint64, req Se
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
req.Content = strings.TrimSpace(req.Content)
|
||||
if conversationID == 0 || req.Content == "" {
|
||||
attachments, ok := normalizeAttachmentURLS(req.AttachmentURLS)
|
||||
req.AttachmentURLS = attachments
|
||||
if conversationID == 0 || (req.Content == "" && len(req.AttachmentURLS) == 0) {
|
||||
return nil, ErrInvalidMessage
|
||||
}
|
||||
if len([]rune(req.Content)) > 1000 {
|
||||
return nil, ErrInvalidMessage
|
||||
}
|
||||
if !ok {
|
||||
return nil, ErrInvalidMessage
|
||||
}
|
||||
return s.repo.SendMessage(principal, conversationID, req)
|
||||
}
|
||||
|
||||
func normalizeAttachmentURLS(items []string) ([]string, bool) {
|
||||
if len(items) > 9 {
|
||||
return nil, false
|
||||
}
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
value := strings.TrimSpace(item)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if len(value) > 500 || strings.Contains(value, "..") {
|
||||
return nil, false
|
||||
}
|
||||
parsed, err := url.Parse(value)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if parsed.Host != "" {
|
||||
return nil, false
|
||||
}
|
||||
switch parsed.Path {
|
||||
case "/api/files/object", "/api/admin/files/object", "/api/public/files/object":
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
key := strings.TrimSpace(parsed.Query().Get("key"))
|
||||
if key == "" || strings.Contains(key, "..") {
|
||||
return nil, false
|
||||
}
|
||||
result = append(result, value)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (s *Service) MarkRead(principal Principal, conversationID uint64) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
|
||||
@@ -41,10 +41,10 @@ func (s *Service) Upload(req uploadRequest) (*UploadDTO, error) {
|
||||
if contentType == "" {
|
||||
contentType = req.Header.Header.Get("Content-Type")
|
||||
}
|
||||
if !allowedContentTypes[contentType] {
|
||||
scene := normalizeScene(req.Scene)
|
||||
if !allowedContentTypes[contentType] || (scene == "chat" && !strings.HasPrefix(contentType, "image/")) {
|
||||
return nil, ErrInvalidFile
|
||||
}
|
||||
scene := normalizeScene(req.Scene)
|
||||
key, err := s.storage.Put(req.Context, scene, req.Header, req.Reader, contentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -65,7 +65,7 @@ func (s *Service) Upload(req uploadRequest) (*UploadDTO, error) {
|
||||
func normalizeScene(scene string) string {
|
||||
scene = strings.TrimSpace(strings.ToLower(scene))
|
||||
switch scene {
|
||||
case "listing", "handoff", "dispute", "realname", "avatar", "home-banner":
|
||||
case "listing", "handoff", "dispute", "realname", "avatar", "home-banner", "chat":
|
||||
return scene
|
||||
default:
|
||||
return "misc"
|
||||
|
||||
Reference in New Issue
Block a user