From f99687c405a764583fe89b16330f98fd962537aa Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Sun, 26 Apr 2026 19:38:16 +0800 Subject: [PATCH 1/2] feat(user): add identity and verification status to user DTOs Add Identity and VerificationStatus fields to UserResponse, UserSelfResponse, and UserDetailResponse. Only expose identity information when verification status is approved to protect privacy of pending/rejected applicants. --- internal/dto/dto.go | 9 ++ internal/dto/user_converter.go | 271 ++++++++++++++++++--------------- 2 files changed, 159 insertions(+), 121 deletions(-) diff --git a/internal/dto/dto.go b/internal/dto/dto.go index fe0ef7d..fc4852f 100644 --- a/internal/dto/dto.go +++ b/internal/dto/dto.go @@ -24,6 +24,9 @@ type UserResponse struct { IsFollowing bool `json:"is_following"` IsFollowingMe bool `json:"is_following_me"` CreatedAt string `json:"created_at"` + // 认证信息 + Identity model.UserIdentity `json:"identity,omitempty"` + VerificationStatus model.VerificationStatus `json:"verification_status,omitempty"` } // UserSelfResponse 用户自身信息响应(包含敏感信息,仅本人可见) @@ -46,6 +49,9 @@ type UserSelfResponse struct { CreatedAt string `json:"created_at"` // 隐私设置 PrivacySettings *PrivacySettingsResponse `json:"privacy_settings,omitempty"` + // 认证信息 + Identity model.UserIdentity `json:"identity,omitempty"` + VerificationStatus model.VerificationStatus `json:"verification_status,omitempty"` } // UserDetailResponse 用户详情响应(仅本人可见,包含敏感信息) @@ -70,6 +76,9 @@ type UserDetailResponse struct { CreatedAt string `json:"created_at"` // 隐私设置 PrivacySettings *PrivacySettingsResponse `json:"privacy_settings,omitempty"` + // 认证信息 + Identity model.UserIdentity `json:"identity,omitempty"` + VerificationStatus model.VerificationStatus `json:"verification_status,omitempty"` } // ==================== Admin User DTOs ==================== diff --git a/internal/dto/user_converter.go b/internal/dto/user_converter.go index d21502f..45a1473 100644 --- a/internal/dto/user_converter.go +++ b/internal/dto/user_converter.go @@ -12,25 +12,36 @@ func getAvatarOrDefault(user *model.User) string { return utils.GetAvatarOrDefault(user.Username, user.Nickname, user.Avatar) } +// publicVerificationFields 仅在认证通过时返回身份和状态,避免泄漏审核中/已拒绝等隐私信息 +func publicVerificationFields(user *model.User) (model.UserIdentity, model.VerificationStatus) { + if user.VerificationStatus == model.VerificationStatusApproved { + return user.Identity, user.VerificationStatus + } + return "", "" +} + // ConvertUserToResponse 将User转换为UserResponse(公开信息,不包含敏感数据) func ConvertUserToResponse(user *model.User) *UserResponse { if user == nil { return nil } + identity, vs := publicVerificationFields(user) return &UserResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: user.PostsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - CreatedAt: FormatTime(user.CreatedAt), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: user.PostsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + CreatedAt: FormatTime(user.CreatedAt), + Identity: identity, + VerificationStatus: vs, } } @@ -40,23 +51,25 @@ func ConvertUserToSelfResponse(user *model.User, postsCount int) *UserSelfRespon return nil } return &UserSelfResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - Email: user.Email, - Phone: user.Phone, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: postsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - IsVerified: user.IsVerified, - CreatedAt: FormatTime(user.CreatedAt), - PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Email: user.Email, + Phone: user.Phone, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: postsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + IsVerified: user.IsVerified, + CreatedAt: FormatTime(user.CreatedAt), + PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings), + Identity: user.Identity, + VerificationStatus: user.VerificationStatus, } } @@ -77,22 +90,25 @@ func ConvertUserToResponseWithFollowing(user *model.User, isFollowing bool) *Use if user == nil { return nil } + identity, vs := publicVerificationFields(user) return &UserResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: user.PostsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - IsFollowing: isFollowing, - IsFollowingMe: false, - CreatedAt: FormatTime(user.CreatedAt), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: user.PostsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + IsFollowing: isFollowing, + IsFollowingMe: false, + CreatedAt: FormatTime(user.CreatedAt), + Identity: identity, + VerificationStatus: vs, } } @@ -101,20 +117,23 @@ func ConvertUserToResponseWithPostsCount(user *model.User, postsCount int) *User if user == nil { return nil } + identity, vs := publicVerificationFields(user) return &UserResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: postsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - CreatedAt: FormatTime(user.CreatedAt), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: postsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + CreatedAt: FormatTime(user.CreatedAt), + Identity: identity, + VerificationStatus: vs, } } @@ -123,22 +142,25 @@ func ConvertUserToResponseWithMutualFollow(user *model.User, isFollowing, isFoll if user == nil { return nil } + identity, vs := publicVerificationFields(user) return &UserResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: user.PostsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - IsFollowing: isFollowing, - IsFollowingMe: isFollowingMe, - CreatedAt: FormatTime(user.CreatedAt), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: user.PostsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + IsFollowing: isFollowing, + IsFollowingMe: isFollowingMe, + CreatedAt: FormatTime(user.CreatedAt), + Identity: identity, + VerificationStatus: vs, } } @@ -147,22 +169,25 @@ func ConvertUserToResponseWithMutualFollowAndPostsCount(user *model.User, isFoll if user == nil { return nil } + identity, vs := publicVerificationFields(user) return &UserResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: postsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - IsFollowing: isFollowing, - IsFollowingMe: isFollowingMe, - CreatedAt: FormatTime(user.CreatedAt), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: postsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + IsFollowing: isFollowing, + IsFollowingMe: isFollowingMe, + CreatedAt: FormatTime(user.CreatedAt), + Identity: identity, + VerificationStatus: vs, } } @@ -172,22 +197,24 @@ func ConvertUserToDetailResponse(user *model.User) *UserDetailResponse { return nil } return &UserDetailResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - Email: user.Email, - EmailVerified: user.EmailVerified, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: user.PostsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - IsVerified: user.IsVerified, - CreatedAt: FormatTime(user.CreatedAt), - PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Email: user.Email, + EmailVerified: user.EmailVerified, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: user.PostsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + IsVerified: user.IsVerified, + CreatedAt: FormatTime(user.CreatedAt), + PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings), + Identity: user.Identity, + VerificationStatus: user.VerificationStatus, } } @@ -197,23 +224,25 @@ func ConvertUserToDetailResponseWithPostsCount(user *model.User, postsCount int) return nil } return &UserDetailResponse{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - Email: user.Email, - EmailVerified: user.EmailVerified, - Phone: user.Phone, - Avatar: getAvatarOrDefault(user), - CoverURL: user.CoverURL, - Bio: user.Bio, - Website: user.Website, - Location: user.Location, - PostsCount: postsCount, - FollowersCount: user.FollowersCount, - FollowingCount: user.FollowingCount, - IsVerified: user.IsVerified, - CreatedAt: FormatTime(user.CreatedAt), - PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings), + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Email: user.Email, + EmailVerified: user.EmailVerified, + Phone: user.Phone, + Avatar: getAvatarOrDefault(user), + CoverURL: user.CoverURL, + Bio: user.Bio, + Website: user.Website, + Location: user.Location, + PostsCount: postsCount, + FollowersCount: user.FollowersCount, + FollowingCount: user.FollowingCount, + IsVerified: user.IsVerified, + CreatedAt: FormatTime(user.CreatedAt), + PrivacySettings: convertPrivacySettingsToResponse(&user.PrivacySettings), + Identity: user.Identity, + VerificationStatus: user.VerificationStatus, } } -- 2.49.1 From 5d12397e29487511e255c43b66494019319c7725 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Sun, 26 Apr 2026 19:42:10 +0800 Subject: [PATCH 2/2] fix(user): add VerificationStatusNone for users without verification Add VerificationStatusNone constant and update converter to return general identity with none status instead of empty strings when user has not applied for verification. --- internal/dto/user_converter.go | 6 ++++-- internal/model/user.go | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/dto/user_converter.go b/internal/dto/user_converter.go index 45a1473..3618ac6 100644 --- a/internal/dto/user_converter.go +++ b/internal/dto/user_converter.go @@ -12,12 +12,14 @@ func getAvatarOrDefault(user *model.User) string { return utils.GetAvatarOrDefault(user.Username, user.Nickname, user.Avatar) } -// publicVerificationFields 仅在认证通过时返回身份和状态,避免泄漏审核中/已拒绝等隐私信息 +// publicVerificationFields 返回认证信息: +// - approved 时返回真实身份和状态 +// - 其他状态统一返回 general + none,不暴露具体审核状态 func publicVerificationFields(user *model.User) (model.UserIdentity, model.VerificationStatus) { if user.VerificationStatus == model.VerificationStatusApproved { return user.Identity, user.VerificationStatus } - return "", "" + return model.UserIdentityGeneral, model.VerificationStatusNone } // ConvertUserToResponse 将User转换为UserResponse(公开信息,不包含敏感数据) diff --git a/internal/model/user.go b/internal/model/user.go index ac1561b..b465ad1 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -54,6 +54,7 @@ const ( VerificationStatusPending VerificationStatus = "pending" // 审核中 VerificationStatusApproved VerificationStatus = "approved" // 已通过 VerificationStatusRejected VerificationStatus = "rejected" // 已拒绝 + VerificationStatusNone VerificationStatus = "none" // 无认证(对外展示用) ) // User 用户实体 -- 2.49.1