fix(shangwutong): 来源信息改为系统消息
This commit is contained in:
@@ -836,8 +836,7 @@ func (h *ConversationHandler) UpdateCustomAttributes(c *gin.Context) {
|
||||
if !requireConnectorShangwutongConversation(c, h.conversationSvc.DB(), conversation) {
|
||||
return
|
||||
}
|
||||
requestContext := service.WithShangwutongRequestMetadata(c.Request.Context(), middleware.IsConnectorService(c), currentUserID(c))
|
||||
conversation, svcErr := h.conversationSvc.UpdateCustomAttributes(requestContext, accountID, conversation.ID, req.CustomAttributes)
|
||||
conversation, svcErr := h.conversationSvc.UpdateCustomAttributes(c.Request.Context(), accountID, conversation.ID, req.CustomAttributes)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gochat/gochat/internal/channel"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/repository"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestUpdateCustomAttributesMergesConnectorPayload(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.AutoMigrate(&model.Conversation{}))
|
||||
|
||||
conversation := model.Conversation{
|
||||
AccountID: 1,
|
||||
CustomAttributes: datatypes.JSON([]byte(`{"swt_sid":"sid","swt_state":"chatting"}`)),
|
||||
}
|
||||
require.NoError(t, db.Create(&conversation).Error)
|
||||
|
||||
svc := NewConversationService(repository.NewConversationRepo(db), repository.NewMessageRepo(db), channel.NewDispatcher(), nil, nil, nil, nil)
|
||||
ctx := WithShangwutongRequestMetadata(context.Background(), true, 0)
|
||||
updated, err := svc.UpdateCustomAttributes(ctx, 1, conversation.ID, datatypes.JSON([]byte(`{"swt_source_channel":"百度搜索推广"}`)))
|
||||
require.NoError(t, err)
|
||||
|
||||
attributes := map[string]any{}
|
||||
require.NoError(t, json.Unmarshal(updated.CustomAttributes, &attributes))
|
||||
require.Equal(t, "sid", attributes["swt_sid"])
|
||||
require.Equal(t, "chatting", attributes["swt_state"])
|
||||
require.Equal(t, "百度搜索推广", attributes["swt_source_channel"])
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
@@ -1647,28 +1646,6 @@ func (s *ConversationService) UpdateCustomAttributes(ctx context.Context, accoun
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata, _ := ctx.Value(shangwutongRequestMetadataKey{}).(shangwutongRequestMetadata)
|
||||
if metadata.ConnectorOrigin {
|
||||
merged := map[string]any{}
|
||||
if len(conversation.CustomAttributes) > 0 {
|
||||
if err := json.Unmarshal(conversation.CustomAttributes, &merged); err != nil {
|
||||
return nil, fmt.Errorf("decode existing conversation custom attributes: %w", err)
|
||||
}
|
||||
}
|
||||
incoming := map[string]any{}
|
||||
if err := json.Unmarshal(attrs, &incoming); err != nil {
|
||||
return nil, fmt.Errorf("decode incoming conversation custom attributes: %w", err)
|
||||
}
|
||||
for key, value := range incoming {
|
||||
merged[key] = value
|
||||
}
|
||||
encoded, err := json.Marshal(merged)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode merged conversation custom attributes: %w", err)
|
||||
}
|
||||
attrs = datatypes.JSON(encoded)
|
||||
}
|
||||
|
||||
if err := s.repo.UpdateCustomAttributes(ctx, id, attrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Restoring the old custom-attribute presentation is intentionally unsupported.
|
||||
-- Migration 000075 remains the historical definition of that superseded UI.
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,52 @@
|
||||
DELETE FROM custom_attribute_definitions
|
||||
WHERE attribute_model = 'conversation_attribute'
|
||||
AND attribute_name IN (
|
||||
'swt_source_url',
|
||||
'swt_source_search_term',
|
||||
'swt_source_purchase_term',
|
||||
'swt_source_keyword_id',
|
||||
'swt_source_channel',
|
||||
'swt_source_realtime_location',
|
||||
'swt_source_region',
|
||||
'swt_source_ad_account_id',
|
||||
'swt_source_wakeable',
|
||||
'swt_baidu_conversation_type',
|
||||
'swt_baidu_agent_name',
|
||||
'swt_baidu_ssid'
|
||||
);
|
||||
|
||||
UPDATE conversations
|
||||
SET
|
||||
custom_attributes = COALESCE(custom_attributes, '{}'::jsonb)
|
||||
- 'swt_source_url'
|
||||
- 'swt_source_description'
|
||||
- 'swt_source_search_term'
|
||||
- 'swt_source_purchase_term'
|
||||
- 'swt_source_keyword_id'
|
||||
- 'swt_source_channel'
|
||||
- 'swt_source_realtime_location'
|
||||
- 'swt_source_region'
|
||||
- 'swt_source_ad_account_id'
|
||||
- 'swt_source_wakeable'
|
||||
- 'swt_baidu_conversation_type'
|
||||
- 'swt_baidu_agent_name'
|
||||
- 'swt_baidu_ssid',
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE COALESCE(custom_attributes, '{}'::jsonb) ?| ARRAY[
|
||||
'swt_source_url',
|
||||
'swt_source_description',
|
||||
'swt_source_search_term',
|
||||
'swt_source_purchase_term',
|
||||
'swt_source_keyword_id',
|
||||
'swt_source_channel',
|
||||
'swt_source_realtime_location',
|
||||
'swt_source_region',
|
||||
'swt_source_ad_account_id',
|
||||
'swt_source_wakeable',
|
||||
'swt_baidu_conversation_type',
|
||||
'swt_baidu_agent_name',
|
||||
'swt_baidu_ssid'
|
||||
];
|
||||
|
||||
-- Historical kind=8 system messages are backfilled from the Connector SQLite
|
||||
-- store because PostgreSQL intentionally does not retain the raw encoded payload.
|
||||
@@ -127,9 +127,9 @@ func mapInboundEvent(kind int64, seqID int64, text, operator, rawTimestamp, sour
|
||||
mapped.Strategy, mapped.RawOnly, mapped.RequiresContact = "contact_attributes", false, true
|
||||
mapped.ContactAttributes = parseEnvironment(text)
|
||||
case 8:
|
||||
mapped.Strategy, mapped.RawOnly, mapped.RequiresContact = "conversation_attributes", false, true
|
||||
mapped.ConversationAttrs = parseSource(text)
|
||||
mapped.RequiresConversation = len(mapped.ConversationAttrs) > 0
|
||||
if content := sourceActivityContent(parseSource(text)); content != "" {
|
||||
activity(content)
|
||||
}
|
||||
case 11:
|
||||
// Account presence is applied in the heartbeat transaction.
|
||||
case 12:
|
||||
@@ -621,6 +621,39 @@ func parseSource(text string) map[string]any {
|
||||
return result
|
||||
}
|
||||
|
||||
func sourceActivityContent(attributes map[string]any) string {
|
||||
if len(attributes) == 0 {
|
||||
return ""
|
||||
}
|
||||
lines := []string{"访客来源信息"}
|
||||
fields := []struct {
|
||||
key string
|
||||
label string
|
||||
}{
|
||||
{"swt_source_search_term", "搜索词"},
|
||||
{"swt_source_purchase_term", "购买词"},
|
||||
{"swt_source_keyword_id", "关键词 ID"},
|
||||
{"swt_source_channel", "流量渠道"},
|
||||
{"swt_source_realtime_location", "实时位置"},
|
||||
{"swt_source_region", "地域"},
|
||||
{"swt_source_ad_account_id", "推广账户 ID"},
|
||||
{"swt_source_wakeable", "是否可唤醒"},
|
||||
{"swt_baidu_conversation_type", "百度会话类型"},
|
||||
{"swt_baidu_agent_name", "百度智能体名称"},
|
||||
{"swt_baidu_ssid", "百度 SSID"},
|
||||
{"swt_source_url", "来源页面"},
|
||||
}
|
||||
for _, field := range fields {
|
||||
if value, ok := attributes[field.key].(string); ok && strings.TrimSpace(value) != "" {
|
||||
lines = append(lines, field.label+":"+strings.TrimSpace(value))
|
||||
}
|
||||
}
|
||||
if len(lines) == 1 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(lines, "<br>")
|
||||
}
|
||||
|
||||
func sourceDescriptionValue(description, label string) string {
|
||||
for _, line := range strings.Split(description, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
@@ -92,23 +92,26 @@ func TestKind8ParsesBaiduSourceDetails(t *testing.T) {
|
||||
"%e7%99%be%e5%ba%a6%e6%99%ba%e8%83%bd%e4%bd%93%e5%90%8d%e7%a7%b0%ef%bc%9a%e8%b4%b5%e5%b7%9e%e7%9b%9b%e4%ba%ac%e4%b8%ad%e5%8c%bb%e8%82%9d%e7%97%85%e5%8c%bb%e9%99%a2%3cbr+%2f%3e" +
|
||||
"%e7%99%be%e5%ba%a6ssid%ef%bc%9a71cbbfedf1e09620d7823b614ecd4de3%3cbr+%2f%3e%3c%2fdiv%3e"
|
||||
mapped := mapInboundEvent(8, 42, text, "", "", "source", 10, time.Now())
|
||||
want := map[string]string{
|
||||
"swt_source_url": "https://ada.baidu.com/site/test/agent?word=%E4%B8%89%E9%98%B3",
|
||||
"swt_source_search_term": "三阳病毒携带",
|
||||
"swt_source_purchase_term": "三阳病毒携带",
|
||||
"swt_source_keyword_id": "1263742519801",
|
||||
"swt_source_channel": "百度搜索推广",
|
||||
"swt_source_realtime_location": "贵州黔南",
|
||||
"swt_source_region": "贵州黔南",
|
||||
"swt_source_ad_account_id": "48989266",
|
||||
"swt_source_wakeable": "是",
|
||||
"swt_baidu_conversation_type": "百度智能客服--百度商家智能体",
|
||||
"swt_baidu_agent_name": "贵州盛京中医肝病医院",
|
||||
"swt_baidu_ssid": "71cbbfedf1e09620d7823b614ecd4de3",
|
||||
want := []string{
|
||||
"来源页面:https://ada.baidu.com/site/test/agent?word=%E4%B8%89%E9%98%B3",
|
||||
"搜索词:三阳病毒携带",
|
||||
"购买词:三阳病毒携带",
|
||||
"关键词 ID:1263742519801",
|
||||
"流量渠道:百度搜索推广",
|
||||
"实时位置:贵州黔南",
|
||||
"地域:贵州黔南",
|
||||
"推广账户 ID:48989266",
|
||||
"是否可唤醒:是",
|
||||
"百度会话类型:百度智能客服--百度商家智能体",
|
||||
"百度智能体名称:贵州盛京中医肝病医院",
|
||||
"百度 SSID:71cbbfedf1e09620d7823b614ecd4de3",
|
||||
}
|
||||
for key, value := range want {
|
||||
if mapped.ConversationAttrs[key] != value {
|
||||
t.Fatalf("%s = %#v, want %q; attributes=%#v", key, mapped.ConversationAttrs[key], value, mapped.ConversationAttrs)
|
||||
if mapped.Message == nil || mapped.Message.MessageType != "activity" || mapped.Strategy != "activity" || len(mapped.ConversationAttrs) != 0 {
|
||||
t.Fatalf("kind=8 mapping = %#v", mapped)
|
||||
}
|
||||
for _, expected := range want {
|
||||
if !strings.Contains(mapped.Message.Content, expected) {
|
||||
t.Fatalf("activity content missing %q: %q", expected, mapped.Message.Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,8 +121,8 @@ func TestKind8ParsesLatestWakeableState(t *testing.T) {
|
||||
"%e6%90%9c%e7%b4%a2%e8%af%8d%ef%bc%9a%e4%b8%89%e9%98%b3%3cbr%2f%3e" +
|
||||
"%e6%98%af%e5%90%a6%e5%8f%af%e5%94%a4%e9%86%92%ef%bc%9a%e6%98%af%3c%2fdiv%3e"
|
||||
mapped := mapInboundEvent(8, 43, text, "", "", "source", 10, time.Now())
|
||||
if mapped.ConversationAttrs["swt_source_search_term"] != "三阳" || mapped.ConversationAttrs["swt_source_wakeable"] != "是" {
|
||||
t.Fatalf("source attributes = %#v", mapped.ConversationAttrs)
|
||||
if mapped.Message == nil || !strings.Contains(mapped.Message.Content, "搜索词:三阳") || !strings.Contains(mapped.Message.Content, "是否可唤醒:是") {
|
||||
t.Fatalf("source activity = %#v", mapped.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +205,7 @@ func TestKnownKindMappingMatrix(t *testing.T) {
|
||||
{0, "5", "conversation_attributes", false}, {2, "hello", "native_message", false},
|
||||
{3, "hello", "native_message", false}, {5, "unverified", "raw_only", true},
|
||||
{7, "127.0.0.1 Beijing ISP 0 0 0 0 1920x1080 24 zh-CN +8 Windows 0 Chrome 149", "contact_attributes", false},
|
||||
{8, "https://example.test 0 Landing 0 0 0 friendlink", "conversation_attributes", false},
|
||||
{8, "https%3A%2F%2Fexample.test+%3Cdiv%3E%E6%90%9C%E7%B4%A2%E8%AF%8D%EF%BC%9Atest%3C%2Fdiv%3E", "activity", false},
|
||||
{11, "3", "raw_only", true}, {12, "alias", "contact_attributes", false},
|
||||
{15, "filemsg|name|https://media.example/file", "native_message", false},
|
||||
{26, "张三|13800138000", "activity", false}, {29, "red", "conversation_attributes", false},
|
||||
|
||||
Reference in New Issue
Block a user