From 47649d33e28d90cc30f6d92ae5b4d70f7ee6bb65 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Fri, 4 Sep 2026 13:45:18 +0800 Subject: [PATCH] Fix friends display name fallback --- server/db/mysql/tenant_adapter.go | 14 ++----- server/db/mysql/user_identity_test.go | 15 ++++--- server/im_http.go | 3 ++ server/im_http_test.go | 59 ++++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/server/db/mysql/tenant_adapter.go b/server/db/mysql/tenant_adapter.go index 7e9a172..127902d 100644 --- a/server/db/mysql/tenant_adapter.go +++ b/server/db/mysql/tenant_adapter.go @@ -84,22 +84,16 @@ func profilePublicFromFields(base any, nickname, avatarFileID sql.NullString) an public := publicMap(base) if nickname.Valid { value := strings.TrimSpace(nickname.String) - for _, key := range []string{"display_name", "nickname", "fn", "name"} { - if value == "" { - delete(public, key) - } else { + if value != "" { + for _, key := range []string{"display_name", "nickname", "fn", "name"} { public[key] = value } } } if avatarFileID.Valid { value := strings.TrimSpace(avatarFileID.String) - for _, key := range []string{"avatar_file_id", "photo", "avatar"} { - if value == "" { - delete(public, key) - } else if key == "avatar_file_id" { - public[key] = value - } + if value != "" { + public["avatar_file_id"] = value } } return public diff --git a/server/db/mysql/user_identity_test.go b/server/db/mysql/user_identity_test.go index 1416c43..efd3d87 100644 --- a/server/db/mysql/user_identity_test.go +++ b/server/db/mysql/user_identity_test.go @@ -47,13 +47,12 @@ func TestProfilePublicFromFields(t *testing.T) { t.Fatalf("profile public not overlaid: %#v", public) } - public = profilePublicFromFields(public, sql.NullString{Valid: true}, sql.NullString{Valid: true}).(map[string]any) - for _, key := range []string{"fn", "name", "nickname", "display_name", "avatar_file_id", "photo", "avatar"} { - if _, ok := public[key]; ok { - t.Fatalf("field %s should be hidden by empty profile value: %#v", key, public) - } - } - if public["public_account"] != "usrAccount" { - t.Fatalf("unrelated public field changed: %#v", public) + public = profilePublicFromFields(map[string]any{ + "fn": "旧昵称", + "avatar_file_id": "old_avatar", + "public_account": "usrAccount", + }, sql.NullString{Valid: true}, sql.NullString{Valid: true}).(map[string]any) + if public["fn"] != "旧昵称" || public["avatar_file_id"] != "old_avatar" || public["public_account"] != "usrAccount" { + t.Fatalf("empty profile fields should keep existing public values: %#v", public) } } diff --git a/server/im_http.go b/server/im_http.go index 95252bf..caab605 100644 --- a/server/im_http.go +++ b/server/im_http.go @@ -8539,6 +8539,9 @@ func friendListItemPayload(item types.FriendListItem) map[string]any { } } } + if item.Friendship.Remark == "" && data["display_name"] == data["user_id"] { + data["display_name_source"] = "user_id" + } data["initial"] = displayInitial(data["display_name"]) return data } diff --git a/server/im_http_test.go b/server/im_http_test.go index d339ca8..1a0d0df 100644 --- a/server/im_http_test.go +++ b/server/im_http_test.go @@ -6274,11 +6274,68 @@ func TestServeIMFriendsList(t *testing.T) { } data := decodeIMHTTPResponse(t, rec).Data.(map[string]any) items := data["items"].([]any) - if len(items) != 1 || items[0].(map[string]any)["display_name"] != "小明" { + if len(items) != 1 || items[0].(map[string]any)["display_name"] != "小明" || items[0].(map[string]any)["display_name_source"] != "remark" { t.Fatalf("unexpected friends list: %#v", data) } } +func TestServeIMFriendsListUsesUserIDSourceOnlyForFallback(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + expectIMBearer(t, ctrl, "token-1", 7, 1) + oldRelations := store.Relations + now := time.Now().UTC().Round(time.Millisecond) + namedPeer := types.Uid(2) + unnamedPeer := types.Uid(3) + store.Relations = &testRelationsStore{friends: []types.FriendListItem{{ + Friendship: types.Friendship{ + TenantID: 7, + UserID: 1, + PeerID: namedPeer, + Status: types.RelationshipFriend, + Topic: types.Uid(1).P2PName(namedPeer), + CreatedAt: now, + UpdatedAt: now, + }, + Peer: &types.User{ObjHeader: types.ObjHeader{Id: namedPeer.String()}, TenantID: 7, Public: map[string]any{"fn": "yang"}}, + }, { + Friendship: types.Friendship{ + TenantID: 7, + UserID: 1, + PeerID: unnamedPeer, + Status: types.RelationshipFriend, + Topic: types.Uid(1).P2PName(unnamedPeer), + CreatedAt: now, + UpdatedAt: now, + }, + Peer: &types.User{ObjHeader: types.ObjHeader{Id: unnamedPeer.String()}, TenantID: 7, Public: map[string]any{}}, + }}} + defer func() { store.Relations = oldRelations }() + + req := httptest.NewRequest(http.MethodGet, "/v1/im/friends", nil) + req.Header.Set("Authorization", "Bearer token-1") + rec := httptest.NewRecorder() + + serveIMFriends(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String()) + } + data := decodeIMHTTPResponse(t, rec).Data.(map[string]any) + items := data["items"].([]any) + if len(items) != 2 { + t.Fatalf("unexpected friends list length: %#v", data) + } + named := items[0].(map[string]any) + if named["display_name"] != "yang" || named["display_name_source"] != "profile" { + t.Fatalf("named friend should use profile display: %#v", named) + } + unnamed := items[1].(map[string]any) + if unnamed["display_name"] != unnamed["user_id"] || unnamed["display_name_source"] != "user_id" { + t.Fatalf("unnamed friend should use user_id fallback source: %#v", unnamed) + } +} + func TestServeIMFriendRemark(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()