This commit introduces several architectural improvements and optimizations across the codebase: - **Performance & Reliability**: - Implemented Redis pipelining in `ConversationCache.CacheMessage` to reduce network round-trips. - Added a circuit breaker to the JPush client to prevent cascading failures. - Introduced batch deletion and batch member addition capabilities in repositories. - Added message idempotency support using `client_msg_id` and a Redis-based cache. - Optimized WebSocket handling with connection limits (total and per-user) and improved error logging. - **Code Refactoring**: - Refactored `Router` to use a `RouterDeps` struct, simplifying the constructor and improving maintainability. - Unified model ID generation logic using new `id_helper.go` (supporting UUID and Snowflake). - Standardized JSON serialization/deserialization in models using `json_helper.go`. - Refactored DTO conversion logic, specifically for `UserResponse` (using functional options) and `Report` responses. - Removed redundant/deprecated DTOs like `PostDetailResponse` and `TradeItemDetailResponse`. - **Cache Improvements**: - Enhanced `LayeredCache` with `SetRaw` to avoid double-encoding when promoting values from Redis to local cache. - Added `DeleteBatch` support to the cache interface. - **Other Changes**: - Cleaned up `config.go` by removing redundant default values and explicit environment variable overrides. - Improved WebSocket registration flow to handle connection limits gracefully.
170 lines
5.7 KiB
Go
170 lines
5.7 KiB
Go
package dto
|
||
|
||
import (
|
||
"with_you/internal/model"
|
||
"with_you/internal/pkg/utils"
|
||
)
|
||
|
||
type UserResponseOption func(*UserResponse)
|
||
|
||
func WithFollowing(isFollowing, isFollowingMe bool) UserResponseOption {
|
||
return func(r *UserResponse) {
|
||
r.IsFollowing = isFollowing
|
||
r.IsFollowingMe = isFollowingMe
|
||
}
|
||
}
|
||
|
||
func WithPostsCount(count int) UserResponseOption {
|
||
return func(r *UserResponse) {
|
||
r.PostsCount = count
|
||
}
|
||
}
|
||
|
||
// getAvatarOrDefault 获取头像URL,如果为空则返回在线头像生成服务的URL
|
||
func getAvatarOrDefault(user *model.User) string {
|
||
return utils.GetAvatarOrDefault(user.Username, user.Nickname, user.Avatar)
|
||
}
|
||
|
||
// publicVerificationFields 返回认证信息:
|
||
// - approved 时返回真实身份和状态
|
||
// - 其他状态统一返回 general + none,不暴露具体审核状态
|
||
func publicVerificationFields(user *model.User) (model.UserIdentity, model.VerificationStatus) {
|
||
if user.VerificationStatus == model.VerificationStatusApproved {
|
||
return user.Identity, user.VerificationStatus
|
||
}
|
||
return model.UserIdentityGeneral, model.VerificationStatusNone
|
||
}
|
||
|
||
func baseUserResponse(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),
|
||
Identity: identity,
|
||
VerificationStatus: vs,
|
||
}
|
||
}
|
||
|
||
// ConvertUserToResponse 将User转换为UserResponse(公开信息,不包含敏感数据)
|
||
func ConvertUserToResponse(user *model.User, opts ...UserResponseOption) *UserResponse {
|
||
r := baseUserResponse(user)
|
||
if r == nil {
|
||
return nil
|
||
}
|
||
for _, opt := range opts {
|
||
opt(r)
|
||
}
|
||
return r
|
||
}
|
||
|
||
// ConvertUserToSelfResponse 将User转换为UserSelfResponse(本人信息,包含敏感数据)
|
||
func ConvertUserToSelfResponse(user *model.User, postsCount int) *UserSelfResponse {
|
||
if user == nil {
|
||
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),
|
||
Identity: user.Identity,
|
||
VerificationStatus: user.VerificationStatus,
|
||
}
|
||
}
|
||
|
||
// ConvertUserToDetailResponseWithPostsCount 将User转换为UserDetailResponse(使用实时计算的帖子数量,仅本人可见)
|
||
func ConvertUserToDetailResponseWithPostsCount(user *model.User, postsCount int) *UserDetailResponse {
|
||
if user == nil {
|
||
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),
|
||
Identity: user.Identity,
|
||
VerificationStatus: user.VerificationStatus,
|
||
}
|
||
}
|
||
|
||
func convertPrivacySettingsToResponse(ps *model.PrivacySettings) *PrivacySettingsResponse {
|
||
if ps == nil {
|
||
return nil
|
||
}
|
||
return &PrivacySettingsResponse{
|
||
FollowersVisibility: string(ps.FollowersVisibility),
|
||
FollowingVisibility: string(ps.FollowingVisibility),
|
||
PostsVisibility: string(ps.PostsVisibility),
|
||
FavoritesVisibility: string(ps.FavoritesVisibility),
|
||
}
|
||
}
|
||
|
||
// ConvertUsersToResponse 将User列表转换为响应列表
|
||
func ConvertUsersToResponse(users []*model.User, opts ...UserResponseOption) []*UserResponse {
|
||
result := make([]*UserResponse, 0, len(users))
|
||
for _, user := range users {
|
||
result = append(result, ConvertUserToResponse(user, opts...))
|
||
}
|
||
return result
|
||
}
|
||
|
||
// ConvertUsersToResponseWithMutualFollowAndPostsCount 将User列表转换为响应列表(包含双向关注状态和帖子数量)
|
||
// followingStatusMap: key是用户ID,value是[isFollowing, isFollowingMe]
|
||
// postsCountMap: key是用户ID,value是帖子数量
|
||
func ConvertUsersToResponseWithMutualFollowAndPostsCount(users []*model.User, followingStatusMap map[string][2]bool, postsCountMap map[string]int64) []*UserResponse {
|
||
result := make([]*UserResponse, 0, len(users))
|
||
for _, user := range users {
|
||
status, hasStatus := followingStatusMap[user.ID]
|
||
postsCount, hasPostsCount := postsCountMap[user.ID]
|
||
if !hasPostsCount {
|
||
postsCount = int64(user.PostsCount)
|
||
}
|
||
var opts []UserResponseOption
|
||
if hasStatus {
|
||
opts = append(opts, WithFollowing(status[0], status[1]))
|
||
}
|
||
opts = append(opts, WithPostsCount(int(postsCount)))
|
||
result = append(result, ConvertUserToResponse(user, opts...))
|
||
}
|
||
return result
|
||
} |