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 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,
|
||||
@@ -467,4 +467,4 @@ func convertUserToBrief(user *model.User) *dto.UserResponse {
|
||||
return nil
|
||||
}
|
||||
return dto.ConvertUserToResponse(user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -171,4 +171,4 @@ func (s *materialServiceImpl) DeleteMaterial(id string) error {
|
||||
|
||||
func (s *materialServiceImpl) IncrementDownloadCount(id string) error {
|
||||
return s.fileRepo.IncrementDownloadCount(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,4 +214,4 @@ func (s *reportServiceImpl) hideTargetContent(ctx context.Context, targetType mo
|
||||
return s.messageRepo.UpdateMessageStatus(targetID, model.MessageStatusDeleted)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
@@ -60,4 +60,4 @@ func (s *setupService) SetupSuperAdmin(ctx context.Context, userID, setupSecret
|
||||
}
|
||||
|
||||
return s.casbinSvc.AddRoleForUser(ctx, user.ID, model.RoleSuperAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -219,4 +219,4 @@ func (s *tradeService) Unfavorite(ctx context.Context, userID string, id string)
|
||||
}
|
||||
_ = s.tradeRepo.DecrementFavorites(ctx, id)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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