diff --git a/internal/cache/keys.go b/internal/cache/keys.go index bd2de12..a8b856b 100644 --- a/internal/cache/keys.go +++ b/internal/cache/keys.go @@ -86,25 +86,13 @@ func ConversationListKey(userID string, page, pageSize int) string { return fmt.Sprintf("%s:%s:%d:%d", PrefixConversationList, userID, page, pageSize) } -// ConversationDetailKey 生成会话详情缓存键 -func ConversationDetailKey(conversationID, userID string) string { - return fmt.Sprintf("%s:%s:%s", PrefixConversationDetail, conversationID, userID) -} // GroupMembersKey 生成群组成员缓存键 func GroupMembersKey(groupID string, page, pageSize int) string { return fmt.Sprintf("%s:%s:page:%d:size:%d", PrefixGroupMembers, groupID, page, pageSize) } -// GroupMembersAllKey 生成群组全量成员ID列表缓存键 -func GroupMembersAllKey(groupID string) string { - return fmt.Sprintf("%s:all:%s", PrefixGroupMembers, groupID) -} -// GroupInfoKey 生成群组信息缓存键 -func GroupInfoKey(groupID string) string { - return fmt.Sprintf("%s:%s", PrefixGroupInfo, groupID) -} // UnreadSystemKey 生成系统消息未读数缓存键 func UnreadSystemKey(userID string) string { @@ -121,15 +109,6 @@ func UnreadDetailKey(userID, conversationID string) string { return fmt.Sprintf("%s:%s:%s", PrefixUnreadDetail, userID, conversationID) } -// UserInfoKey 生成用户信息缓存键 -func UserInfoKey(userID string) string { - return fmt.Sprintf("%s:%s", PrefixUserInfo, userID) -} - -// UserMeKey 生成当前用户信息缓存键 -func UserMeKey(userID string) string { - return fmt.Sprintf("%s:%s", PrefixUserMe, userID) -} // ChannelsAllListKey 频道全量列表缓存键(含未启用,供 MapByIDs 解析历史帖子 channel) func ChannelsAllListKey() string { @@ -156,20 +135,12 @@ func InvalidateConversationList(cache Cache, userID string) { cache.DeleteByPrefix(PrefixConversationList + ":" + userID + ":") } -// InvalidateConversationDetail 失效会话详情缓存 -func InvalidateConversationDetail(cache Cache, conversationID, userID string) { - cache.Delete(ConversationDetailKey(conversationID, userID)) -} // InvalidateGroupMembers 失效群组成员缓存 func InvalidateGroupMembers(cache Cache, groupID string) { cache.DeleteByPrefix(PrefixGroupMembers + ":" + groupID) } -// InvalidateGroupInfo 失效群组信息缓存 -func InvalidateGroupInfo(cache Cache, groupID string) { - cache.Delete(GroupInfoKey(groupID)) -} // InvalidateUnreadSystem 失效系统消息未读数缓存 func InvalidateUnreadSystem(cache Cache, userID string) { @@ -186,11 +157,6 @@ func InvalidateUnreadDetail(cache Cache, userID, conversationID string) { cache.Delete(UnreadDetailKey(userID, conversationID)) } -// InvalidateUserInfo 失效用户信息缓存 -func InvalidateUserInfo(cache Cache, userID string) { - cache.Delete(UserInfoKey(userID)) - cache.Delete(UserMeKey(userID)) -} // ============================================================ // 消息缓存 Key 生成函数 @@ -231,17 +197,9 @@ func MessageIdempotentKey(senderID, clientMsgID string) string { return fmt.Sprintf("%s:%s:%s", keyPrefixMsgIdempotent, senderID, clientMsgID) } -// PushDedupKey 推送去重键,防止同一消息重复推送给同一用户 -func PushDedupKey(userID, messageID string) string { - return fmt.Sprintf("push_dedup:%s:%s", userID, messageID) -} // UserReadSeqKey 用户已读位置缓存键(OpenIM 风格 SEQ_USER_READ:{convID}:{userID}) func UserReadSeqKey(convID, userID string) string { return fmt.Sprintf("%s:%s:%s", PrefixUserReadSeq, convID, userID) } -// ConvMaxSeqKey 会话最大 seq 缓存键 -func ConvMaxSeqKey(convID string) string { - return fmt.Sprintf("%s:%s", keyPrefixMsgSeq, convID) -} diff --git a/internal/cache/layered_cache.go b/internal/cache/layered_cache.go index 6f776a0..3bc160f 100644 --- a/internal/cache/layered_cache.go +++ b/internal/cache/layered_cache.go @@ -2,13 +2,11 @@ import ( "context" - "encoding/json" "strconv" "sync" "time" redislib "github.com/redis/go-redis/v9" - "go.uber.org/zap" redisPkg "with_you/internal/pkg/redis" ) @@ -412,59 +410,6 @@ func (c *LayeredCache) GetRedisClient() *redisPkg.Client { return c.redis.GetRedisClient() } -func GetTypedLayered[T any](c *LayeredCache, key string) (T, bool) { - var zero T - if !c.enabled { - return zero, false - } - - raw, ok := c.Get(key) - if !ok { - return zero, false - } - - if typed, ok := raw.(T); ok { - return typed, true - } - - if str, ok := raw.(string); ok { - var out T - if err := json.Unmarshal([]byte(str), &out); err != nil { - return zero, false - } - return out, true - } - - if data, err := json.Marshal(raw); err == nil { - var out T - if err := json.Unmarshal(data, &out); err != nil { - return zero, false - } - return out, true - } - - return zero, false -} - -func GetOrLoadLayered[T any]( - c *LayeredCache, - key string, - ttl time.Duration, - loader func() (T, error), -) (T, error) { - if cached, ok := GetTypedLayered[T](c, key); ok { - return cached, nil - } - - loaded, err := loader() - if err != nil { - var zero T - return zero, err - } - - c.Set(key, loaded, ttl) - return loaded, nil -} func (c *LayeredCache) InvalidateUserCache(userID uint) { c.DeleteByPrefix(cacheKeyPrefixUser + ":") @@ -509,15 +454,3 @@ func buildGroupMembersKey(groupID uint) string { } var _ Cache = (*LayeredCache)(nil) - -func (c *LayeredCache) logStats() { - stats := c.GetStats() - zap.L().Debug("LayeredCache stats", - zap.Int64("local_hits", stats.LocalHits), - zap.Int64("redis_hits", stats.RedisHits), - zap.Int64("misses", stats.Misses), - zap.Int64("sets", stats.Sets), - zap.Int64("invalidates", stats.Invalidates), - zap.Float64("hit_rate", c.GetHitRate()), - ) -} diff --git a/internal/cache/metrics.go b/internal/cache/metrics.go index 53cdf74..93278c0 100644 --- a/internal/cache/metrics.go +++ b/internal/cache/metrics.go @@ -14,25 +14,6 @@ type cacheMetrics struct { // metrics 全局缓存指标实例 var metrics cacheMetrics -// MetricsSnapshot 指标快照 -type MetricsSnapshot struct { - Hit int64 - Miss int64 - DecodeError int64 - SetError int64 - Invalidate int64 -} - -// GetMetricsSnapshot 获取当前指标快照 -func GetMetricsSnapshot() MetricsSnapshot { - return MetricsSnapshot{ - Hit: metrics.hit.Load(), - Miss: metrics.miss.Load(), - DecodeError: metrics.decodeError.Load(), - SetError: metrics.setError.Load(), - Invalidate: metrics.invalidate.Load(), - } -} // recordHit 记录缓存命中 func recordHit() { diff --git a/internal/dto/call_dto.go b/internal/dto/call_dto.go index 365f8b7..bcb77ee 100644 --- a/internal/dto/call_dto.go +++ b/internal/dto/call_dto.go @@ -143,49 +143,3 @@ type CallConfigResponse struct { ICEServers []ICEServerConfig `json:"ice_servers"` } -// ==================== Converter Functions ==================== - -// ConvertCallSessionToResponse 转换通话会话为响应格式 -func ConvertCallSessionToResponse(call *model.CallSession, caller *model.User, participants []*model.User) *CallSessionResponse { - resp := &CallSessionResponse{ - ID: call.ID, - ConversationID: call.ConversationID, - CallerID: call.CallerID, - CallType: call.CallType, - Status: call.Status, - StartedAt: call.StartedAt, - EndedAt: call.EndedAt, - Duration: call.Duration, - CreatedAt: call.CreatedAt, - } - - if call.GroupID != nil { - resp.GroupID = *call.GroupID - } - - if caller != nil { - resp.Caller = ConvertUserToResponse(caller) - } - - // 转换参与者 - resp.Participants = make([]CallParticipantResponse, 0, len(call.Participants)) - userMap := make(map[string]*model.User) - for _, u := range participants { - userMap[u.ID] = u - } - - for _, p := range call.Participants { - pr := CallParticipantResponse{ - UserID: p.UserID, - Status: p.Status, - JoinedAt: p.JoinedAt, - LeftAt: p.LeftAt, - } - if u, ok := userMap[p.UserID]; ok { - pr.User = ConvertUserToResponse(u) - } - resp.Participants = append(resp.Participants, pr) - } - - return resp -} diff --git a/internal/dto/group_converter.go b/internal/dto/group_converter.go index fec09da..6f6e38b 100644 --- a/internal/dto/group_converter.go +++ b/internal/dto/group_converter.go @@ -50,26 +50,6 @@ func GroupMemberToResponse(member *model.GroupMember) *GroupMemberResponse { } } -// GroupMemberToResponseWithUser 将GroupMember转换为GroupMemberResponse(包含用户信息) -func GroupMemberToResponseWithUser(member *model.GroupMember, user *model.User) *GroupMemberResponse { - if member == nil { - return nil - } - resp := GroupMemberToResponse(member) - if user != nil { - resp.User = ConvertUserToResponse(user) - } - return resp -} - -// GroupMembersToResponse 将GroupMember列表转换为GroupMemberResponse列表 -func GroupMembersToResponse(members []model.GroupMember) []*GroupMemberResponse { - result := make([]*GroupMemberResponse, 0, len(members)) - for i := range members { - result = append(result, GroupMemberToResponse(&members[i])) - } - return result -} // GroupAnnouncementToResponse 将GroupAnnouncement转换为GroupAnnouncementResponse func GroupAnnouncementToResponse(announcement *model.GroupAnnouncement) *GroupAnnouncementResponse { diff --git a/internal/dto/message_converter.go b/internal/dto/message_converter.go index 77ca2b1..aa303ee 100644 --- a/internal/dto/message_converter.go +++ b/internal/dto/message_converter.go @@ -107,14 +107,6 @@ func ConvertMessagesToResponse(messages []*model.Message) []*MessageResponse { return result } -// ConvertConversationsToResponse 将Conversation列表转换为响应列表 -func ConvertConversationsToResponse(convs []*model.Conversation) []*ConversationResponse { - result := make([]*ConversationResponse, 0, len(convs)) - for _, conv := range convs { - result = append(result, ConvertConversationToResponse(conv, nil, 0, nil, false, false)) - } - return result -} // ==================== PushRecord 转换 ==================== diff --git a/internal/dto/notification_converter.go b/internal/dto/notification_converter.go index aee79f1..c5a9375 100644 --- a/internal/dto/notification_converter.go +++ b/internal/dto/notification_converter.go @@ -33,49 +33,6 @@ func ConvertNotificationsToResponse(notifications []*model.Notification) []*Noti return result } -// ==================== SystemMessage 转换 ==================== - -// SystemMessageToResponse 将Message转换为SystemMessageResponse -func SystemMessageToResponse(msg *model.Message) *SystemMessageResponse { - if msg == nil { - return nil - } - - // 从 segments 中提取文本内容 - content := ExtractTextContentFromModel(msg.Segments) - - resp := &SystemMessageResponse{ - ID: msg.ID, - SenderID: msg.SenderID, - ReceiverID: "", // 系统消息的接收者需要从上下文获取 - Content: content, - Category: string(msg.Category), - SystemType: string(msg.SystemType), - CreatedAt: msg.CreatedAt, - } - if msg.ExtraData != nil { - resp.ExtraData = map[string]any{ - "actor_id": msg.ExtraData.ActorID, - "actor_name": msg.ExtraData.ActorName, - "avatar_url": msg.ExtraData.AvatarURL, - "target_id": msg.ExtraData.TargetID, - "target_type": msg.ExtraData.TargetType, - "action_url": msg.ExtraData.ActionURL, - "action_time": msg.ExtraData.ActionTime, - } - } - return resp -} - -// SystemMessagesToResponse 将Message列表转换为SystemMessageResponse列表 -func SystemMessagesToResponse(messages []*model.Message) []*SystemMessageResponse { - result := make([]*SystemMessageResponse, 0, len(messages)) - for _, msg := range messages { - result = append(result, SystemMessageToResponse(msg)) - } - return result -} - // SystemNotificationToResponse 将SystemNotification转换为SystemMessageResponse func SystemNotificationToResponse(n *model.SystemNotification) *SystemMessageResponse { if n == nil { @@ -118,11 +75,3 @@ func SystemNotificationToResponse(n *model.SystemNotification) *SystemMessageRes return resp } -// SystemNotificationsToResponse 将SystemNotification列表转换为SystemMessageResponse列表 -func SystemNotificationsToResponse(notifications []*model.SystemNotification) []*SystemMessageResponse { - result := make([]*SystemMessageResponse, 0, len(notifications)) - for _, n := range notifications { - result = append(result, SystemNotificationToResponse(n)) - } - return result -} diff --git a/internal/dto/post_converter.go b/internal/dto/post_converter.go index 4294b27..24100b7 100644 --- a/internal/dto/post_converter.go +++ b/internal/dto/post_converter.go @@ -212,14 +212,6 @@ func ConvertCommentToResponse(comment *model.Comment, isLiked bool) *CommentResp } } -// ConvertCommentsToResponse 将Comment列表转换为响应列表 -func ConvertCommentsToResponse(comments []*model.Comment, isLiked bool) []*CommentResponse { - result := make([]*CommentResponse, 0, len(comments)) - for _, comment := range comments { - result = append(result, ConvertCommentToResponse(comment, isLiked)) - } - return result -} // IsLikedChecker 点赞状态检查器接口 type IsLikedChecker interface { diff --git a/internal/dto/report.go b/internal/dto/report.go index aac5f82..9859fdf 100644 --- a/internal/dto/report.go +++ b/internal/dto/report.go @@ -108,11 +108,6 @@ type AdminBatchHandleResponse struct { // ==================== 转换函数 ==================== -func baseAdminReportFields(report *model.Report) (string, string) { - reason := string(report.Reason) - status := string(report.Status) - return reason, status -} // ConvertReportToResponse 将 Report 模型转换为响应 func ConvertReportToResponse(report *model.Report) *ReportResponse { diff --git a/internal/dto/segment.go b/internal/dto/segment.go index 695f449..d6d19c4 100644 --- a/internal/dto/segment.go +++ b/internal/dto/segment.go @@ -8,15 +8,6 @@ import ( "with_you/internal/model" ) -// ParseSegmentData 解析Segment数据到目标结构体 -func ParseSegmentData(segment model.MessageSegment, target any) error { - dataBytes, err := json.Marshal(segment.Data) - if err != nil { - return err - } - return json.Unmarshal(dataBytes, target) -} - // NewTextSegment 创建文本Segment func NewTextSegment(content string) model.MessageSegment { return model.MessageSegment{ @@ -25,137 +16,6 @@ func NewTextSegment(content string) model.MessageSegment { } } -// NewImageSegment 创建图片Segment -func NewImageSegment(url string, width, height int, thumbnailURL string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeImage), - Data: map[string]any{ - "url": url, - "width": width, - "height": height, - "thumbnail_url": thumbnailURL, - }, - } -} - -// NewImageSegmentWithSize 创建带文件大小的图片Segment -func NewImageSegmentWithSize(url string, width, height int, thumbnailURL string, fileSize int64) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeImage), - Data: map[string]any{ - "url": url, - "width": width, - "height": height, - "thumbnail_url": thumbnailURL, - "file_size": fileSize, - }, - } -} - -// NewVoiceSegment 创建语音Segment -func NewVoiceSegment(url string, duration int, fileSize int64) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeVoice), - Data: map[string]any{ - "url": url, - "duration": duration, - "file_size": fileSize, - }, - } -} - -// NewVideoSegment 创建视频Segment -func NewVideoSegment(url string, width, height, duration int, thumbnailURL string, fileSize int64) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeVideo), - Data: map[string]any{ - "url": url, - "width": width, - "height": height, - "duration": duration, - "thumbnail_url": thumbnailURL, - "file_size": fileSize, - }, - } -} - -// NewFileSegment 创建文件Segment -func NewFileSegment(url, name string, size int64, mimeType string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeFile), - Data: map[string]any{ - "url": url, - "name": name, - "size": size, - "mime_type": mimeType, - }, - } -} - -// NewAtSegment 创建@Segment,只存储 user_id,昵称由前端根据群成员列表实时解析 -func NewAtSegment(userID string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeAt), - Data: map[string]any{ - "user_id": userID, - }, - } -} - -// NewAtAllSegment 创建@所有人Segment -func NewAtAllSegment() model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeAt), - Data: map[string]any{ - "user_id": "all", - }, - } -} - -// NewReplySegment 创建回复Segment -func NewReplySegment(messageID string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeReply), - Data: map[string]any{"id": messageID}, - } -} - -// NewFaceSegment 创建表情Segment -func NewFaceSegment(id int, name, url string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeFace), - Data: map[string]any{ - "id": id, - "name": name, - "url": url, - }, - } -} - -// NewLinkSegment 创建链接Segment -func NewLinkSegment(url, title, description, image string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypeLink), - Data: map[string]any{ - "url": url, - "title": title, - "description": description, - "image": image, - }, - } -} - -// NewPostRefSegment 创建帖子内链Segment -func NewPostRefSegment(postID, title string) model.MessageSegment { - return model.MessageSegment{ - Type: string(SegmentTypePostRef), - Data: map[string]any{ - "post_id": postID, - "title": title, - }, - } -} - // ExtractVoteSegmentData 从消息链中提取投票Segment数据 func ExtractVoteSegmentData(segments model.MessageSegments) *VoteSegmentData { for _, segment := range segments { @@ -179,22 +39,6 @@ func HasVoteSegment(segments model.MessageSegments) bool { return HasSegmentType(segments, SegmentTypeVote) } -// ExtractTextContentFromJSON 从JSON格式的segments中提取纯文本内容 -// 用于从数据库读取的 []byte 格式的 segments -// 已废弃:现在数据库直接存储 model.MessageSegments 类型 -func ExtractTextContentFromJSON(segmentsJSON []byte) string { - if len(segmentsJSON) == 0 { - return "" - } - - var segments model.MessageSegments - if err := json.Unmarshal(segmentsJSON, &segments); err != nil { - return "" - } - - return ExtractTextContentFromModel(segments) -} - // ExtractTextContentFromModel 从 model.MessageSegments 中提取纯文本内容 func ExtractTextContentFromModel(segments model.MessageSegments) string { var result string @@ -290,72 +134,6 @@ func ExtractReferencedPosts(segments model.MessageSegments) []string { return postIDs } -// HasPostRefSegment 检查消息链中是否包含帖子引用 -func HasPostRefSegment(segments model.MessageSegments) bool { - return HasSegmentType(segments, SegmentTypePostRef) -} - -// IsAtAll 检查消息是否@了所有人 -func IsAtAll(segments model.MessageSegments) bool { - return slices.ContainsFunc(segments, func(segment model.MessageSegment) bool { - if segment.Type == string(SegmentTypeAt) { - if userID, ok := segment.Data["user_id"].(string); ok && userID == "all" { - return true - } - } - return false - }) -} - -// GetReplyMessageID 从消息链中获取被回复的消息ID -// 如果没有回复segment,返回空字符串 -func GetReplyMessageID(segments model.MessageSegments) string { - idx := slices.IndexFunc(segments, func(segment model.MessageSegment) bool { - return segment.Type == string(SegmentTypeReply) - }) - if idx == -1 { - return "" - } - if id, ok := segments[idx].Data["id"].(string); ok { - return id - } - return "" -} - -// BuildSegmentsFromContent 从旧版content构建segments -// 用于兼容旧版本消息 -func BuildSegmentsFromContent(contentType, content string, mediaURL *string) model.MessageSegments { - var segments model.MessageSegments - - switch contentType { - case "text": - segments = append(segments, NewTextSegment(content)) - case "image": - if mediaURL != nil { - segments = append(segments, NewImageSegment(*mediaURL, 0, 0, "")) - } - case "voice": - if mediaURL != nil { - segments = append(segments, NewVoiceSegment(*mediaURL, 0, 0)) - } - case "video": - if mediaURL != nil { - segments = append(segments, NewVideoSegment(*mediaURL, 0, 0, 0, "", 0)) - } - case "file": - if mediaURL != nil { - segments = append(segments, NewFileSegment(*mediaURL, content, 0, "")) - } - default: - // 默认当作文本处理 - if content != "" { - segments = append(segments, NewTextSegment(content)) - } - } - - return segments -} - // HasSegmentType 检查消息链中是否包含指定类型的segment func HasSegmentType(segments model.MessageSegments, segmentType SegmentType) bool { return slices.ContainsFunc(segments, func(segment model.MessageSegment) bool { @@ -363,71 +141,3 @@ func HasSegmentType(segments model.MessageSegments, segmentType SegmentType) boo }) } -// GetSegmentsByType 获取消息链中所有指定类型的segment -func GetSegmentsByType(segments model.MessageSegments, segmentType SegmentType) []model.MessageSegment { - var result []model.MessageSegment - for _, segment := range segments { - if segment.Type == string(segmentType) { - result = append(result, segment) - } - } - return result -} - -// GetFirstImageURL 获取消息链中第一张图片的URL -// 如果没有图片,返回空字符串 -func GetFirstImageURL(segments model.MessageSegments) string { - idx := slices.IndexFunc(segments, func(segment model.MessageSegment) bool { - return segment.Type == string(SegmentTypeImage) - }) - if idx == -1 { - return "" - } - if url, ok := segments[idx].Data["url"].(string); ok { - return url - } - return "" -} - -// GetFirstMediaURL 获取消息链中第一个媒体文件的URL(图片/视频/语音/文件) -// 用于兼容旧版本API -func GetFirstMediaURL(segments model.MessageSegments) string { - idx := slices.IndexFunc(segments, func(segment model.MessageSegment) bool { - switch segment.Type { - case string(SegmentTypeImage), string(SegmentTypeVideo), string(SegmentTypeVoice), string(SegmentTypeFile): - return true - } - return false - }) - if idx == -1 { - return "" - } - if url, ok := segments[idx].Data["url"].(string); ok { - return url - } - return "" -} - -// DetermineContentType 从消息链推断消息类型(用于兼容旧版本) -func DetermineContentType(segments model.MessageSegments) string { - if len(segments) == 0 { - return "text" - } - - // 优先检查媒体类型 - for _, segment := range segments { - switch segment.Type { - case string(SegmentTypeImage): - return "image" - case string(SegmentTypeVideo): - return "video" - case string(SegmentTypeVoice): - return "voice" - case string(SegmentTypeFile): - return "file" - } - } - - // 默认返回text - return "text" -} diff --git a/internal/dto/trade_converter.go b/internal/dto/trade_converter.go index 6832dd7..d5946dd 100644 --- a/internal/dto/trade_converter.go +++ b/internal/dto/trade_converter.go @@ -66,6 +66,3 @@ func ConvertTradeItemsToResponse(items []*model.TradeItem, favoriteMap map[strin return result } -func ConvertTradeItemToDetailResponse(item *model.TradeItem, isFavorited bool) *TradeItemResponse { - return ConvertTradeItemToResponse(item, isFavorited) -} \ No newline at end of file diff --git a/internal/errors/app_errors.go b/internal/errors/app_errors.go index 626e37e..5390ee5 100644 --- a/internal/errors/app_errors.go +++ b/internal/errors/app_errors.go @@ -151,16 +151,6 @@ func Wrap(err error, appErr *AppError) *AppError { } } -// New 创建新错误 -func New(code, message string) *AppError { - return &AppError{Code: code, Message: message} -} - -// Is 判断是否为特定错误 -func Is(err, target error) bool { - return errors.Is(err, target) -} - // As 将错误转换为 AppError func As(err error) (*AppError, bool) { var appErr *AppError diff --git a/internal/grpc/runner/server.go b/internal/grpc/runner/server.go index 259bbb9..7b6c498 100644 --- a/internal/grpc/runner/server.go +++ b/internal/grpc/runner/server.go @@ -1,7 +1,6 @@ package runner import ( - "context" "fmt" "net" "time" @@ -12,7 +11,6 @@ import ( "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" ) // Server gRPC 服务器 @@ -23,19 +21,6 @@ type Server struct { grpcSrv *grpc.Server } -// NewServer 创建新的 gRPC 服务器(内部构造 Hub + TaskManager) -func NewServer(cfg *config.GRPCConfig, logger *zap.Logger) *Server { - taskManager := NewTaskManager(nil, 60*time.Second, logger) - hub := NewRunnerHub(taskManager, logger) - taskManager.SetDispatcher(hub) - - return &Server{ - cfg: cfg, - logger: logger, - hub: hub, - } -} - // NewServerWithDeps 使用外部依赖创建 gRPC 服务器(Wire DI 使用) func NewServerWithDeps(cfg *config.GRPCConfig, logger *zap.Logger, hub *RunnerHub) *Server { return &Server{ @@ -111,43 +96,3 @@ func (s *Server) Stop() { } } -// GetHub 获取 RunnerHub -func (s *Server) GetHub() *RunnerHub { - return s.hub -} - -// GetTaskManager 获取 TaskManager -func (s *Server) GetTaskManager() *TaskManager { - return s.hub.taskManager -} - -// Shutdown 优雅关闭 -func (s *Server) Shutdown(ctx context.Context) error { - if s.grpcSrv == nil { - return nil - } - - done := make(chan struct{}) - go func() { - s.grpcSrv.GracefulStop() - close(done) - }() - - select { - case <-done: - s.logger.Info("gRPC server stopped gracefully") - return nil - case <-ctx.Done(): - s.logger.Warn("gRPC server shutdown timeout, forcing stop") - s.grpcSrv.Stop() - return ctx.Err() - } -} - -// GetClientCredentials 获取客户端凭证配置 -func GetClientCredentials(cfg *config.GRPCConfig) (credentials.TransportCredentials, error) { - if cfg.TLSEnabled { - return credentials.NewClientTLSFromFile(cfg.TLSCertFile, "") - } - return insecure.NewCredentials(), nil -} diff --git a/internal/handler/admin_report_handler.go b/internal/handler/admin_report_handler.go index 94ad024..b418b30 100644 --- a/internal/handler/admin_report_handler.go +++ b/internal/handler/admin_report_handler.go @@ -1,7 +1,6 @@ package handler import ( - "strconv" "with_you/internal/dto" "with_you/internal/pkg/response" "with_you/internal/service" @@ -138,15 +137,3 @@ func (h *AdminReportHandler) BatchHandle(c *gin.Context) { response.Success(c, result) } -// parsePageParams 解析分页参数 -func parsePageParams(c *gin.Context) (int, int) { - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) - if page < 1 { - page = 1 - } - if pageSize < 1 || pageSize > 100 { - pageSize = 20 - } - return page, pageSize -} diff --git a/internal/middleware/casbin.go b/internal/middleware/casbin.go index 91e3ab0..8aaa8e3 100644 --- a/internal/middleware/casbin.go +++ b/internal/middleware/casbin.go @@ -66,23 +66,6 @@ func CasbinAuth(casbinService service.CasbinService) gin.HandlerFunc { } } -// OptionalCasbinAuth 可选的 Casbin 权限检查 -// 不阻止请求,但会设置权限标志 -func OptionalCasbinAuth(casbinService service.CasbinService) gin.HandlerFunc { - return func(c *gin.Context) { - userID, exists := c.Get("user_id") - if exists { - path := c.Request.URL.Path - method := c.Request.Method - allowed, err := casbinService.EnforceForUser(c.Request.Context(), userID.(string), path, method) - if err == nil { - c.Set("permission_granted", allowed) - } - } - c.Next() - } -} - // RequireRole 要求特定角色的中间件 func RequireRole(casbinService service.CasbinService, requiredRoles ...string) gin.HandlerFunc { return func(c *gin.Context) { diff --git a/internal/middleware/logger.go b/internal/middleware/logger.go deleted file mode 100644 index a256b3f..0000000 --- a/internal/middleware/logger.go +++ /dev/null @@ -1,49 +0,0 @@ -package middleware - -import ( - "time" - - "github.com/gin-gonic/gin" - "go.uber.org/zap" -) - -// Logger 日志中间件 -func Logger(logger *zap.Logger) gin.HandlerFunc { - return func(c *gin.Context) { - start := time.Now() - path := c.Request.URL.Path - - c.Next() - - latency := time.Since(start) - statusCode := c.Writer.Status() - - logger.Info("request", - zap.String("method", c.Request.Method), - zap.String("path", path), - zap.Int("status", statusCode), - zap.Duration("latency", latency), - zap.String("ip", c.ClientIP()), - zap.String("user-agent", c.Request.UserAgent()), - ) - } -} - -// Recovery 恢复中间件 -func Recovery(logger *zap.Logger) gin.HandlerFunc { - return func(c *gin.Context) { - defer func() { - if err := recover(); err != nil { - logger.Error("panic recovered", - zap.Any("error", err), - ) - c.JSON(500, gin.H{ - "code": 500, - "message": "internal server error", - }) - c.Abort() - } - }() - c.Next() - } -} diff --git a/internal/middleware/ratelimit.go b/internal/middleware/ratelimit.go index bb1c152..920d291 100644 --- a/internal/middleware/ratelimit.go +++ b/internal/middleware/ratelimit.go @@ -75,27 +75,6 @@ func (rl *RateLimiter) isAllowed(key string) bool { return true } -func (rl *RateLimiter) count(key string) int { - rl.mu.Lock() - defer rl.mu.Unlock() - - now := time.Now() - times := rl.requests[key] - - var valid []time.Time - for _, t := range times { - if now.Sub(t) < rl.window { - valid = append(valid, t) - } - } - rl.requests[key] = valid - return len(valid) -} - -func RateLimit(requestsPerMinute int) gin.HandlerFunc { - return RateLimitWithDuration(requestsPerMinute, time.Minute) -} - func RateLimitWithDuration(limit int, window time.Duration) gin.HandlerFunc { limiter := NewRateLimiter(limit, window) @@ -115,32 +94,6 @@ func RateLimitWithDuration(limit int, window time.Duration) gin.HandlerFunc { } } -func RateLimitWithKey(requestsPerMinute int, keyFunc func(c *gin.Context) string) gin.HandlerFunc { - return RateLimitWithDurationAndKey(requestsPerMinute, time.Minute, keyFunc) -} - -func RateLimitWithDurationAndKey(limit int, window time.Duration, keyFunc func(c *gin.Context) string) gin.HandlerFunc { - limiter := NewRateLimiter(limit, window) - - return func(c *gin.Context) { - key := keyFunc(c) - if key == "" { - key = c.ClientIP() - } - - if !limiter.isAllowed(key) { - c.JSON(http.StatusTooManyRequests, gin.H{ - "code": 429, - "message": "too many requests, please try again later", - }) - c.Abort() - return - } - - c.Next() - } -} - // ============================================================ // IP 封禁与登录失败追踪 // ============================================================ diff --git a/internal/model/id_helper.go b/internal/model/id_helper.go index b54690d..58874c3 100644 --- a/internal/model/id_helper.go +++ b/internal/model/id_helper.go @@ -4,7 +4,6 @@ import ( "strconv" "github.com/google/uuid" - "gorm.io/gorm" "with_you/internal/pkg/utils" ) @@ -37,20 +36,3 @@ func SetSnowflakeInt64ID(id *int64) error { return nil } -func GenerateUUID() string { - return uuid.New().String() -} - -func GenerateSnowflakeString() (string, error) { - id, err := utils.GetSnowflake().GenerateID() - if err != nil { - return "", err - } - return strconv.FormatInt(id, 10), nil -} - -func GenerateSnowflakeInt64() (int64, error) { - return utils.GetSnowflake().GenerateID() -} - -func nop(_ *gorm.DB) error { return nil } \ No newline at end of file diff --git a/internal/model/init.go b/internal/model/init.go index 895161a..a0981db 100644 --- a/internal/model/init.go +++ b/internal/model/init.go @@ -415,9 +415,3 @@ func seedMaterialSubjects(db *gorm.DB) error { return nil } -// CloseDB 关闭数据库连接 -func CloseDB(db *gorm.DB) { - if sqlDB, err := db.DB(); err == nil { - sqlDB.Close() - } -} diff --git a/internal/model/message.go b/internal/model/message.go index ba69e3d..e299bf8 100644 --- a/internal/model/message.go +++ b/internal/model/message.go @@ -303,76 +303,6 @@ func (m *Message) IsInteractionNotification() bool { } } -// BatchDecryptMessages 批量并行解密消息 -// 用于批量查询后提高解密性能,比逐条调用 AfterFind 更高效 -// workerCount 指定并行工作数,如果 <= 0 则使用默认值 -func BatchDecryptMessages(messages []*Message, workerCount int) error { - if len(messages) == 0 { - return nil - } - - encryptor := crypto.GetMessageEncryptor() - if encryptor == nil { - // 加密器未初始化,直接解析 JSON(兼容模式) - for _, m := range messages { - if m.decrypted || m.SegmentsEncrypted == "" { - m.decrypted = true - continue - } - if err := json.Unmarshal([]byte(m.SegmentsEncrypted), &m.Segments); err != nil { - return err - } - m.decrypted = true - } - return nil - } - - // 小批量直接串行处理 - if len(messages) <= 10 { - for _, m := range messages { - if err := m.decryptSegments(); err != nil { - return err - } - } - return nil - } - - // 并行解密 - // 先收集需要解密的密文 - ciphertexts := make([]string, len(messages)) - for i, m := range messages { - ciphertexts[i] = m.SegmentsEncrypted - } - - // 批量并行解密 - plaintexts := encryptor.BatchDecrypt(ciphertexts, workerCount) - - // 解析结果 - for i, m := range messages { - if m.decrypted { - continue - } - if m.SegmentsEncrypted == "" { - m.decrypted = true - continue - } - - plaintext := plaintexts[i] - if plaintext == nil { - // 解密失败,尝试直接解析(兼容旧数据) - if err := json.Unmarshal([]byte(m.SegmentsEncrypted), &m.Segments); err != nil { - continue // 忽略单条错误 - } - } else { - if err := json.Unmarshal(plaintext, &m.Segments); err != nil { - continue // 忽略单条错误 - } - } - m.decrypted = true - } - - return nil -} // BatchDecryptMessagesParallel 使用 worker pool 并行解密 // 这是一个更高效的版本,适用于大量消息 diff --git a/internal/pkg/circuitbreaker/circuitbreaker.go b/internal/pkg/circuitbreaker/circuitbreaker.go index 5f9b726..a520f1e 100644 --- a/internal/pkg/circuitbreaker/circuitbreaker.go +++ b/internal/pkg/circuitbreaker/circuitbreaker.go @@ -22,15 +22,6 @@ type Config struct { Name string // 熔断器名称,用于日志 } -func DefaultConfig(name string) Config { - return Config{ - FailureThreshold: 5, - SuccessThreshold: 3, - Timeout: 30 * time.Second, - Name: name, - } -} - type Breaker struct { cfg Config mu sync.Mutex diff --git a/internal/pkg/crypto/batch_decrypt.go b/internal/pkg/crypto/batch_decrypt.go index 5779823..9488e21 100644 --- a/internal/pkg/crypto/batch_decrypt.go +++ b/internal/pkg/crypto/batch_decrypt.go @@ -1,7 +1,6 @@ package crypto import ( - "encoding/json" "sync" ) @@ -75,35 +74,3 @@ func (e *MessageEncryptor) BatchDecrypt(ciphertexts []string, workerCount int) [ return results } -// DecryptMessageSegments 解密消息段 JSON -// 这是一个便捷方法,直接返回解析后的 MessageSegments -func (e *MessageEncryptor) DecryptMessageSegments(ciphertext string) ([]byte, error) { - if e == nil || ciphertext == "" { - return nil, nil - } - return e.Decrypt(ciphertext) -} - -// TryParseMessageSegments 尝试解析消息段 -// 先尝试解密,如果失败则尝试直接解析(兼容未加密的旧数据) -func TryParseMessageSegments(ciphertext string, segments any) error { - if ciphertext == "" { - return nil - } - - encryptor := GetMessageEncryptor() - if encryptor == nil { - // 加密器未初始化,直接解析 - return json.Unmarshal([]byte(ciphertext), segments) - } - - // 尝试解密 - plaintext, err := encryptor.Decrypt(ciphertext) - if err != nil { - // 解密失败,可能是未加密的旧数据,尝试直接解析 - return json.Unmarshal([]byte(ciphertext), segments) - } - - // 解析解密后的数据 - return json.Unmarshal(plaintext, segments) -} diff --git a/internal/pkg/crypto/message_encryptor.go b/internal/pkg/crypto/message_encryptor.go index 285de3a..831fc0d 100644 --- a/internal/pkg/crypto/message_encryptor.go +++ b/internal/pkg/crypto/message_encryptor.go @@ -167,7 +167,3 @@ func (e *MessageEncryptor) RotateKey(newKey string, newVersion int) error { return nil } -// IsInitialized 检查加密器是否已初始化 -func IsInitialized() bool { - return globalEncryptor != nil -} diff --git a/internal/pkg/cursor/builder.go b/internal/pkg/cursor/builder.go index 7cdaf8c..191d820 100644 --- a/internal/pkg/cursor/builder.go +++ b/internal/pkg/cursor/builder.go @@ -64,19 +64,6 @@ func (b *CursorBuilder) WithPageSize(size int) *CursorBuilder { return b } -// WithDirection 设置分页方向 -func (b *CursorBuilder) WithDirection(direction Direction) *CursorBuilder { - if b.err != nil { - return b - } - - if direction == "" { - b.direction = Forward - } else { - b.direction = direction - } - return b -} // Error 返回构建过程中的错误 func (b *CursorBuilder) Error() error { @@ -166,79 +153,11 @@ func HasMore(itemsLen int, pageSize int) bool { return itemsLen > pageSize } -// TrimItems 裁剪结果到实际页面大小 -func TrimItems(itemsLen int, pageSize int) int { - if itemsLen > pageSize { - return pageSize - } - return itemsLen -} -// BuildResponse 从结果构建响应 -// items: 结果切片(需要是切片类型) -// getSortValue: 获取排序字段值的函数 -// getID: 获取ID的函数 -func BuildResponse[T any](items []T, order SortOrder, pageSize int, getSortValue func(T) string, getID func(T) string) *PageResponse { - hasMore := len(items) > pageSize - // 裁剪到实际页面大小 - if hasMore { - items = items[:pageSize] - } - - var nextCursor, prevCursor string - - // 生成下一页游标 - if hasMore && len(items) > 0 { - lastItem := items[len(items)-1] - nextCursor = NewCursor(getSortValue(lastItem), getID(lastItem), order).Encode() - } - - return &PageResponse{ - Items: items, - NextCursor: nextCursor, - PrevCursor: prevCursor, - HasMore: hasMore, - } -} - -// BuildResponseWithPrev 从结果构建响应(包含上一页游标) -func BuildResponseWithPrev[T any](items []T, order SortOrder, pageSize int, getSortValue func(T) string, getID func(T) string) *PageResponse { - hasMore := len(items) > pageSize - - // 裁剪到实际页面大小 - if hasMore { - items = items[:pageSize] - } - - var nextCursor, prevCursor string - - // 生成下一页游标 - if hasMore && len(items) > 0 { - lastItem := items[len(items)-1] - nextCursor = NewCursor(getSortValue(lastItem), getID(lastItem), order).Encode() - } - - // 生成上一页游标 - if len(items) > 0 { - firstItem := items[0] - prevCursor = NewCursor(getSortValue(firstItem), getID(firstItem), order).Encode() - } - - return &PageResponse{ - Items: items, - NextCursor: nextCursor, - PrevCursor: prevCursor, - HasMore: hasMore, - } -} // FormatTime 格式化时间为字符串(用于游标) func FormatTime(t time.Time) string { return t.Format(time.RFC3339Nano) } -// ParseTime 解析时间字符串(从游标) -func ParseTime(s string) (time.Time, error) { - return time.Parse(time.RFC3339Nano, s) -} diff --git a/internal/pkg/cursor/pagination.go b/internal/pkg/cursor/pagination.go index 887bb3e..68bc58e 100644 --- a/internal/pkg/cursor/pagination.go +++ b/internal/pkg/cursor/pagination.go @@ -99,15 +99,6 @@ type PageResponse struct { HasMore bool `json:"has_more"` } -// NewPageResponse 创建分页响应 -func NewPageResponse(items any, nextCursor, prevCursor string, hasMore bool) *PageResponse { - return &PageResponse{ - Items: items, - NextCursor: nextCursor, - PrevCursor: prevCursor, - HasMore: hasMore, - } -} // CursorPageResult 游标分页结果(泛型版本,供内部使用) type CursorPageResult[T any] struct { diff --git a/internal/pkg/hook/hook.go b/internal/pkg/hook/hook.go index 92a95b5..3c39213 100644 --- a/internal/pkg/hook/hook.go +++ b/internal/pkg/hook/hook.go @@ -187,11 +187,6 @@ func WithAsync() HookOption { } } -func WithTimeout(timeout time.Duration) HookOption { - return func(h *Hook) { - h.Timeout = timeout - } -} func (m *Manager) Trigger(ctx context.Context, hookType HookType, userID uint, data any) error { return m.TriggerWithMetadata(ctx, hookType, userID, data, nil) diff --git a/internal/pkg/hook/types.go b/internal/pkg/hook/types.go index c740acf..a5f52d5 100644 --- a/internal/pkg/hook/types.go +++ b/internal/pkg/hook/types.go @@ -1,8 +1,6 @@ package hook import ( - "context" - "go.uber.org/zap" ) @@ -196,144 +194,3 @@ func (b *BuiltinHooks) registerLoggingHooks() { func (b *BuiltinHooks) registerCacheInvalidationHooks() { } -type HookTriggerHelper struct { - manager *Manager -} - -func NewHookTriggerHelper(manager *Manager) *HookTriggerHelper { - return &HookTriggerHelper{manager: manager} -} - -func (h *HookTriggerHelper) TriggerPostCreated(ctx context.Context, userID uint, data *PostHookData) { - h.manager.Trigger(ctx, HookPostCreated, userID, data) -} - -func (h *HookTriggerHelper) TriggerPostUpdated(ctx context.Context, userID uint, data *PostHookData) { - h.manager.Trigger(ctx, HookPostUpdated, userID, data) -} - -func (h *HookTriggerHelper) TriggerPostDeleted(ctx context.Context, userID uint, postID uint) { - h.manager.Trigger(ctx, HookPostDeleted, userID, postID) -} - -func (h *HookTriggerHelper) TriggerCommentCreated(ctx context.Context, userID uint, data *CommentHookData) { - h.manager.Trigger(ctx, HookCommentCreated, userID, data) -} - -func (h *HookTriggerHelper) TriggerCommentDeleted(ctx context.Context, userID uint, commentID uint) { - h.manager.Trigger(ctx, HookCommentDeleted, userID, commentID) -} - -func (h *HookTriggerHelper) TriggerUserCreated(ctx context.Context, data *UserHookData) { - h.manager.Trigger(ctx, HookUserCreated, data.UserID, data) -} - -func (h *HookTriggerHelper) TriggerUserUpdated(ctx context.Context, userID uint, data *UserHookData) { - h.manager.Trigger(ctx, HookUserUpdated, userID, data) -} - -func (h *HookTriggerHelper) TriggerUserDeleted(ctx context.Context, userID uint) { - h.manager.Trigger(ctx, HookUserDeleted, userID, userID) -} - -func (h *HookTriggerHelper) TriggerUserLogin(ctx context.Context, userID uint, data *UserHookData) { - h.manager.Trigger(ctx, HookUserLogin, userID, data) -} - -func (h *HookTriggerHelper) TriggerUserLogout(ctx context.Context, userID uint) { - h.manager.Trigger(ctx, HookUserLogout, userID, userID) -} - -func (h *HookTriggerHelper) TriggerGroupCreated(ctx context.Context, userID uint, data *GroupHookData) { - h.manager.Trigger(ctx, HookGroupCreated, userID, data) -} - -func (h *HookTriggerHelper) TriggerGroupUpdated(ctx context.Context, userID uint, data *GroupHookData) { - h.manager.Trigger(ctx, HookGroupUpdated, userID, data) -} - -func (h *HookTriggerHelper) TriggerGroupDeleted(ctx context.Context, userID uint, groupID uint) { - h.manager.Trigger(ctx, HookGroupDeleted, userID, groupID) -} - -func (h *HookTriggerHelper) TriggerGroupJoined(ctx context.Context, userID uint, groupID uint) { - h.manager.Trigger(ctx, HookGroupJoined, userID, groupID) -} - -func (h *HookTriggerHelper) TriggerGroupLeft(ctx context.Context, userID uint, groupID uint) { - h.manager.Trigger(ctx, HookGroupLeft, userID, groupID) -} - -func (h *HookTriggerHelper) TriggerMessageSent(ctx context.Context, userID uint, data *MessageHookData) { - h.manager.Trigger(ctx, HookMessageSent, userID, data) -} - -func (h *HookTriggerHelper) TriggerMessageRead(ctx context.Context, userID uint, messageID uint) { - h.manager.Trigger(ctx, HookMessageRead, userID, messageID) -} - -func (h *HookTriggerHelper) TriggerNotificationSent(ctx context.Context, userID uint, data *NotificationHookData) { - h.manager.Trigger(ctx, HookNotificationSent, userID, data) -} - -func (h *HookTriggerHelper) TriggerVoteCreated(ctx context.Context, userID uint, data *VoteHookData) { - h.manager.Trigger(ctx, HookVoteCreated, userID, data) -} - -func (h *HookTriggerHelper) TriggerVoteUpdated(ctx context.Context, userID uint, data *VoteHookData) { - h.manager.Trigger(ctx, HookVoteUpdated, userID, data) -} - -func (h *HookTriggerHelper) TriggerFileUploaded(ctx context.Context, userID uint, data *FileHookData) { - h.manager.Trigger(ctx, HookFileUploaded, userID, data) -} - -func (h *HookTriggerHelper) TriggerPostPreModerate(ctx context.Context, userID uint, data *PostModerateHookData, result *ModerationResult) { - h.manager.TriggerWithMetadata(ctx, HookPostPreModerate, userID, data, map[string]any{ - "result": result, - }) -} - -func (h *HookTriggerHelper) TriggerPostModerated(ctx context.Context, userID uint, data *PostModeratedHookData) { - h.manager.Trigger(ctx, HookPostModerated, userID, data) -} - -func (h *HookTriggerHelper) TriggerCommentPreModerate(ctx context.Context, userID uint, data *CommentModerateHookData, result *ModerationResult) { - h.manager.TriggerWithMetadata(ctx, HookCommentPreModerate, userID, data, map[string]any{ - "result": result, - }) -} - -func (h *HookTriggerHelper) TriggerCommentModerated(ctx context.Context, userID uint, data *CommentModeratedHookData) { - h.manager.Trigger(ctx, HookCommentModerated, userID, data) -} - -func (h *HookTriggerHelper) TriggerUserAvatarPreModerate(ctx context.Context, userID uint, data *UserAvatarModerateHookData, result *ModerationResult) { - h.manager.TriggerWithMetadata(ctx, HookUserAvatarPreModerate, userID, data, map[string]any{ - "result": result, - }) -} - -func (h *HookTriggerHelper) TriggerUserAvatarModerated(ctx context.Context, userID uint, data *UserAvatarModeratedHookData) { - h.manager.Trigger(ctx, HookUserAvatarModerated, userID, data) -} - -func (h *HookTriggerHelper) TriggerUserCoverPreModerate(ctx context.Context, userID uint, data *UserCoverModerateHookData, result *ModerationResult) { - h.manager.TriggerWithMetadata(ctx, HookUserCoverPreModerate, userID, data, map[string]any{ - "result": result, - }) -} - -func (h *HookTriggerHelper) TriggerUserCoverModerated(ctx context.Context, userID uint, data *UserCoverModeratedHookData) { - h.manager.Trigger(ctx, HookUserCoverModerated, userID, data) -} - -func (h *HookTriggerHelper) TriggerUserBioPreModerate(ctx context.Context, userID uint, data *UserBioModerateHookData, result *ModerationResult) { - h.manager.TriggerWithMetadata(ctx, HookUserBioPreModerate, userID, data, map[string]any{ - "result": result, - }) -} - -func (h *HookTriggerHelper) TriggerUserBioModerated(ctx context.Context, userID uint, data *UserBioModeratedHookData) { - h.manager.Trigger(ctx, HookUserBioModerated, userID, data) -} diff --git a/internal/pkg/response/response.go b/internal/pkg/response/response.go index 9e00d65..9a9ad62 100644 --- a/internal/pkg/response/response.go +++ b/internal/pkg/response/response.go @@ -111,21 +111,6 @@ func Paginated(c *gin.Context, list any, total int64, page, pageSize int) { }) } -// HandleServiceError 统一处理 Service 错误 -// 如果 err 是 *apperrors.AppError,返回对应的业务错误码和消息 -// 如果 err 是其他错误,返回 false(调用方应处理通用错误) -// 返回 true 表示错误已处理,false 表示需要调用方继续处理 -func HandleServiceError(c *gin.Context, err error) bool { - if err == nil { - return false - } - if se, ok := apperrors.As(err); ok { - statusCode := statusCodeFromAppErrorCode(se.Code) - ErrorWithStringCode(c, statusCode, se.Code, se.Message) - return true - } - return false -} // HandleError 统一处理错误(带默认消息) // 如果 err 是 *apperrors.AppError,返回对应的业务错误码和消息 diff --git a/internal/pkg/tencent/config.go b/internal/pkg/tencent/config.go index 906d184..0c6e143 100644 --- a/internal/pkg/tencent/config.go +++ b/internal/pkg/tencent/config.go @@ -46,13 +46,3 @@ type tencentAPIResponse struct { } `json:"Response"` } -func ConfigFromAppConfig(cfg *Config) Config { - return Config{ - Enabled: cfg.Enabled, - SecretID: cfg.SecretID, - SecretKey: cfg.SecretKey, - Region: cfg.Region, - BizType: cfg.BizType, - Timeout: cfg.Timeout, - } -} diff --git a/internal/pkg/utils/avatar.go b/internal/pkg/utils/avatar.go index b030513..9384317 100644 --- a/internal/pkg/utils/avatar.go +++ b/internal/pkg/utils/avatar.go @@ -15,6 +15,3 @@ func GetAvatarOrDefault(username, nickname, avatar string) string { return DefaultAvatarURL } -func GetAvatarOrDefaultFromInfo(info AvatarInfo) string { - return GetAvatarOrDefault(info.Username, info.Nickname, info.Avatar) -} diff --git a/internal/pkg/utils/snowflake.go b/internal/pkg/utils/snowflake.go index d01da71..8775313 100644 --- a/internal/pkg/utils/snowflake.go +++ b/internal/pkg/utils/snowflake.go @@ -61,12 +61,6 @@ var ( globalSnowflakeErr error ) -// InitSnowflake 初始化全局雪花算法实例 -// nodeID: 机器ID,范围0-1023,可以通过环境变量 NODE_ID 配置 -func InitSnowflake(nodeID int64) error { - globalSnowflake, globalSnowflakeErr = NewSnowflake(nodeID) - return globalSnowflakeErr -} // GetSnowflake 获取全局雪花算法实例 // 如果未初始化,会自动使用默认配置初始化 @@ -191,71 +185,12 @@ func (s *Snowflake) waitNextMillis(timestamp int64) int64 { return now } -// ParseID 解析雪花算法ID,提取其中的信息 -// id: 要解析的雪花算法ID -// 返回值:包含时间戳、机器ID、序列号的结构体 -func ParseID(id int64) *IDInfo { - // 提取序列号(低12位) - sequence := id & maxSequence - - // 提取机器ID(中间10位) - nodeID := (id >> nodeIDShift) & maxNodeID - - // 提取时间戳(高41位) - timestamp := (id >> timestampShift) + customEpoch - - return &IDInfo{ - Timestamp: timestamp, - NodeID: nodeID, - Sequence: sequence, - } -} // currentTimestamp 获取当前时间戳(毫秒) func currentTimestamp() int64 { return time.Now().UnixNano() / 1e6 } -// GetNodeID 获取当前机器ID -func (s *Snowflake) GetNodeID() int64 { - return s.nodeID -} -// GetCustomEpoch 获取自定义纪元时间 -func GetCustomEpoch() int64 { - return customEpoch -} -// IDToTime 将雪花算法ID转换为生成时间 -// 这是一个便捷方法,等价于 ParseID(id).Timestamp -func IDToTime(id int64) time.Time { - info := ParseID(id) - return time.Unix(0, info.Timestamp*1e6) // 毫秒转纳秒 -} -// ValidateID 验证ID是否为有效的雪花算法ID -// 检查时间戳是否在合理范围内 -func ValidateID(id int64) bool { - if id <= 0 { - return false - } - - info := ParseID(id) - - // 检查时间戳是否在合理范围内 - // 不能早于纪元时间,不能晚于当前时间太多(允许1分钟的时钟偏差) - now := currentTimestamp() - if info.Timestamp < customEpoch || info.Timestamp > now+60000 { - return false - } - - // 检查机器ID和序列号是否在有效范围内 - if info.NodeID < 0 || info.NodeID > maxNodeID { - return false - } - if info.Sequence < 0 || info.Sequence > maxSequence { - return false - } - - return true -} diff --git a/internal/pkg/utils/validator.go b/internal/pkg/utils/validator.go index 78c5578..09fb8a2 100644 --- a/internal/pkg/utils/validator.go +++ b/internal/pkg/utils/validator.go @@ -2,7 +2,6 @@ package utils import ( "regexp" - "strings" ) // ValidateEmail 验证邮箱 @@ -43,39 +42,6 @@ func ValidatePassword(password string) bool { return hasUpper && hasLower && hasDigit } -// ValidatePasswordWithDetail 验证密码强度并返回详细信息 -func ValidatePasswordWithDetail(password string) (bool, string) { - if len(password) < 8 { - return false, "密码长度至少8位" - } - if len(password) > 50 { - return false, "密码长度不能超过50位" - } - - var hasUpper, hasLower, hasDigit bool - for _, c := range password { - switch { - case 'A' <= c && c <= 'Z': - hasUpper = true - case 'a' <= c && c <= 'z': - hasLower = true - case '0' <= c && c <= '9': - hasDigit = true - } - } - - if !hasUpper { - return false, "密码必须包含大写字母" - } - if !hasLower { - return false, "密码必须包含小写字母" - } - if !hasDigit { - return false, "密码必须包含数字" - } - - return true, "" -} // ValidatePhone 验证手机号 func ValidatePhone(phone string) bool { @@ -84,13 +50,3 @@ func ValidatePhone(phone string) bool { return matched } -// SanitizeHTML 清理HTML,防止XSS攻击 -func SanitizeHTML(input string) string { - input = strings.ReplaceAll(input, "&", "&") - input = strings.ReplaceAll(input, "<", "<") - input = strings.ReplaceAll(input, ">", ">") - input = strings.ReplaceAll(input, "\"", """) - input = strings.ReplaceAll(input, "'", "'") - input = strings.ReplaceAll(input, "/", "/") - return input -} diff --git a/internal/repository/comment_repo.go b/internal/repository/comment_repo.go index 35d976b..dc442a3 100644 --- a/internal/repository/comment_repo.go +++ b/internal/repository/comment_repo.go @@ -241,20 +241,6 @@ func (r *commentRepository) GetByPostIDWithReplies(postID string, page, pageSize return comments, total, nil } -// loadFlatReplies 加载评论的所有回复(扁平化,所有层级都在同一层) -func (r *commentRepository) loadFlatReplies(rootComment *model.Comment, limit int) { - var allReplies []*model.Comment - - // 查询所有以该评论为根评论的回复(不包括顶级评论本身) - r.db.Where("root_id = ? AND status = ?", rootComment.ID, model.CommentStatusPublished). - Preload("User"). - Order("created_at ASC"). - Limit(limit). - Find(&allReplies) - - rootComment.Replies = allReplies -} - // GetRepliesByRootID 根据根评论ID分页获取回复(扁平化) func (r *commentRepository) GetRepliesByRootID(rootID string, page, pageSize int) ([]*model.Comment, int64, error) { var replies []*model.Comment diff --git a/internal/service/account_cleanup_service.go b/internal/service/account_cleanup_service.go deleted file mode 100644 index 1b63b33..0000000 --- a/internal/service/account_cleanup_service.go +++ /dev/null @@ -1,114 +0,0 @@ -package service - -import ( - "context" - "time" - - "with_you/internal/model" - "with_you/internal/repository" - - "go.uber.org/zap" - "gorm.io/gorm" -) - -const AccountDeletionCoolDownDays = 90 - -type AccountCleanupService interface { - CleanupExpiredAccounts(ctx context.Context) (int64, error) -} - -type accountCleanupServiceImpl struct { - db *gorm.DB - userRepo repository.UserRepository - logService *LogService -} - -func NewAccountCleanupService( - db *gorm.DB, - userRepo repository.UserRepository, - logService *LogService, -) AccountCleanupService { - return &accountCleanupServiceImpl{ - db: db, - userRepo: userRepo, - logService: logService, - } -} - -func (s *accountCleanupServiceImpl) CleanupExpiredAccounts(ctx context.Context) (int64, error) { - cutoffTime := time.Now().AddDate(0, 0, -AccountDeletionCoolDownDays) - - users, err := s.userRepo.GetPendingDeletionUsers(cutoffTime) - if err != nil { - return 0, err - } - - if len(users) == 0 { - return 0, nil - } - - var deletedCount int64 - for _, user := range users { - if err := s.anonymizeAndDeleteUser(ctx, user); err != nil { - zap.L().Error("Failed to cleanup user", - zap.String("user_id", user.ID), - zap.Error(err), - ) - continue - } - deletedCount++ - } - - zap.L().Info("Cleaned up expired accounts", - zap.Int64("count", deletedCount), - ) - return deletedCount, nil -} - -func (s *accountCleanupServiceImpl) anonymizeAndDeleteUser(ctx context.Context, user *model.User) error { - deletedUserID := "deleted_user" - - return s.db.Transaction(func(tx *gorm.DB) error { - if err := tx.Model(&model.Post{}).Where("user_id = ?", user.ID).Update("user_id", deletedUserID).Error; err != nil { - return err - } - - if err := tx.Model(&model.Comment{}).Where("user_id = ?", user.ID).Update("user_id", deletedUserID).Error; err != nil { - return err - } - - if err := tx.Where("follower_id = ? OR following_id = ?", user.ID, user.ID).Delete(&model.Follow{}).Error; err != nil { - return err - } - - if err := tx.Where("user_id = ?", user.ID).Delete(&model.Favorite{}).Error; err != nil { - return err - } - - if err := tx.Where("user_id = ?", user.ID).Delete(&model.PostLike{}).Error; err != nil { - return err - } - - if err := tx.Where("user_id = ?", user.ID).Delete(&model.CommentLike{}).Error; err != nil { - return err - } - - if err := tx.Unscoped().Delete(&model.User{}, "id = ?", user.ID).Error; err != nil { - return err - } - - if s.logService != nil { - s.logService.OperationLog.RecordOperation(&model.OperationLog{ - UserID: user.ID, - UserName: user.Username, - Operation: "account_deleted", - Module: "user", - TargetType: "user", - TargetID: user.ID, - Status: "success", - }) - } - - return nil - }) -} diff --git a/internal/service/log_cleanup_service.go b/internal/service/log_cleanup_service.go index 994da69..77292d1 100644 --- a/internal/service/log_cleanup_service.go +++ b/internal/service/log_cleanup_service.go @@ -197,9 +197,3 @@ func NewLogService( } } -// GetLogCleanupServiceFromApp 从应用中获取日志清理服务 -func GetLogCleanupServiceFromApp(app any) LogCleanupService { - // 这里需要根据实际的app结构来获取 - // 暂时返回nil,实际使用时需要实现 - return nil -} diff --git a/internal/service/sensitive_service.go b/internal/service/sensitive_service.go index 450a686..d3c5ffb 100644 --- a/internal/service/sensitive_service.go +++ b/internal/service/sensitive_service.go @@ -4,11 +4,9 @@ import ( "context" "encoding/json" "fmt" - "regexp" "strings" "sync" "time" - "unicode/utf8" "with_you/internal/model" redisclient "with_you/internal/pkg/redis" @@ -501,51 +499,3 @@ func (s *sensitiveServiceImpl) loadFromRedis(ctx context.Context) error { return nil } -// ==================== 辅助函数 ==================== - -// ContainsSensitiveWord 快速检查文本是否包含敏感词 -func ContainsSensitiveWord(text string, tree *SensitiveWordTree) bool { - if tree == nil || text == "" { - return false - } - hasSensitive, _ := tree.Check(text) - return hasSensitive -} - -// FilterSensitiveWords 过滤敏感词并返回替换后的文本 -func FilterSensitiveWords(text string, tree *SensitiveWordTree, repl string) string { - if tree == nil || text == "" { - return text - } - if repl == "" { - repl = "***" - } - return tree.Replace(text, repl) -} - -// ValidateTextLength 验证文本长度是否合法 -func ValidateTextLength(text string, minLen, maxLen int) bool { - length := utf8.RuneCountInString(text) - return length >= minLen && length <= maxLen -} - -// SanitizeText 清理文本,移除多余空白字符 -func SanitizeText(text string) string { - // 替换多个连续空白字符为单个空格 - spaceReg := regexp.MustCompile(`\s+`) - text = spaceReg.ReplaceAllString(text, " ") - // 去除首尾空白 - return strings.TrimSpace(text) -} - -// ==================== 默认敏感词列表 ==================== - -// DefaultSensitiveWords 返回默认敏感词列表(示例) -func DefaultSensitiveWords() map[string]struct{} { - return map[string]struct{}{ - // 示例敏感词,实际需要从数据库或配置加载 - "测试敏感词1": {}, - "测试敏感词2": {}, - "测试敏感词3": {}, - } -}