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
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -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
|
||||
@@ -148,7 +148,7 @@ func (h *PushHandler) UpdateDeviceToken(c *gin.Context) {
|
||||
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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"with_you/internal/dto"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() 实现大小写不敏感的模糊搜索
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
// 消息操作
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
|
||||
@@ -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{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"with_you/internal/pkg/jwt"
|
||||
"time"
|
||||
"with_you/internal/pkg/jwt"
|
||||
)
|
||||
|
||||
// JWTService JWT服务
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
Reference in New Issue
Block a user