From 67a56609520b0efac66ca431aa98c042f3c7fc91 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Tue, 28 Apr 2026 14:53:04 +0800 Subject: [PATCH] refactor: unify code formatting and improve push/search implementations - Remove BOM from all Go source files - Standardize import ordering and whitespace across handlers and services - Replace PostgreSQL full-text search with ILIKE pattern matching in post and user repositories - Enhance JPush client with TLS transport, rate limit monitoring, and simplified API - Refactor PushService to include userID in device management methods - Add MaxDevicesPerUser limit and extract helper functions for push payload construction --- internal/handler/admin_comment_handler.go | 2 +- internal/handler/admin_dashboard_handler.go | 2 +- internal/handler/admin_group_handler.go | 2 +- internal/handler/admin_log_handler.go | 2 +- internal/handler/admin_post_handler.go | 2 +- .../handler/admin_profile_audit_handler.go | 2 +- internal/handler/admin_report_handler.go | 6 +- internal/handler/admin_user_handler.go | 2 +- .../handler/admin_verification_handler.go | 2 +- internal/handler/call_handler.go | 2 +- internal/handler/channel_handler.go | 3 +- internal/handler/group_handler.go | 2 +- internal/handler/material_handler.go | 2 +- internal/handler/message_handler.go | 4 +- internal/handler/notification_handler.go | 2 +- internal/handler/push_handler.go | 8 +- internal/handler/qrcode_handler.go | 2 +- internal/handler/report_handler.go | 4 +- internal/handler/role_handler.go | 2 +- internal/handler/schedule_handler.go | 2 +- internal/handler/sticker_handler.go | 2 +- internal/handler/system_message_handler.go | 2 +- internal/handler/trade_handler.go | 2 +- internal/handler/upload_handler.go | 2 +- internal/handler/verification_handler.go | 2 +- internal/handler/vote_handler.go | 2 +- internal/pkg/jpush/client.go | 81 ++--- internal/repository/post_repo.go | 20 +- internal/repository/user_repo.go | 5 +- internal/service/account_cleanup_service.go | 2 +- internal/service/admin_comment_service.go | 2 +- internal/service/admin_dashboard_service.go | 2 +- internal/service/admin_group_service.go | 2 +- internal/service/admin_post_service.go | 2 +- internal/service/admin_report_service.go | 46 +-- internal/service/admin_user_service.go | 2 +- internal/service/async_log_manager.go | 2 +- internal/service/call_service.go | 2 +- internal/service/channel_service.go | 3 +- internal/service/chat_service.go | 4 +- internal/service/email_code_service.go | 2 +- internal/service/email_service.go | 2 +- internal/service/group_service.go | 2 +- internal/service/hot_rank_worker.go | 26 +- internal/service/jwt_service.go | 4 +- internal/service/log_cleanup_service.go | 2 +- internal/service/log_service.go | 2 +- internal/service/material_service.go | 4 +- internal/service/message_segment_image.go | 2 +- internal/service/message_service.go | 2 +- internal/service/notification_service.go | 2 +- internal/service/post_ai_service.go | 2 +- internal/service/push_service.go | 335 +++++++++--------- internal/service/qrcode_login_service.go | 2 +- internal/service/report_service.go | 44 +-- internal/service/role_service.go | 2 +- internal/service/schedule_service.go | 2 +- internal/service/schedule_sync_service.go | 2 +- internal/service/sensitive_service.go | 2 +- internal/service/setup_service.go | 10 +- internal/service/sticker_service.go | 2 +- internal/service/system_message_service.go | 2 +- internal/service/trade_service.go | 2 +- internal/service/upload_service.go | 2 +- internal/service/user_activity_service.go | 2 +- .../service/user_profile_audit_service.go | 2 +- internal/service/verification_service.go | 2 +- 67 files changed, 356 insertions(+), 351 deletions(-) diff --git a/internal/handler/admin_comment_handler.go b/internal/handler/admin_comment_handler.go index 6bfaace..3d406d4 100644 --- a/internal/handler/admin_comment_handler.go +++ b/internal/handler/admin_comment_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_dashboard_handler.go b/internal/handler/admin_dashboard_handler.go index 219ca15..6c018eb 100644 --- a/internal/handler/admin_dashboard_handler.go +++ b/internal/handler/admin_dashboard_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_group_handler.go b/internal/handler/admin_group_handler.go index 2678eed..f472029 100644 --- a/internal/handler/admin_group_handler.go +++ b/internal/handler/admin_group_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_log_handler.go b/internal/handler/admin_log_handler.go index 07e47fc..ba2e233 100644 --- a/internal/handler/admin_log_handler.go +++ b/internal/handler/admin_log_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_post_handler.go b/internal/handler/admin_post_handler.go index 1c137aa..be447ac 100644 --- a/internal/handler/admin_post_handler.go +++ b/internal/handler/admin_post_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_profile_audit_handler.go b/internal/handler/admin_profile_audit_handler.go index cfd41c3..2a4a6d0 100644 --- a/internal/handler/admin_profile_audit_handler.go +++ b/internal/handler/admin_profile_audit_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_report_handler.go b/internal/handler/admin_report_handler.go index c0925b6..94ad024 100644 --- a/internal/handler/admin_report_handler.go +++ b/internal/handler/admin_report_handler.go @@ -1,10 +1,10 @@ -package handler +package handler import ( + "strconv" "with_you/internal/dto" "with_you/internal/pkg/response" "with_you/internal/service" - "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -149,4 +149,4 @@ func parsePageParams(c *gin.Context) (int, int) { pageSize = 20 } return page, pageSize -} \ No newline at end of file +} diff --git a/internal/handler/admin_user_handler.go b/internal/handler/admin_user_handler.go index f6f08a5..3bcccd0 100644 --- a/internal/handler/admin_user_handler.go +++ b/internal/handler/admin_user_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/admin_verification_handler.go b/internal/handler/admin_verification_handler.go index a6ea4fe..8528b59 100644 --- a/internal/handler/admin_verification_handler.go +++ b/internal/handler/admin_verification_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/call_handler.go b/internal/handler/call_handler.go index 0836ffb..85e9b6e 100644 --- a/internal/handler/call_handler.go +++ b/internal/handler/call_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/channel_handler.go b/internal/handler/channel_handler.go index 9fb31bf..18e7e84 100644 --- a/internal/handler/channel_handler.go +++ b/internal/handler/channel_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "with_you/internal/model" @@ -120,4 +120,3 @@ func (h *ChannelHandler) AdminDelete(c *gin.Context) { } response.Success(c, gin.H{"success": true}) } - diff --git a/internal/handler/group_handler.go b/internal/handler/group_handler.go index 222738e..b78ada5 100644 --- a/internal/handler/group_handler.go +++ b/internal/handler/group_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "errors" diff --git a/internal/handler/material_handler.go b/internal/handler/material_handler.go index 370072e..052d3f2 100644 --- a/internal/handler/material_handler.go +++ b/internal/handler/material_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "errors" diff --git a/internal/handler/message_handler.go b/internal/handler/message_handler.go index 8a7c6ff..1afd050 100644 --- a/internal/handler/message_handler.go +++ b/internal/handler/message_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "context" @@ -940,7 +940,7 @@ func (h *MessageHandler) HandleSetConversationNotificationMuted(c *gin.Context) } response.SuccessWithMessage(c, "conversation notification_muted status updated", gin.H{ - "conversation_id": conversationID, + "conversation_id": conversationID, "notification_muted": req.NotificationMuted, }) } diff --git a/internal/handler/notification_handler.go b/internal/handler/notification_handler.go index 08409c2..e513436 100644 --- a/internal/handler/notification_handler.go +++ b/internal/handler/notification_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/push_handler.go b/internal/handler/push_handler.go index ba3ea7c..7e10011 100644 --- a/internal/handler/push_handler.go +++ b/internal/handler/push_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "with_you/internal/dto" @@ -67,7 +67,7 @@ func (h *PushHandler) UnregisterDevice(c *gin.Context) { return } - err := h.pushService.UnregisterDevice(c.Request.Context(), deviceID) + err := h.pushService.UnregisterDevice(c.Request.Context(), userID, deviceID) if err != nil { response.InternalServerError(c, "failed to unregister device") return @@ -141,14 +141,14 @@ func (h *PushHandler) UpdateDeviceToken(c *gin.Context) { } var req struct { - PushToken string `json:"push_token"` + PushToken string `json:"push_token"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } - err := h.pushService.UpdateDeviceToken(c.Request.Context(), deviceID, req.PushToken) + err := h.pushService.UpdateDeviceToken(c.Request.Context(), userID, deviceID, req.PushToken) if err != nil { response.InternalServerError(c, "failed to update device token") return diff --git a/internal/handler/qrcode_handler.go b/internal/handler/qrcode_handler.go index 34d76fa..4825f26 100644 --- a/internal/handler/qrcode_handler.go +++ b/internal/handler/qrcode_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "fmt" diff --git a/internal/handler/report_handler.go b/internal/handler/report_handler.go index 32696e0..4f21efb 100644 --- a/internal/handler/report_handler.go +++ b/internal/handler/report_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "with_you/internal/dto" @@ -58,4 +58,4 @@ func (h *ReportHandler) Create(c *gin.Context) { } response.Success(c, dto.ConvertReportToResponse(report)) -} \ No newline at end of file +} diff --git a/internal/handler/role_handler.go b/internal/handler/role_handler.go index 9705a40..dcedf09 100644 --- a/internal/handler/role_handler.go +++ b/internal/handler/role_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "errors" diff --git a/internal/handler/schedule_handler.go b/internal/handler/schedule_handler.go index 9d2a01d..f8f9231 100644 --- a/internal/handler/schedule_handler.go +++ b/internal/handler/schedule_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/sticker_handler.go b/internal/handler/sticker_handler.go index 5e14d79..e3e8f83 100644 --- a/internal/handler/sticker_handler.go +++ b/internal/handler/sticker_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "errors" diff --git a/internal/handler/system_message_handler.go b/internal/handler/system_message_handler.go index df59c83..a1ae958 100644 --- a/internal/handler/system_message_handler.go +++ b/internal/handler/system_message_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/trade_handler.go b/internal/handler/trade_handler.go index 00185ad..7376d14 100644 --- a/internal/handler/trade_handler.go +++ b/internal/handler/trade_handler.go @@ -217,4 +217,4 @@ func (h *TradeHandler) Unfavorite(c *gin.Context) { return } response.Success(c, dto.TradeFavoriteResponse{Favorited: false}) -} \ No newline at end of file +} diff --git a/internal/handler/upload_handler.go b/internal/handler/upload_handler.go index 356d90f..be95b87 100644 --- a/internal/handler/upload_handler.go +++ b/internal/handler/upload_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "github.com/gin-gonic/gin" diff --git a/internal/handler/verification_handler.go b/internal/handler/verification_handler.go index 85406a6..969a3a2 100644 --- a/internal/handler/verification_handler.go +++ b/internal/handler/verification_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "strconv" diff --git a/internal/handler/vote_handler.go b/internal/handler/vote_handler.go index 8545767..7a31485 100644 --- a/internal/handler/vote_handler.go +++ b/internal/handler/vote_handler.go @@ -1,4 +1,4 @@ -package handler +package handler import ( "errors" diff --git a/internal/pkg/jpush/client.go b/internal/pkg/jpush/client.go index e04b3f2..3c26cd3 100644 --- a/internal/pkg/jpush/client.go +++ b/internal/pkg/jpush/client.go @@ -2,10 +2,12 @@ package jpush import ( "bytes" + "crypto/tls" "encoding/base64" "encoding/json" "fmt" "io" + "net" "net/http" "net/url" "strconv" @@ -16,8 +18,8 @@ import ( const ( PushAPIBaseURL = "https://api.jpush.cn/v3" - DeviceAPIBaseURL = "https://device.jpush.cn/v3" - ReportAPIBaseURL = "https://report.jpush.cn/v3" + DeviceAPIBaseURL = "https://device.jpush.cn/v3" + ReportAPIBaseURL = "https://report.jpush.cn/v3" ) type Client struct { @@ -26,14 +28,25 @@ type Client struct { production bool httpClient *http.Client logger *zap.Logger + lastRateLimit *RateLimitInfo } func NewClient(appKey, masterSecret string, production bool, logger *zap.Logger) *Client { + transport := &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 20, + IdleConnTimeout: 90 * time.Second, + TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12}, + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + } return &Client{ appKey: appKey, masterSecret: masterSecret, production: production, - httpClient: &http.Client{Timeout: 30 * time.Second}, + httpClient: &http.Client{Timeout: 30 * time.Second, Transport: transport}, logger: logger, } } @@ -79,9 +92,9 @@ type IOSNotif struct { } type PushOptions struct { - TimeToLive int `json:"time_to_live,omitempty"` - ApnsProduction bool `json:"apns_production"` - ApnsCollapseID string `json:"apns_collapse_id,omitempty"` + TimeToLive int `json:"time_to_live,omitempty"` + ApnsProduction bool `json:"apns_production"` + ApnsCollapseID string `json:"apns_collapse_id,omitempty"` } type PushResponse struct { @@ -96,7 +109,7 @@ type RateLimitInfo struct { } type APIError struct { - ErrCode int `json:"code"` + ErrCode int `json:"code"` ErrMessage string `json:"message"` ErrDetail struct { Code int `json:"code"` @@ -199,6 +212,14 @@ func (c *Client) Push(payload map[string]any) (*PushResponse, *RateLimitInfo, er return nil, rateLimit, err } + c.lastRateLimit = rateLimit + if rateLimit != nil && rateLimit.Limit > 0 && rateLimit.Remaining < rateLimit.Limit/10 { + c.logger.Warn("jpush rate limit approaching", + zap.Int("remaining", rateLimit.Remaining), + zap.Int("limit", rateLimit.Limit), + ) + } + result, parseErr := c.parseResponse(resp, respBody) if parseErr != nil { c.logger.Error("jpush push response parse failed", @@ -214,7 +235,7 @@ func (c *Client) Push(payload map[string]any) (*PushResponse, *RateLimitInfo, er return result, rateLimit, parseErr } -func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *Notification, extras map[string]any) (*PushResponse, error) { +func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *Notification) (*PushResponse, error) { if len(registrationIDs) == 0 { return nil, fmt.Errorf("registration IDs cannot be empty") } @@ -228,8 +249,6 @@ func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *N zap.String("alert", notification.Alert), ) - mergeExtras(notification, extras) - payload := map[string]any{ "platform": "all", "audience": map[string]any{"registration_id": registrationIDs}, @@ -256,7 +275,7 @@ func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *N return result, nil } -func (c *Client) PushByAliases(aliases []string, notification *Notification, extras map[string]any) (*PushResponse, error) { +func (c *Client) PushByAliases(aliases []string, notification *Notification) (*PushResponse, error) { if len(aliases) == 0 { return nil, fmt.Errorf("aliases cannot be empty") } @@ -270,8 +289,6 @@ func (c *Client) PushByAliases(aliases []string, notification *Notification, ext zap.String("alert", notification.Alert), ) - mergeExtras(notification, extras) - payload := map[string]any{ "platform": "all", "audience": map[string]any{"alias": aliases}, @@ -295,7 +312,7 @@ func (c *Client) PushByAliases(aliases []string, notification *Notification, ext zap.Int("count", len(aliases)), zap.String("msg_id", result.MsgID), ) - return result, err + return result, nil } func (c *Client) PushAll(notification *Notification) (*PushResponse, error) { @@ -325,29 +342,7 @@ func (c *Client) PushAll(notification *Notification) (*PushResponse, error) { c.logger.Info("jpush push all success", zap.String("msg_id", result.MsgID), ) - return result, err -} - -func mergeExtras(notification *Notification, extras map[string]any) { - if extras == nil { - return - } - if notification.Android != nil { - if notification.Android.Extras == nil { - notification.Android.Extras = make(map[string]any) - } - for k, v := range extras { - notification.Android.Extras[k] = v - } - } - if notification.IOS != nil { - if notification.IOS.Extras == nil { - notification.IOS.Extras = make(map[string]any) - } - for k, v := range extras { - notification.IOS.Extras[k] = v - } - } + return result, nil } func BuildNotification(title, body string, notificationType string, extras map[string]any) *Notification { @@ -394,7 +389,7 @@ func (c *Client) GetDeviceInfo(registrationID string) (*DeviceInfo, error) { zap.String("registration_id", registrationID), ) - path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID) + path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID) resp, body, _, err := c.doRequest("GET", path, nil) if err != nil { return nil, err @@ -427,7 +422,7 @@ func (c *Client) SetDeviceAlias(registrationID string, alias string) error { zap.String("alias", alias), ) - path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID) + path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID) payload := map[string]any{ "alias": alias, } @@ -461,7 +456,7 @@ func (c *Client) SetDeviceTags(registrationID string, addTags, removeTags []stri zap.Strings("remove_tags", removeTags), ) - path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID) + path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID) tags := map[string]any{} if len(addTags) > 0 { tags["add"] = addTags @@ -499,7 +494,7 @@ func (c *Client) GetAliasRegistrationIDs(alias string) ([]string, error) { zap.String("alias", alias), ) - path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias) + path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias) resp, body, _, err := c.doRequest("GET", path, nil) if err != nil { return nil, err @@ -532,7 +527,7 @@ func (c *Client) DeleteAlias(alias string) error { zap.String("alias", alias), ) - path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias) + path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias) resp, body, _, err := c.doRequest("DELETE", path, nil) if err != nil { return err @@ -559,7 +554,7 @@ func (c *Client) RemoveAliasDevices(alias string, registrationIDs []string) erro zap.Int("count", len(registrationIDs)), ) - path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias) + path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias) payload := map[string]any{ "registration_ids": map[string]any{ "remove": registrationIDs, diff --git a/internal/repository/post_repo.go b/internal/repository/post_repo.go index 39b08d0..6241e57 100644 --- a/internal/repository/post_repo.go +++ b/internal/repository/post_repo.go @@ -510,11 +510,8 @@ func (r *postRepository) Search(keyword string, page, pageSize int) ([]*model.Po // 搜索标题和内容 if keyword != "" { if r.db.Dialector.Name() == "postgres" { - // PostgreSQL 使用全文检索表达式,为 pg_trgm/GIN 索引升级预留路径 - query = query.Where( - "to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)", - keyword, - ) + searchPattern := "%" + keyword + "%" + query = query.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern) } else { searchPattern := "%" + keyword + "%" query = query.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern) @@ -880,10 +877,8 @@ func (r *postRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int // 关键词搜索(标题或内容) if query.Keyword != "" { if r.db.Dialector.Name() == "postgres" { - db = db.Where( - "to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)", - query.Keyword, - ) + searchPattern := "%" + query.Keyword + "%" + db = db.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern) } else { searchPattern := "%" + query.Keyword + "%" db = db.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern) @@ -1130,11 +1125,8 @@ func (r *postRepository) SearchPostsByCursor(ctx context.Context, keyword string // 搜索标题和内容 if keyword != "" { if r.db.Dialector.Name() == "postgres" { - // PostgreSQL 使用全文检索表达式 - query = query.Where( - "to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)", - keyword, - ) + searchPattern := "%" + keyword + "%" + query = query.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern) } else { searchPattern := "%" + keyword + "%" query = query.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern) diff --git a/internal/repository/user_repo.go b/internal/repository/user_repo.go index cc19694..d6d4fdf 100644 --- a/internal/repository/user_repo.go +++ b/internal/repository/user_repo.go @@ -439,9 +439,10 @@ func (r *userRepository) Search(keyword string, page, pageSize int) ([]*model.Us // 搜索用户名、昵称、简介 if keyword != "" { if r.db.Dialector.Name() == "postgres" { + searchPattern := "%" + keyword + "%" query = query.Where( - "to_tsvector('simple', COALESCE(username, '') || ' ' || COALESCE(nickname, '') || ' ' || COALESCE(bio, '')) @@ plainto_tsquery('simple', ?)", - keyword, + "username ILIKE ? OR nickname ILIKE ? OR bio ILIKE ?", + searchPattern, searchPattern, searchPattern, ) } else { // SQLite: 使用 LOWER() 实现大小写不敏感的模糊搜索 diff --git a/internal/service/account_cleanup_service.go b/internal/service/account_cleanup_service.go index 5cbbf44..199e306 100644 --- a/internal/service/account_cleanup_service.go +++ b/internal/service/account_cleanup_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/admin_comment_service.go b/internal/service/admin_comment_service.go index 40d492b..c6c6320 100644 --- a/internal/service/admin_comment_service.go +++ b/internal/service/admin_comment_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/admin_dashboard_service.go b/internal/service/admin_dashboard_service.go index 5ca2e9d..94d6e3d 100644 --- a/internal/service/admin_dashboard_service.go +++ b/internal/service/admin_dashboard_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/admin_group_service.go b/internal/service/admin_group_service.go index c6a1053..d46e8dc 100644 --- a/internal/service/admin_group_service.go +++ b/internal/service/admin_group_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/admin_post_service.go b/internal/service/admin_post_service.go index 21056bc..54b7926 100644 --- a/internal/service/admin_post_service.go +++ b/internal/service/admin_post_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/admin_report_service.go b/internal/service/admin_report_service.go index d1a0daf..1f8cf90 100644 --- a/internal/service/admin_report_service.go +++ b/internal/service/admin_report_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" @@ -26,15 +26,15 @@ type AdminReportService interface { // adminReportServiceImpl 管理端举报服务实现 type adminReportServiceImpl struct { - reportRepo repository.ReportRepository - postRepo repository.PostRepository - commentRepo repository.CommentRepository - messageRepo repository.MessageRepository - userRepo repository.UserRepository - notifyRepo repository.SystemNotificationRepository - pushService PushService - txManager repository.TransactionManager - logService OperationLogService + reportRepo repository.ReportRepository + postRepo repository.PostRepository + commentRepo repository.CommentRepository + messageRepo repository.MessageRepository + userRepo repository.UserRepository + notifyRepo repository.SystemNotificationRepository + pushService PushService + txManager repository.TransactionManager + logService OperationLogService } // NewAdminReportService 创建管理端举报服务 @@ -50,15 +50,15 @@ func NewAdminReportService( logService OperationLogService, ) AdminReportService { return &adminReportServiceImpl{ - reportRepo: reportRepo, - postRepo: postRepo, - commentRepo: commentRepo, - messageRepo: messageRepo, - userRepo: userRepo, - notifyRepo: notifyRepo, - pushService: pushService, - txManager: txManager, - logService: logService, + reportRepo: reportRepo, + postRepo: postRepo, + commentRepo: commentRepo, + messageRepo: messageRepo, + userRepo: userRepo, + notifyRepo: notifyRepo, + pushService: pushService, + txManager: txManager, + logService: logService, } } @@ -405,9 +405,9 @@ func (s *adminReportServiceImpl) notifyReporter(ctx context.Context, report *mod // 创建通知 notification := &model.SystemNotification{ ReceiverID: report.ReporterID, - Type: notifyType, - Title: title, - Content: content, + Type: notifyType, + Title: title, + Content: content, ExtraData: &model.SystemNotificationExtra{ ActorIDStr: report.ReporterID, TargetID: report.TargetID, @@ -467,4 +467,4 @@ func convertUserToBrief(user *model.User) *dto.UserResponse { return nil } return dto.ConvertUserToResponse(user) -} \ No newline at end of file +} diff --git a/internal/service/admin_user_service.go b/internal/service/admin_user_service.go index 5b8d2fe..8f0855f 100644 --- a/internal/service/admin_user_service.go +++ b/internal/service/admin_user_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/async_log_manager.go b/internal/service/async_log_manager.go index 3009558..a1f81ef 100644 --- a/internal/service/async_log_manager.go +++ b/internal/service/async_log_manager.go @@ -1,4 +1,4 @@ -package service +package service import ( "cmp" diff --git a/internal/service/call_service.go b/internal/service/call_service.go index 72c4e62..59141a1 100644 --- a/internal/service/call_service.go +++ b/internal/service/call_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/channel_service.go b/internal/service/channel_service.go index 5f27e94..290f461 100644 --- a/internal/service/channel_service.go +++ b/internal/service/channel_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "time" @@ -126,4 +126,3 @@ func (s *channelServiceImpl) Delete(id string) error { s.invalidateChannelListCache() return nil } - diff --git a/internal/service/chat_service.go b/internal/service/chat_service.go index 22084dd..71cb518 100644 --- a/internal/service/chat_service.go +++ b/internal/service/chat_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" @@ -27,7 +27,7 @@ type ChatService interface { GetConversationList(ctx context.Context, userID string, page, pageSize int) ([]*model.Conversation, int64, error) GetConversationByID(ctx context.Context, conversationID string, userID string) (*model.Conversation, error) DeleteConversationForSelf(ctx context.Context, conversationID string, userID string) error -SetConversationPinned(ctx context.Context, conversationID string, userID string, isPinned bool) error + SetConversationPinned(ctx context.Context, conversationID string, userID string, isPinned bool) error SetConversationNotificationMuted(ctx context.Context, conversationID string, userID string, notificationMuted bool) error // 消息操作 diff --git a/internal/service/email_code_service.go b/internal/service/email_code_service.go index c447457..0a8783f 100644 --- a/internal/service/email_code_service.go +++ b/internal/service/email_code_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/email_service.go b/internal/service/email_service.go index e04402e..95bf6b1 100644 --- a/internal/service/email_service.go +++ b/internal/service/email_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/group_service.go b/internal/service/group_service.go index 6ee1218..d2fee95 100644 --- a/internal/service/group_service.go +++ b/internal/service/group_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "cmp" diff --git a/internal/service/hot_rank_worker.go b/internal/service/hot_rank_worker.go index e8645e9..b8ef04e 100644 --- a/internal/service/hot_rank_worker.go +++ b/internal/service/hot_rank_worker.go @@ -1,4 +1,4 @@ -package service +package service import ( "cmp" @@ -24,24 +24,24 @@ type hotRankScored struct { // HotRankWorker 定时在候选池上重算热门分,写入 Redis ZSET(仅 TopN)及 top_ids(Redis+本地缓存) type HotRankWorker struct { - cfg *config.Config - postRepo repository.PostRepository + cfg *config.Config + postRepo repository.PostRepository channelRepo repository.ChannelRepository - c cache.Cache - mu sync.Mutex - stopOnce sync.Once - stopCh chan struct{} - doneCh chan struct{} + c cache.Cache + mu sync.Mutex + stopOnce sync.Once + stopCh chan struct{} + doneCh chan struct{} } func NewHotRankWorker(cfg *config.Config, postRepo repository.PostRepository, channelRepo repository.ChannelRepository, c cache.Cache) *HotRankWorker { return &HotRankWorker{ - cfg: cfg, - postRepo: postRepo, + cfg: cfg, + postRepo: postRepo, channelRepo: channelRepo, - c: c, - stopCh: make(chan struct{}), - doneCh: make(chan struct{}), + c: c, + stopCh: make(chan struct{}), + doneCh: make(chan struct{}), } } diff --git a/internal/service/jwt_service.go b/internal/service/jwt_service.go index 1293d85..bf4addb 100644 --- a/internal/service/jwt_service.go +++ b/internal/service/jwt_service.go @@ -1,8 +1,8 @@ -package service +package service import ( - "with_you/internal/pkg/jwt" "time" + "with_you/internal/pkg/jwt" ) // JWTService JWT服务 diff --git a/internal/service/log_cleanup_service.go b/internal/service/log_cleanup_service.go index 7df35fe..994da69 100644 --- a/internal/service/log_cleanup_service.go +++ b/internal/service/log_cleanup_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/log_service.go b/internal/service/log_service.go index 3bd4c1a..2de6afd 100644 --- a/internal/service/log_service.go +++ b/internal/service/log_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/material_service.go b/internal/service/material_service.go index 6ddd79b..ce6db09 100644 --- a/internal/service/material_service.go +++ b/internal/service/material_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "errors" @@ -171,4 +171,4 @@ func (s *materialServiceImpl) DeleteMaterial(id string) error { func (s *materialServiceImpl) IncrementDownloadCount(id string) error { return s.fileRepo.IncrementDownloadCount(id) -} \ No newline at end of file +} diff --git a/internal/service/message_segment_image.go b/internal/service/message_segment_image.go index 0f3e536..4fc8728 100644 --- a/internal/service/message_segment_image.go +++ b/internal/service/message_segment_image.go @@ -1,4 +1,4 @@ -package service +package service import ( "fmt" diff --git a/internal/service/message_service.go b/internal/service/message_service.go index 0844879..642b878 100644 --- a/internal/service/message_service.go +++ b/internal/service/message_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/notification_service.go b/internal/service/notification_service.go index 5bf73b8..f191a23 100644 --- a/internal/service/notification_service.go +++ b/internal/service/notification_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/post_ai_service.go b/internal/service/post_ai_service.go index 510635a..cfec88b 100644 --- a/internal/service/post_ai_service.go +++ b/internal/service/post_ai_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/push_service.go b/internal/service/push_service.go index c0d0434..9ff9b86 100644 --- a/internal/service/push_service.go +++ b/internal/service/push_service.go @@ -25,13 +25,15 @@ const ( DefaultExpiredTime = 24 * time.Hour // PushQueueSize 推送队列大小 PushQueueSize = 1000 + // MaxDevicesPerUser 每个用户最大设备数 + MaxDevicesPerUser = 10 ) // ChatMessageSender 聊天消息发送者信息 type ChatMessageSender struct { - ID string - Name string - Avatar string + ID string + Name string + Avatar string } // PushPriority 推送优先级 @@ -62,8 +64,8 @@ type PushService interface { // 设备管理 RegisterDevice(ctx context.Context, userID string, deviceID string, deviceType model.DeviceType, pushToken string) error - UnregisterDevice(ctx context.Context, deviceID string) error - UpdateDeviceToken(ctx context.Context, deviceID string, newPushToken string) error + UnregisterDevice(ctx context.Context, userID string, deviceID string) error + UpdateDeviceToken(ctx context.Context, userID string, deviceID string, newPushToken string) error // 推送记录管理 CreatePushRecord(ctx context.Context, userID string, messageID string, channel model.PushChannel) (*model.PushRecord, error) @@ -135,48 +137,15 @@ func (s *pushServiceImpl) PushToUser(ctx context.Context, userID string, message } // 用户不在线,尝试通过极光推送直接发送 - if s.jpushClient != nil { + if s.isJPushAvailable() { devices, err := s.deviceRepo.GetActiveByUserID(userID) if err == nil && len(devices) > 0 { - mobileDevices := make([]*model.DeviceToken, 0) - for _, d := range devices { - if d.SupportsMobilePush() && d.PushToken != "" { - mobileDevices = append(mobileDevices, d) - } - } + mobileDevices := getMobileDevices(devices) if len(mobileDevices) > 0 { - title := "新消息" - content := dto.ExtractTextContentFromModel(message.Segments) - if message.IsSystemMessage() || message.Category == model.CategoryNotification { - if message.ExtraData != nil && message.ExtraData.ActorName != "" { - title = message.ExtraData.ActorName - } else { - title = "系统通知" - } - } - extras := map[string]any{ - "message_id": message.ID, - "conversation_id": message.ConversationID, - "sender_id": message.SenderID, - "category": string(message.Category), - } - if message.ExtraData != nil { - extras["actor_name"] = message.ExtraData.ActorName - extras["avatar_url"] = message.ExtraData.AvatarURL - extras["target_id"] = message.ExtraData.TargetID - extras["target_type"] = message.ExtraData.TargetType - } - pushErr := s.pushViaJPushBatch(mobileDevices, title, content, extras) + payload := buildMessagePayload(message) + pushErr := s.pushViaJPushBatch(mobileDevices, payload.Title, payload.Content, payload.Extras) if pushErr == nil { - for _, device := range mobileDevices { - record, rerr := s.CreatePushRecord(ctx, userID, message.ID, model.PushChannelJPush) - if rerr == nil { - record.DeviceToken = device.PushToken - record.DeviceType = string(device.DeviceType) - record.MarkPushed() - s.pushRepo.Update(record) - } - } + s.batchCreatePushedRecords(ctx, userID, message.ID, mobileDevices) return nil } } @@ -247,11 +216,10 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m } // 构建普通聊天消息的 WebSocket 消息 - 使用新的 WSEventResponse 格式 - // 获取会话类型 (private/group) + // 根据 Category 推断 detailType,群聊会话需要查询 Conversation 表获取准确类型 detailType := "private" - if message.ConversationID != "" { - // 从会话中获取类型,需要查询数据库或从消息中判断 - // 这里暂时默认为 private,group 类型需要额外逻辑 + if message.Category == model.CategoryNotification || message.IsSystemMessage() { + detailType = "system" } // 直接使用 message.Segments @@ -275,9 +243,50 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m return true } +// messagePayload 推送消息的标题、内容和附加数据 +type messagePayload struct { + Title string + Content string + Extras map[string]any +} + +// buildMessagePayload 从消息中构建推送所需的标题、内容和附加数据 +func buildMessagePayload(message *model.Message) messagePayload { + _ = message.Decrypt() + + title := "新消息" + content := dto.ExtractTextContentFromModel(message.Segments) + if message.IsSystemMessage() || message.Category == model.CategoryNotification { + if message.ExtraData != nil && message.ExtraData.ActorName != "" { + title = message.ExtraData.ActorName + } else { + title = "系统通知" + } + } + + extras := map[string]any{ + "message_id": message.ID, + "conversation_id": message.ConversationID, + "sender_id": message.SenderID, + "category": string(message.Category), + } + if message.ExtraData != nil { + extras["actor_name"] = message.ExtraData.ActorName + extras["avatar_url"] = message.ExtraData.AvatarURL + extras["target_id"] = message.ExtraData.TargetID + extras["target_type"] = message.ExtraData.TargetType + } + + return messagePayload{ + Title: title, + Content: content, + Extras: extras, + } +} + // pushViaJPush 通过极光推送发送通知 func (s *pushServiceImpl) pushViaJPush(ctx context.Context, device *model.DeviceToken, title, content string, extras map[string]any) error { - if s.jpushClient == nil { + if !s.isJPushAvailable() { return errors.New("jpush client not configured") } @@ -286,7 +295,7 @@ func (s *pushServiceImpl) pushViaJPush(ctx context.Context, device *model.Device } notification := jpush.BuildNotification(title, content, "message", extras) - _, err := s.jpushClient.PushByRegistrationIDs([]string{device.PushToken}, notification, extras) + _, err := s.jpushClient.PushByRegistrationIDs([]string{device.PushToken}, notification) if err != nil { return fmt.Errorf("jpush push failed: %w", err) } @@ -294,9 +303,37 @@ func (s *pushServiceImpl) pushViaJPush(ctx context.Context, device *model.Device return nil } +// batchCreatePushedRecords 批量创建已推送成功的记录 +func (s *pushServiceImpl) batchCreatePushedRecords(ctx context.Context, userID string, messageID string, devices []*model.DeviceToken) { + expiredAt := time.Now().Add(DefaultExpiredTime) + records := make([]*model.PushRecord, 0, len(devices)) + for _, device := range devices { + now := time.Now() + record := &model.PushRecord{ + UserID: userID, + MessageID: messageID, + PushChannel: model.PushChannelJPush, + PushStatus: model.PushStatusPushed, + DeviceToken: device.PushToken, + DeviceType: string(device.DeviceType), + MaxRetry: MaxRetryCount, + ExpiredAt: &expiredAt, + PushedAt: &now, + } + records = append(records, record) + } + if err := s.pushRepo.BatchCreate(records); err != nil { + zap.L().Error("batch create pushed records failed", + zap.String("userID", userID), + zap.String("messageID", messageID), + zap.Error(err), + ) + } +} + // pushViaJPushBatch 批量通过极光推送发送通知 func (s *pushServiceImpl) pushViaJPushBatch(devices []*model.DeviceToken, title, content string, extras map[string]any) error { - if s.jpushClient == nil { + if !s.isJPushAvailable() { return errors.New("jpush client not configured") } @@ -312,7 +349,7 @@ func (s *pushServiceImpl) pushViaJPushBatch(devices []*model.DeviceToken, title, } notification := jpush.BuildNotification(title, content, "message", extras) - _, err := s.jpushClient.PushByRegistrationIDs(regIDs, notification, extras) + _, err := s.jpushClient.PushByRegistrationIDs(regIDs, notification) if err != nil { return fmt.Errorf("jpush batch push failed: %w", err) } @@ -322,6 +359,11 @@ func (s *pushServiceImpl) pushViaJPushBatch(devices []*model.DeviceToken, title, // RegisterDevice 注册设备 func (s *pushServiceImpl) RegisterDevice(ctx context.Context, userID string, deviceID string, deviceType model.DeviceType, pushToken string) error { + existing, err := s.deviceRepo.GetByUserID(userID) + if err == nil && len(existing) >= MaxDevicesPerUser { + return fmt.Errorf("exceeded maximum device limit (%d)", MaxDevicesPerUser) + } + deviceToken := &model.DeviceToken{ UserID: userID, DeviceID: deviceID, @@ -335,16 +377,26 @@ func (s *pushServiceImpl) RegisterDevice(ctx context.Context, userID string, dev } // UnregisterDevice 注销设备 -func (s *pushServiceImpl) UnregisterDevice(ctx context.Context, deviceID string) error { +func (s *pushServiceImpl) UnregisterDevice(ctx context.Context, userID string, deviceID string) error { + device, err := s.deviceRepo.GetByDeviceID(deviceID) + if err != nil { + return fmt.Errorf("device not found: %w", err) + } + if device.UserID != userID { + return fmt.Errorf("device does not belong to current user") + } return s.deviceRepo.Deactivate(deviceID) } // UpdateDeviceToken 更新设备Token -func (s *pushServiceImpl) UpdateDeviceToken(ctx context.Context, deviceID string, newPushToken string) error { +func (s *pushServiceImpl) UpdateDeviceToken(ctx context.Context, userID string, deviceID string, newPushToken string) error { deviceToken, err := s.deviceRepo.GetByDeviceID(deviceID) if err != nil { return fmt.Errorf("device not found: %w", err) } + if deviceToken.UserID != userID { + return fmt.Errorf("device does not belong to current user") + } deviceToken.PushToken = newPushToken deviceToken.Activate() @@ -417,58 +469,19 @@ func (s *pushServiceImpl) processPushTask(task *pushTask) { return } - // 提取消息标题和内容 - _ = task.message.Decrypt() - title := "新消息" - content := dto.ExtractTextContentFromModel(task.message.Segments) - if task.message.IsSystemMessage() || task.message.Category == model.CategoryNotification { - if task.message.ExtraData != nil && task.message.ExtraData.ActorName != "" { - title = task.message.ExtraData.ActorName - } else { - title = "系统通知" - } - } - - extras := map[string]any{ - "message_id": task.message.ID, - "conversation_id": task.message.ConversationID, - "sender_id": task.message.SenderID, - "category": string(task.message.Category), - } - if task.message.ExtraData != nil { - extras["actor_name"] = task.message.ExtraData.ActorName - extras["avatar_url"] = task.message.ExtraData.AvatarURL - extras["target_id"] = task.message.ExtraData.TargetID - extras["target_type"] = task.message.ExtraData.TargetType - } + payload := buildMessagePayload(task.message) // 尝试使用极光推送(统一推送到所有移动设备) - mobileDevices := make([]*model.DeviceToken, 0) - for _, device := range devices { - if device.SupportsMobilePush() && device.PushToken != "" { - mobileDevices = append(mobileDevices, device) - } - } + mobileDevices := getMobileDevices(devices) if len(mobileDevices) > 0 { // 优先使用极光推送 - if s.jpushClient != nil { - err := s.pushViaJPushBatch(mobileDevices, title, content, extras) + if s.isJPushAvailable() { + err := s.pushViaJPushBatch(mobileDevices, payload.Title, payload.Content, payload.Extras) if err == nil { - // 极光推送成功,为每个设备创建推送记录 - for _, device := range mobileDevices { - record, rerr := s.CreatePushRecord(ctx, task.userID, task.message.ID, model.PushChannelJPush) - if rerr != nil { - continue - } - record.DeviceToken = device.PushToken - record.DeviceType = string(device.DeviceType) - record.MarkPushed() - s.pushRepo.Update(record) - } + s.batchCreatePushedRecords(ctx, task.userID, task.message.ID, mobileDevices) return } - // 极光推送失败,记录错误,逐个设备重试 } // 极光推送不可用或批量推送失败,逐个设备处理 @@ -482,8 +495,8 @@ func (s *pushServiceImpl) processPushTask(task *pushTask) { record.DeviceType = string(device.DeviceType) var pushErr error - if s.jpushClient != nil { - pushErr = s.pushViaJPush(ctx, device, title, content, extras) + if s.isJPushAvailable() { + pushErr = s.pushViaJPush(ctx, device, payload.Title, payload.Content, payload.Extras) } else { pushErr = errors.New("jpush not configured") } @@ -510,9 +523,29 @@ func (s *pushServiceImpl) getChannelForDevice(device *model.DeviceToken) model.P } } -// retryFailedPushes 重试失败的推送 +// isJPushAvailable 检查极光推送是否可用 +func (s *pushServiceImpl) isJPushAvailable() bool { + return s.jpushClient != nil +} + +// getMobileDevices 从设备列表中筛选出支持手机推送的活跃设备 +func getMobileDevices(devices []*model.DeviceToken) []*model.DeviceToken { + mobileDevices := make([]*model.DeviceToken, 0, len(devices)) + for _, d := range devices { + if d.SupportsMobilePush() && d.PushToken != "" { + mobileDevices = append(mobileDevices, d) + } + } + return mobileDevices +} + +// retryFailedPushes 重试失败的推送(使用指数退避) func (s *pushServiceImpl) retryFailedPushes() { - ticker := time.NewTicker(5 * time.Minute) + baseInterval := 5 * time.Minute + maxInterval := 30 * time.Minute + currentInterval := baseInterval + + ticker := time.NewTicker(currentInterval) defer ticker.Stop() for { @@ -520,21 +553,31 @@ func (s *pushServiceImpl) retryFailedPushes() { case <-s.stopChan: return case <-ticker.C: - s.doRetry() + retryCount := s.doRetry() + if retryCount > 0 { + currentInterval = baseInterval + } else { + currentInterval = currentInterval * 2 + if currentInterval > maxInterval { + currentInterval = maxInterval + } + } + ticker.Reset(currentInterval) } } } -// doRetry 执行重试 -func (s *pushServiceImpl) doRetry() { +// doRetry 执行重试,返回本次成功重试的数量 +func (s *pushServiceImpl) doRetry() int { ctx := context.Background() // 获取失败待重试的推送 records, err := s.pushRepo.GetFailedPushesForRetry(100) if err != nil { - return + return 0 } + retriedCount := 0 for _, record := range records { // 检查是否过期 if record.IsExpired() { @@ -555,6 +598,7 @@ func (s *pushServiceImpl) doRetry() { if s.pushViaWebSocket(ctx, record.UserID, message) { record.MarkDelivered() s.pushRepo.Update(record) + retriedCount++ continue } @@ -568,40 +612,21 @@ func (s *pushServiceImpl) doRetry() { } var pushErr error - if s.jpushClient != nil && device.SupportsMobilePush() && device.PushToken != "" { - _ = message.Decrypt() - title := "新消息" - content := dto.ExtractTextContentFromModel(message.Segments) - if message.IsSystemMessage() || message.Category == model.CategoryNotification { - if message.ExtraData != nil && message.ExtraData.ActorName != "" { - title = message.ExtraData.ActorName - } else { - title = "系统通知" - } - } - extras := map[string]any{ - "message_id": message.ID, - "conversation_id": message.ConversationID, - "sender_id": message.SenderID, - "category": string(message.Category), - } - if message.ExtraData != nil { - extras["actor_name"] = message.ExtraData.ActorName - extras["avatar_url"] = message.ExtraData.AvatarURL - extras["target_id"] = message.ExtraData.TargetID - extras["target_type"] = message.ExtraData.TargetType - } - pushErr = s.pushViaJPush(ctx, device, title, content, extras) + if s.isJPushAvailable() && device.SupportsMobilePush() && device.PushToken != "" { + payload := buildMessagePayload(message) + pushErr = s.pushViaJPush(ctx, device, payload.Title, payload.Content, payload.Extras) } if pushErr != nil { record.MarkFailed(pushErr.Error()) } else { record.MarkPushed() + retriedCount++ } s.pushRepo.Update(record) } } + return retriedCount } // PushChatMessage 推送聊天消息给离线用户 @@ -623,7 +648,7 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co } // 3. 获取用户的移动设备 - if s.jpushClient == nil { + if !s.isJPushAvailable() { return nil } devices, err := s.deviceRepo.GetActiveByUserID(userID) @@ -631,16 +656,16 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co return nil } - var regIDs []string - for _, d := range devices { - if d.SupportsMobilePush() && d.PushToken != "" { - regIDs = append(regIDs, d.PushToken) - } - } - if len(regIDs) == 0 { + mobileDevices := getMobileDevices(devices) + if len(mobileDevices) == 0 { return nil } + var regIDs []string + for _, d := range mobileDevices { + regIDs = append(regIDs, d.PushToken) + } + // 4. 构建通知内容 title := sender.Name if title == "" { @@ -655,13 +680,13 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co } extras := map[string]any{ "notification_type": "chat_message", - "message_id": message.ID, - "conversation_id": message.ConversationID, + "message_id": message.ID, + "conversation_id": message.ConversationID, "conversation_type": string(convType), - "sender_id": message.SenderID, - "sender_name": sender.Name, - "sender_avatar": sender.Avatar, - "category": string(message.Category), + "sender_id": message.SenderID, + "sender_name": sender.Name, + "sender_avatar": sender.Avatar, + "category": string(message.Category), } if message.Category == model.CategoryNotification && message.ExtraData != nil { title = message.ExtraData.ActorName @@ -679,7 +704,7 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co if sender.Avatar != "" { notification.Android.LargeIcon = sender.Avatar } - if _, pushErr := s.jpushClient.PushByRegistrationIDs(regIDs, notification, extras); pushErr != nil { + if _, pushErr := s.jpushClient.PushByRegistrationIDs(regIDs, notification); pushErr != nil { zap.L().Error("jpush push chat message failed", zap.String("userID", userID), zap.String("conversationID", conversationID), @@ -717,13 +742,13 @@ func (s *pushServiceImpl) pushSystemViaWebSocket(ctx context.Context, userID str } sysMsg := map[string]any{ - "type": "notification", + "type": "notification", "system_type": msgType, - "title": title, - "content": content, - "is_read": false, - "extra_data": data, - "created_at": time.Now().Format("2006-01-02T15:04:05Z07:00"), + "title": title, + "content": content, + "is_read": false, + "extra_data": data, + "created_at": time.Now().Format("2006-01-02T15:04:05Z07:00"), } s.wsHub.PublishToUserOnline(userID, "system_notification", sysMsg) return true @@ -737,15 +762,10 @@ func (s *pushServiceImpl) PushSystemNotification(ctx context.Context, userID str } // 用户不在线,尝试极光推送 - if s.jpushClient != nil { + if s.isJPushAvailable() { devices, err := s.deviceRepo.GetActiveByUserID(userID) if err == nil && len(devices) > 0 { - mobileDevices := make([]*model.DeviceToken, 0) - for _, d := range devices { - if d.SupportsMobilePush() && d.PushToken != "" { - mobileDevices = append(mobileDevices, d) - } - } + mobileDevices := getMobileDevices(devices) if len(mobileDevices) > 0 { content := notification.Content title := notification.Title @@ -777,7 +797,6 @@ func (s *pushServiceImpl) PushSystemNotification(ctx context.Context, userID str return ids }(), jpushNotif, - extras, ) if pushErr == nil { return nil diff --git a/internal/service/qrcode_login_service.go b/internal/service/qrcode_login_service.go index c69de92..93e347b 100644 --- a/internal/service/qrcode_login_service.go +++ b/internal/service/qrcode_login_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/report_service.go b/internal/service/report_service.go index 2598813..51c47f2 100644 --- a/internal/service/report_service.go +++ b/internal/service/report_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" @@ -22,16 +22,16 @@ type ReportService interface { // reportServiceImpl 举报服务实现 type reportServiceImpl struct { - reportRepo repository.ReportRepository - postRepo repository.PostRepository - commentRepo repository.CommentRepository - messageRepo repository.MessageRepository - userRepo repository.UserRepository - notifyRepo repository.SystemNotificationRepository - pushService PushService - txManager repository.TransactionManager - logService OperationLogService - config *config.ReportConfig + reportRepo repository.ReportRepository + postRepo repository.PostRepository + commentRepo repository.CommentRepository + messageRepo repository.MessageRepository + userRepo repository.UserRepository + notifyRepo repository.SystemNotificationRepository + pushService PushService + txManager repository.TransactionManager + logService OperationLogService + config *config.ReportConfig } // NewReportService 创建举报服务 @@ -52,16 +52,16 @@ func NewReportService( reportConfig.AutoHideThreshold = 3 } return &reportServiceImpl{ - reportRepo: reportRepo, - postRepo: postRepo, - commentRepo: commentRepo, - messageRepo: messageRepo, - userRepo: userRepo, - notifyRepo: notifyRepo, - pushService: pushService, - txManager: txManager, - logService: logService, - config: reportConfig, + reportRepo: reportRepo, + postRepo: postRepo, + commentRepo: commentRepo, + messageRepo: messageRepo, + userRepo: userRepo, + notifyRepo: notifyRepo, + pushService: pushService, + txManager: txManager, + logService: logService, + config: reportConfig, } } @@ -214,4 +214,4 @@ func (s *reportServiceImpl) hideTargetContent(ctx context.Context, targetType mo return s.messageRepo.UpdateMessageStatus(targetID, model.MessageStatusDeleted) } return nil -} \ No newline at end of file +} diff --git a/internal/service/role_service.go b/internal/service/role_service.go index b45d62f..9995b96 100644 --- a/internal/service/role_service.go +++ b/internal/service/role_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/schedule_service.go b/internal/service/schedule_service.go index 41a49b7..9341f31 100644 --- a/internal/service/schedule_service.go +++ b/internal/service/schedule_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "encoding/json" diff --git a/internal/service/schedule_sync_service.go b/internal/service/schedule_sync_service.go index 2f7669c..9440190 100644 --- a/internal/service/schedule_sync_service.go +++ b/internal/service/schedule_sync_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/sensitive_service.go b/internal/service/sensitive_service.go index fa0d4af..450a686 100644 --- a/internal/service/sensitive_service.go +++ b/internal/service/sensitive_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/setup_service.go b/internal/service/setup_service.go index ae9df63..773a6c2 100644 --- a/internal/service/setup_service.go +++ b/internal/service/setup_service.go @@ -13,10 +13,10 @@ type SetupService interface { } type setupService struct { - userRepo repository.UserRepository - roleRepo repository.RoleRepository - casbinSvc CasbinService - setupSecret string + userRepo repository.UserRepository + roleRepo repository.RoleRepository + casbinSvc CasbinService + setupSecret string } func NewSetupService( @@ -60,4 +60,4 @@ func (s *setupService) SetupSuperAdmin(ctx context.Context, userID, setupSecret } return s.casbinSvc.AddRoleForUser(ctx, user.ID, model.RoleSuperAdmin) -} \ No newline at end of file +} diff --git a/internal/service/sticker_service.go b/internal/service/sticker_service.go index eb19c56..d7729b0 100644 --- a/internal/service/sticker_service.go +++ b/internal/service/sticker_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "errors" diff --git a/internal/service/system_message_service.go b/internal/service/system_message_service.go index ce00892..d933804 100644 --- a/internal/service/system_message_service.go +++ b/internal/service/system_message_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/trade_service.go b/internal/service/trade_service.go index 553a1b5..d337554 100644 --- a/internal/service/trade_service.go +++ b/internal/service/trade_service.go @@ -219,4 +219,4 @@ func (s *tradeService) Unfavorite(ctx context.Context, userID string, id string) } _ = s.tradeRepo.DecrementFavorites(ctx, id) return nil -} \ No newline at end of file +} diff --git a/internal/service/upload_service.go b/internal/service/upload_service.go index 074d467..4523182 100644 --- a/internal/service/upload_service.go +++ b/internal/service/upload_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "bytes" diff --git a/internal/service/user_activity_service.go b/internal/service/user_activity_service.go index 09533fe..c6f65e6 100644 --- a/internal/service/user_activity_service.go +++ b/internal/service/user_activity_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/user_profile_audit_service.go b/internal/service/user_profile_audit_service.go index 57a86af..3e7bbcc 100644 --- a/internal/service/user_profile_audit_service.go +++ b/internal/service/user_profile_audit_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context" diff --git a/internal/service/verification_service.go b/internal/service/verification_service.go index 4a878a8..00f2d65 100644 --- a/internal/service/verification_service.go +++ b/internal/service/verification_service.go @@ -1,4 +1,4 @@ -package service +package service import ( "context"