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 (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"with_you/internal/dto"
|
"with_you/internal/dto"
|
||||||
"with_you/internal/pkg/response"
|
"with_you/internal/pkg/response"
|
||||||
"with_you/internal/service"
|
"with_you/internal/service"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"with_you/internal/model"
|
"with_you/internal/model"
|
||||||
@@ -120,4 +120,3 @@ func (h *ChannelHandler) AdminDelete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
response.Success(c, gin.H{"success": true})
|
response.Success(c, gin.H{"success": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -940,7 +940,7 @@ func (h *MessageHandler) HandleSetConversationNotificationMuted(c *gin.Context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
response.SuccessWithMessage(c, "conversation notification_muted status updated", gin.H{
|
response.SuccessWithMessage(c, "conversation notification_muted status updated", gin.H{
|
||||||
"conversation_id": conversationID,
|
"conversation_id": conversationID,
|
||||||
"notification_muted": req.NotificationMuted,
|
"notification_muted": req.NotificationMuted,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"with_you/internal/dto"
|
"with_you/internal/dto"
|
||||||
@@ -67,7 +67,7 @@ func (h *PushHandler) UnregisterDevice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.pushService.UnregisterDevice(c.Request.Context(), deviceID)
|
err := h.pushService.UnregisterDevice(c.Request.Context(), userID, deviceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalServerError(c, "failed to unregister device")
|
response.InternalServerError(c, "failed to unregister device")
|
||||||
return
|
return
|
||||||
@@ -148,7 +148,7 @@ func (h *PushHandler) UpdateDeviceToken(c *gin.Context) {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
response.InternalServerError(c, "failed to update device token")
|
response.InternalServerError(c, "failed to update device token")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"with_you/internal/dto"
|
"with_you/internal/dto"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package jpush
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -16,8 +18,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
PushAPIBaseURL = "https://api.jpush.cn/v3"
|
PushAPIBaseURL = "https://api.jpush.cn/v3"
|
||||||
DeviceAPIBaseURL = "https://device.jpush.cn/v3"
|
DeviceAPIBaseURL = "https://device.jpush.cn/v3"
|
||||||
ReportAPIBaseURL = "https://report.jpush.cn/v3"
|
ReportAPIBaseURL = "https://report.jpush.cn/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@@ -26,14 +28,25 @@ type Client struct {
|
|||||||
production bool
|
production bool
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
lastRateLimit *RateLimitInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(appKey, masterSecret string, production bool, logger *zap.Logger) *Client {
|
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{
|
return &Client{
|
||||||
appKey: appKey,
|
appKey: appKey,
|
||||||
masterSecret: masterSecret,
|
masterSecret: masterSecret,
|
||||||
production: production,
|
production: production,
|
||||||
httpClient: &http.Client{Timeout: 30 * time.Second},
|
httpClient: &http.Client{Timeout: 30 * time.Second, Transport: transport},
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,9 +92,9 @@ type IOSNotif struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PushOptions struct {
|
type PushOptions struct {
|
||||||
TimeToLive int `json:"time_to_live,omitempty"`
|
TimeToLive int `json:"time_to_live,omitempty"`
|
||||||
ApnsProduction bool `json:"apns_production"`
|
ApnsProduction bool `json:"apns_production"`
|
||||||
ApnsCollapseID string `json:"apns_collapse_id,omitempty"`
|
ApnsCollapseID string `json:"apns_collapse_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PushResponse struct {
|
type PushResponse struct {
|
||||||
@@ -96,7 +109,7 @@ type RateLimitInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
ErrCode int `json:"code"`
|
ErrCode int `json:"code"`
|
||||||
ErrMessage string `json:"message"`
|
ErrMessage string `json:"message"`
|
||||||
ErrDetail struct {
|
ErrDetail struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
@@ -199,6 +212,14 @@ func (c *Client) Push(payload map[string]any) (*PushResponse, *RateLimitInfo, er
|
|||||||
return nil, rateLimit, err
|
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)
|
result, parseErr := c.parseResponse(resp, respBody)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
c.logger.Error("jpush push response parse failed",
|
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
|
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 {
|
if len(registrationIDs) == 0 {
|
||||||
return nil, fmt.Errorf("registration IDs cannot be empty")
|
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),
|
zap.String("alert", notification.Alert),
|
||||||
)
|
)
|
||||||
|
|
||||||
mergeExtras(notification, extras)
|
|
||||||
|
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"platform": "all",
|
"platform": "all",
|
||||||
"audience": map[string]any{"registration_id": registrationIDs},
|
"audience": map[string]any{"registration_id": registrationIDs},
|
||||||
@@ -256,7 +275,7 @@ func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *N
|
|||||||
return result, nil
|
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 {
|
if len(aliases) == 0 {
|
||||||
return nil, fmt.Errorf("aliases cannot be empty")
|
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),
|
zap.String("alert", notification.Alert),
|
||||||
)
|
)
|
||||||
|
|
||||||
mergeExtras(notification, extras)
|
|
||||||
|
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"platform": "all",
|
"platform": "all",
|
||||||
"audience": map[string]any{"alias": aliases},
|
"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.Int("count", len(aliases)),
|
||||||
zap.String("msg_id", result.MsgID),
|
zap.String("msg_id", result.MsgID),
|
||||||
)
|
)
|
||||||
return result, err
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PushAll(notification *Notification) (*PushResponse, error) {
|
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",
|
c.logger.Info("jpush push all success",
|
||||||
zap.String("msg_id", result.MsgID),
|
zap.String("msg_id", result.MsgID),
|
||||||
)
|
)
|
||||||
return result, err
|
return result, nil
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildNotification(title, body string, notificationType string, extras map[string]any) *Notification {
|
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),
|
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)
|
resp, body, _, err := c.doRequest("GET", path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -427,7 +422,7 @@ func (c *Client) SetDeviceAlias(registrationID string, alias string) error {
|
|||||||
zap.String("alias", alias),
|
zap.String("alias", alias),
|
||||||
)
|
)
|
||||||
|
|
||||||
path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID)
|
path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID)
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"alias": alias,
|
"alias": alias,
|
||||||
}
|
}
|
||||||
@@ -461,7 +456,7 @@ func (c *Client) SetDeviceTags(registrationID string, addTags, removeTags []stri
|
|||||||
zap.Strings("remove_tags", removeTags),
|
zap.Strings("remove_tags", removeTags),
|
||||||
)
|
)
|
||||||
|
|
||||||
path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID)
|
path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID)
|
||||||
tags := map[string]any{}
|
tags := map[string]any{}
|
||||||
if len(addTags) > 0 {
|
if len(addTags) > 0 {
|
||||||
tags["add"] = addTags
|
tags["add"] = addTags
|
||||||
@@ -499,7 +494,7 @@ func (c *Client) GetAliasRegistrationIDs(alias string) ([]string, error) {
|
|||||||
zap.String("alias", alias),
|
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)
|
resp, body, _, err := c.doRequest("GET", path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -532,7 +527,7 @@ func (c *Client) DeleteAlias(alias string) error {
|
|||||||
zap.String("alias", alias),
|
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)
|
resp, body, _, err := c.doRequest("DELETE", path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -559,7 +554,7 @@ func (c *Client) RemoveAliasDevices(alias string, registrationIDs []string) erro
|
|||||||
zap.Int("count", len(registrationIDs)),
|
zap.Int("count", len(registrationIDs)),
|
||||||
)
|
)
|
||||||
|
|
||||||
path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias)
|
path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias)
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"registration_ids": map[string]any{
|
"registration_ids": map[string]any{
|
||||||
"remove": registrationIDs,
|
"remove": registrationIDs,
|
||||||
|
|||||||
@@ -510,11 +510,8 @@ func (r *postRepository) Search(keyword string, page, pageSize int) ([]*model.Po
|
|||||||
// 搜索标题和内容
|
// 搜索标题和内容
|
||||||
if keyword != "" {
|
if keyword != "" {
|
||||||
if r.db.Dialector.Name() == "postgres" {
|
if r.db.Dialector.Name() == "postgres" {
|
||||||
// PostgreSQL 使用全文检索表达式,为 pg_trgm/GIN 索引升级预留路径
|
searchPattern := "%" + keyword + "%"
|
||||||
query = query.Where(
|
query = query.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern)
|
||||||
"to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)",
|
|
||||||
keyword,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
searchPattern := "%" + keyword + "%"
|
searchPattern := "%" + keyword + "%"
|
||||||
query = query.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
|
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 query.Keyword != "" {
|
||||||
if r.db.Dialector.Name() == "postgres" {
|
if r.db.Dialector.Name() == "postgres" {
|
||||||
db = db.Where(
|
searchPattern := "%" + query.Keyword + "%"
|
||||||
"to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)",
|
db = db.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern)
|
||||||
query.Keyword,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
searchPattern := "%" + query.Keyword + "%"
|
searchPattern := "%" + query.Keyword + "%"
|
||||||
db = db.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
|
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 keyword != "" {
|
||||||
if r.db.Dialector.Name() == "postgres" {
|
if r.db.Dialector.Name() == "postgres" {
|
||||||
// PostgreSQL 使用全文检索表达式
|
searchPattern := "%" + keyword + "%"
|
||||||
query = query.Where(
|
query = query.Where("title ILIKE ? OR content ILIKE ?", searchPattern, searchPattern)
|
||||||
"to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)",
|
|
||||||
keyword,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
searchPattern := "%" + keyword + "%"
|
searchPattern := "%" + keyword + "%"
|
||||||
query = query.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
|
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 keyword != "" {
|
||||||
if r.db.Dialector.Name() == "postgres" {
|
if r.db.Dialector.Name() == "postgres" {
|
||||||
|
searchPattern := "%" + keyword + "%"
|
||||||
query = query.Where(
|
query = query.Where(
|
||||||
"to_tsvector('simple', COALESCE(username, '') || ' ' || COALESCE(nickname, '') || ' ' || COALESCE(bio, '')) @@ plainto_tsquery('simple', ?)",
|
"username ILIKE ? OR nickname ILIKE ? OR bio ILIKE ?",
|
||||||
keyword,
|
searchPattern, searchPattern, searchPattern,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// SQLite: 使用 LOWER() 实现大小写不敏感的模糊搜索
|
// SQLite: 使用 LOWER() 实现大小写不敏感的模糊搜索
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -26,15 +26,15 @@ type AdminReportService interface {
|
|||||||
|
|
||||||
// adminReportServiceImpl 管理端举报服务实现
|
// adminReportServiceImpl 管理端举报服务实现
|
||||||
type adminReportServiceImpl struct {
|
type adminReportServiceImpl struct {
|
||||||
reportRepo repository.ReportRepository
|
reportRepo repository.ReportRepository
|
||||||
postRepo repository.PostRepository
|
postRepo repository.PostRepository
|
||||||
commentRepo repository.CommentRepository
|
commentRepo repository.CommentRepository
|
||||||
messageRepo repository.MessageRepository
|
messageRepo repository.MessageRepository
|
||||||
userRepo repository.UserRepository
|
userRepo repository.UserRepository
|
||||||
notifyRepo repository.SystemNotificationRepository
|
notifyRepo repository.SystemNotificationRepository
|
||||||
pushService PushService
|
pushService PushService
|
||||||
txManager repository.TransactionManager
|
txManager repository.TransactionManager
|
||||||
logService OperationLogService
|
logService OperationLogService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdminReportService 创建管理端举报服务
|
// NewAdminReportService 创建管理端举报服务
|
||||||
@@ -50,15 +50,15 @@ func NewAdminReportService(
|
|||||||
logService OperationLogService,
|
logService OperationLogService,
|
||||||
) AdminReportService {
|
) AdminReportService {
|
||||||
return &adminReportServiceImpl{
|
return &adminReportServiceImpl{
|
||||||
reportRepo: reportRepo,
|
reportRepo: reportRepo,
|
||||||
postRepo: postRepo,
|
postRepo: postRepo,
|
||||||
commentRepo: commentRepo,
|
commentRepo: commentRepo,
|
||||||
messageRepo: messageRepo,
|
messageRepo: messageRepo,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
notifyRepo: notifyRepo,
|
notifyRepo: notifyRepo,
|
||||||
pushService: pushService,
|
pushService: pushService,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
logService: logService,
|
logService: logService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,9 +405,9 @@ func (s *adminReportServiceImpl) notifyReporter(ctx context.Context, report *mod
|
|||||||
// 创建通知
|
// 创建通知
|
||||||
notification := &model.SystemNotification{
|
notification := &model.SystemNotification{
|
||||||
ReceiverID: report.ReporterID,
|
ReceiverID: report.ReporterID,
|
||||||
Type: notifyType,
|
Type: notifyType,
|
||||||
Title: title,
|
Title: title,
|
||||||
Content: content,
|
Content: content,
|
||||||
ExtraData: &model.SystemNotificationExtra{
|
ExtraData: &model.SystemNotificationExtra{
|
||||||
ActorIDStr: report.ReporterID,
|
ActorIDStr: report.ReporterID,
|
||||||
TargetID: report.TargetID,
|
TargetID: report.TargetID,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
@@ -126,4 +126,3 @@ func (s *channelServiceImpl) Delete(id string) error {
|
|||||||
s.invalidateChannelListCache()
|
s.invalidateChannelListCache()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -27,7 +27,7 @@ type ChatService interface {
|
|||||||
GetConversationList(ctx context.Context, userID string, page, pageSize int) ([]*model.Conversation, int64, error)
|
GetConversationList(ctx context.Context, userID string, page, pageSize int) ([]*model.Conversation, int64, error)
|
||||||
GetConversationByID(ctx context.Context, conversationID string, userID string) (*model.Conversation, error)
|
GetConversationByID(ctx context.Context, conversationID string, userID string) (*model.Conversation, error)
|
||||||
DeleteConversationForSelf(ctx context.Context, conversationID string, userID string) 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
|
SetConversationNotificationMuted(ctx context.Context, conversationID string, userID string, notificationMuted bool) error
|
||||||
|
|
||||||
// 消息操作
|
// 消息操作
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@@ -24,24 +24,24 @@ type hotRankScored struct {
|
|||||||
|
|
||||||
// HotRankWorker 定时在候选池上重算热门分,写入 Redis ZSET(仅 TopN)及 top_ids(Redis+本地缓存)
|
// HotRankWorker 定时在候选池上重算热门分,写入 Redis ZSET(仅 TopN)及 top_ids(Redis+本地缓存)
|
||||||
type HotRankWorker struct {
|
type HotRankWorker struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
postRepo repository.PostRepository
|
postRepo repository.PostRepository
|
||||||
channelRepo repository.ChannelRepository
|
channelRepo repository.ChannelRepository
|
||||||
c cache.Cache
|
c cache.Cache
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
doneCh chan struct{}
|
doneCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHotRankWorker(cfg *config.Config, postRepo repository.PostRepository, channelRepo repository.ChannelRepository, c cache.Cache) *HotRankWorker {
|
func NewHotRankWorker(cfg *config.Config, postRepo repository.PostRepository, channelRepo repository.ChannelRepository, c cache.Cache) *HotRankWorker {
|
||||||
return &HotRankWorker{
|
return &HotRankWorker{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
postRepo: postRepo,
|
postRepo: postRepo,
|
||||||
channelRepo: channelRepo,
|
channelRepo: channelRepo,
|
||||||
c: c,
|
c: c,
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
doneCh: make(chan struct{}),
|
doneCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"with_you/internal/pkg/jwt"
|
|
||||||
"time"
|
"time"
|
||||||
|
"with_you/internal/pkg/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWTService JWT服务
|
// JWTService JWT服务
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -25,13 +25,15 @@ const (
|
|||||||
DefaultExpiredTime = 24 * time.Hour
|
DefaultExpiredTime = 24 * time.Hour
|
||||||
// PushQueueSize 推送队列大小
|
// PushQueueSize 推送队列大小
|
||||||
PushQueueSize = 1000
|
PushQueueSize = 1000
|
||||||
|
// MaxDevicesPerUser 每个用户最大设备数
|
||||||
|
MaxDevicesPerUser = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChatMessageSender 聊天消息发送者信息
|
// ChatMessageSender 聊天消息发送者信息
|
||||||
type ChatMessageSender struct {
|
type ChatMessageSender struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Avatar string
|
Avatar string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushPriority 推送优先级
|
// PushPriority 推送优先级
|
||||||
@@ -62,8 +64,8 @@ type PushService interface {
|
|||||||
|
|
||||||
// 设备管理
|
// 设备管理
|
||||||
RegisterDevice(ctx context.Context, userID string, deviceID string, deviceType model.DeviceType, pushToken string) error
|
RegisterDevice(ctx context.Context, userID string, deviceID string, deviceType model.DeviceType, pushToken string) error
|
||||||
UnregisterDevice(ctx context.Context, deviceID string) error
|
UnregisterDevice(ctx context.Context, userID string, deviceID string) error
|
||||||
UpdateDeviceToken(ctx context.Context, deviceID string, newPushToken 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)
|
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)
|
devices, err := s.deviceRepo.GetActiveByUserID(userID)
|
||||||
if err == nil && len(devices) > 0 {
|
if err == nil && len(devices) > 0 {
|
||||||
mobileDevices := make([]*model.DeviceToken, 0)
|
mobileDevices := getMobileDevices(devices)
|
||||||
for _, d := range devices {
|
|
||||||
if d.SupportsMobilePush() && d.PushToken != "" {
|
|
||||||
mobileDevices = append(mobileDevices, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(mobileDevices) > 0 {
|
if len(mobileDevices) > 0 {
|
||||||
title := "新消息"
|
payload := buildMessagePayload(message)
|
||||||
content := dto.ExtractTextContentFromModel(message.Segments)
|
pushErr := s.pushViaJPushBatch(mobileDevices, payload.Title, payload.Content, payload.Extras)
|
||||||
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)
|
|
||||||
if pushErr == nil {
|
if pushErr == nil {
|
||||||
for _, device := range mobileDevices {
|
s.batchCreatePushedRecords(ctx, userID, message.ID, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,11 +216,10 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 构建普通聊天消息的 WebSocket 消息 - 使用新的 WSEventResponse 格式
|
// 构建普通聊天消息的 WebSocket 消息 - 使用新的 WSEventResponse 格式
|
||||||
// 获取会话类型 (private/group)
|
// 根据 Category 推断 detailType,群聊会话需要查询 Conversation 表获取准确类型
|
||||||
detailType := "private"
|
detailType := "private"
|
||||||
if message.ConversationID != "" {
|
if message.Category == model.CategoryNotification || message.IsSystemMessage() {
|
||||||
// 从会话中获取类型,需要查询数据库或从消息中判断
|
detailType = "system"
|
||||||
// 这里暂时默认为 private,group 类型需要额外逻辑
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接使用 message.Segments
|
// 直接使用 message.Segments
|
||||||
@@ -275,9 +243,50 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m
|
|||||||
return true
|
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 通过极光推送发送通知
|
// pushViaJPush 通过极光推送发送通知
|
||||||
func (s *pushServiceImpl) pushViaJPush(ctx context.Context, device *model.DeviceToken, title, content string, extras map[string]any) error {
|
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")
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("jpush push failed: %w", err)
|
return fmt.Errorf("jpush push failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -294,9 +303,37 @@ func (s *pushServiceImpl) pushViaJPush(ctx context.Context, device *model.Device
|
|||||||
return nil
|
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 批量通过极光推送发送通知
|
// pushViaJPushBatch 批量通过极光推送发送通知
|
||||||
func (s *pushServiceImpl) pushViaJPushBatch(devices []*model.DeviceToken, title, content string, extras map[string]any) error {
|
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")
|
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)
|
notification := jpush.BuildNotification(title, content, "message", extras)
|
||||||
_, err := s.jpushClient.PushByRegistrationIDs(regIDs, notification, extras)
|
_, err := s.jpushClient.PushByRegistrationIDs(regIDs, notification)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("jpush batch push failed: %w", err)
|
return fmt.Errorf("jpush batch push failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -322,6 +359,11 @@ func (s *pushServiceImpl) pushViaJPushBatch(devices []*model.DeviceToken, title,
|
|||||||
|
|
||||||
// RegisterDevice 注册设备
|
// RegisterDevice 注册设备
|
||||||
func (s *pushServiceImpl) RegisterDevice(ctx context.Context, userID string, deviceID string, deviceType model.DeviceType, pushToken string) error {
|
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{
|
deviceToken := &model.DeviceToken{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
@@ -335,16 +377,26 @@ func (s *pushServiceImpl) RegisterDevice(ctx context.Context, userID string, dev
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterDevice 注销设备
|
// 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)
|
return s.deviceRepo.Deactivate(deviceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDeviceToken 更新设备Token
|
// 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)
|
deviceToken, err := s.deviceRepo.GetByDeviceID(deviceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("device not found: %w", err)
|
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.PushToken = newPushToken
|
||||||
deviceToken.Activate()
|
deviceToken.Activate()
|
||||||
@@ -417,58 +469,19 @@ func (s *pushServiceImpl) processPushTask(task *pushTask) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取消息标题和内容
|
payload := buildMessagePayload(task.message)
|
||||||
_ = 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试使用极光推送(统一推送到所有移动设备)
|
// 尝试使用极光推送(统一推送到所有移动设备)
|
||||||
mobileDevices := make([]*model.DeviceToken, 0)
|
mobileDevices := getMobileDevices(devices)
|
||||||
for _, device := range devices {
|
|
||||||
if device.SupportsMobilePush() && device.PushToken != "" {
|
|
||||||
mobileDevices = append(mobileDevices, device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mobileDevices) > 0 {
|
if len(mobileDevices) > 0 {
|
||||||
// 优先使用极光推送
|
// 优先使用极光推送
|
||||||
if s.jpushClient != nil {
|
if s.isJPushAvailable() {
|
||||||
err := s.pushViaJPushBatch(mobileDevices, title, content, extras)
|
err := s.pushViaJPushBatch(mobileDevices, payload.Title, payload.Content, payload.Extras)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 极光推送成功,为每个设备创建推送记录
|
s.batchCreatePushedRecords(ctx, task.userID, task.message.ID, mobileDevices)
|
||||||
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)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 极光推送失败,记录错误,逐个设备重试
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 极光推送不可用或批量推送失败,逐个设备处理
|
// 极光推送不可用或批量推送失败,逐个设备处理
|
||||||
@@ -482,8 +495,8 @@ func (s *pushServiceImpl) processPushTask(task *pushTask) {
|
|||||||
record.DeviceType = string(device.DeviceType)
|
record.DeviceType = string(device.DeviceType)
|
||||||
|
|
||||||
var pushErr error
|
var pushErr error
|
||||||
if s.jpushClient != nil {
|
if s.isJPushAvailable() {
|
||||||
pushErr = s.pushViaJPush(ctx, device, title, content, extras)
|
pushErr = s.pushViaJPush(ctx, device, payload.Title, payload.Content, payload.Extras)
|
||||||
} else {
|
} else {
|
||||||
pushErr = errors.New("jpush not configured")
|
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() {
|
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()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -520,21 +553,31 @@ func (s *pushServiceImpl) retryFailedPushes() {
|
|||||||
case <-s.stopChan:
|
case <-s.stopChan:
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
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 执行重试
|
// doRetry 执行重试,返回本次成功重试的数量
|
||||||
func (s *pushServiceImpl) doRetry() {
|
func (s *pushServiceImpl) doRetry() int {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// 获取失败待重试的推送
|
// 获取失败待重试的推送
|
||||||
records, err := s.pushRepo.GetFailedPushesForRetry(100)
|
records, err := s.pushRepo.GetFailedPushesForRetry(100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retriedCount := 0
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
// 检查是否过期
|
// 检查是否过期
|
||||||
if record.IsExpired() {
|
if record.IsExpired() {
|
||||||
@@ -555,6 +598,7 @@ func (s *pushServiceImpl) doRetry() {
|
|||||||
if s.pushViaWebSocket(ctx, record.UserID, message) {
|
if s.pushViaWebSocket(ctx, record.UserID, message) {
|
||||||
record.MarkDelivered()
|
record.MarkDelivered()
|
||||||
s.pushRepo.Update(record)
|
s.pushRepo.Update(record)
|
||||||
|
retriedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,40 +612,21 @@ func (s *pushServiceImpl) doRetry() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pushErr error
|
var pushErr error
|
||||||
if s.jpushClient != nil && device.SupportsMobilePush() && device.PushToken != "" {
|
if s.isJPushAvailable() && device.SupportsMobilePush() && device.PushToken != "" {
|
||||||
_ = message.Decrypt()
|
payload := buildMessagePayload(message)
|
||||||
title := "新消息"
|
pushErr = s.pushViaJPush(ctx, device, payload.Title, payload.Content, payload.Extras)
|
||||||
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 pushErr != nil {
|
if pushErr != nil {
|
||||||
record.MarkFailed(pushErr.Error())
|
record.MarkFailed(pushErr.Error())
|
||||||
} else {
|
} else {
|
||||||
record.MarkPushed()
|
record.MarkPushed()
|
||||||
|
retriedCount++
|
||||||
}
|
}
|
||||||
s.pushRepo.Update(record)
|
s.pushRepo.Update(record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return retriedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushChatMessage 推送聊天消息给离线用户
|
// PushChatMessage 推送聊天消息给离线用户
|
||||||
@@ -623,7 +648,7 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 获取用户的移动设备
|
// 3. 获取用户的移动设备
|
||||||
if s.jpushClient == nil {
|
if !s.isJPushAvailable() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
devices, err := s.deviceRepo.GetActiveByUserID(userID)
|
devices, err := s.deviceRepo.GetActiveByUserID(userID)
|
||||||
@@ -631,16 +656,16 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var regIDs []string
|
mobileDevices := getMobileDevices(devices)
|
||||||
for _, d := range devices {
|
if len(mobileDevices) == 0 {
|
||||||
if d.SupportsMobilePush() && d.PushToken != "" {
|
|
||||||
regIDs = append(regIDs, d.PushToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(regIDs) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var regIDs []string
|
||||||
|
for _, d := range mobileDevices {
|
||||||
|
regIDs = append(regIDs, d.PushToken)
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 构建通知内容
|
// 4. 构建通知内容
|
||||||
title := sender.Name
|
title := sender.Name
|
||||||
if title == "" {
|
if title == "" {
|
||||||
@@ -655,13 +680,13 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co
|
|||||||
}
|
}
|
||||||
extras := map[string]any{
|
extras := map[string]any{
|
||||||
"notification_type": "chat_message",
|
"notification_type": "chat_message",
|
||||||
"message_id": message.ID,
|
"message_id": message.ID,
|
||||||
"conversation_id": message.ConversationID,
|
"conversation_id": message.ConversationID,
|
||||||
"conversation_type": string(convType),
|
"conversation_type": string(convType),
|
||||||
"sender_id": message.SenderID,
|
"sender_id": message.SenderID,
|
||||||
"sender_name": sender.Name,
|
"sender_name": sender.Name,
|
||||||
"sender_avatar": sender.Avatar,
|
"sender_avatar": sender.Avatar,
|
||||||
"category": string(message.Category),
|
"category": string(message.Category),
|
||||||
}
|
}
|
||||||
if message.Category == model.CategoryNotification && message.ExtraData != nil {
|
if message.Category == model.CategoryNotification && message.ExtraData != nil {
|
||||||
title = message.ExtraData.ActorName
|
title = message.ExtraData.ActorName
|
||||||
@@ -679,7 +704,7 @@ func (s *pushServiceImpl) PushChatMessage(ctx context.Context, userID string, co
|
|||||||
if sender.Avatar != "" {
|
if sender.Avatar != "" {
|
||||||
notification.Android.LargeIcon = 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.L().Error("jpush push chat message failed",
|
||||||
zap.String("userID", userID),
|
zap.String("userID", userID),
|
||||||
zap.String("conversationID", conversationID),
|
zap.String("conversationID", conversationID),
|
||||||
@@ -717,13 +742,13 @@ func (s *pushServiceImpl) pushSystemViaWebSocket(ctx context.Context, userID str
|
|||||||
}
|
}
|
||||||
|
|
||||||
sysMsg := map[string]any{
|
sysMsg := map[string]any{
|
||||||
"type": "notification",
|
"type": "notification",
|
||||||
"system_type": msgType,
|
"system_type": msgType,
|
||||||
"title": title,
|
"title": title,
|
||||||
"content": content,
|
"content": content,
|
||||||
"is_read": false,
|
"is_read": false,
|
||||||
"extra_data": data,
|
"extra_data": data,
|
||||||
"created_at": time.Now().Format("2006-01-02T15:04:05Z07:00"),
|
"created_at": time.Now().Format("2006-01-02T15:04:05Z07:00"),
|
||||||
}
|
}
|
||||||
s.wsHub.PublishToUserOnline(userID, "system_notification", sysMsg)
|
s.wsHub.PublishToUserOnline(userID, "system_notification", sysMsg)
|
||||||
return true
|
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)
|
devices, err := s.deviceRepo.GetActiveByUserID(userID)
|
||||||
if err == nil && len(devices) > 0 {
|
if err == nil && len(devices) > 0 {
|
||||||
mobileDevices := make([]*model.DeviceToken, 0)
|
mobileDevices := getMobileDevices(devices)
|
||||||
for _, d := range devices {
|
|
||||||
if d.SupportsMobilePush() && d.PushToken != "" {
|
|
||||||
mobileDevices = append(mobileDevices, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(mobileDevices) > 0 {
|
if len(mobileDevices) > 0 {
|
||||||
content := notification.Content
|
content := notification.Content
|
||||||
title := notification.Title
|
title := notification.Title
|
||||||
@@ -777,7 +797,6 @@ func (s *pushServiceImpl) PushSystemNotification(ctx context.Context, userID str
|
|||||||
return ids
|
return ids
|
||||||
}(),
|
}(),
|
||||||
jpushNotif,
|
jpushNotif,
|
||||||
extras,
|
|
||||||
)
|
)
|
||||||
if pushErr == nil {
|
if pushErr == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -22,16 +22,16 @@ type ReportService interface {
|
|||||||
|
|
||||||
// reportServiceImpl 举报服务实现
|
// reportServiceImpl 举报服务实现
|
||||||
type reportServiceImpl struct {
|
type reportServiceImpl struct {
|
||||||
reportRepo repository.ReportRepository
|
reportRepo repository.ReportRepository
|
||||||
postRepo repository.PostRepository
|
postRepo repository.PostRepository
|
||||||
commentRepo repository.CommentRepository
|
commentRepo repository.CommentRepository
|
||||||
messageRepo repository.MessageRepository
|
messageRepo repository.MessageRepository
|
||||||
userRepo repository.UserRepository
|
userRepo repository.UserRepository
|
||||||
notifyRepo repository.SystemNotificationRepository
|
notifyRepo repository.SystemNotificationRepository
|
||||||
pushService PushService
|
pushService PushService
|
||||||
txManager repository.TransactionManager
|
txManager repository.TransactionManager
|
||||||
logService OperationLogService
|
logService OperationLogService
|
||||||
config *config.ReportConfig
|
config *config.ReportConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReportService 创建举报服务
|
// NewReportService 创建举报服务
|
||||||
@@ -52,16 +52,16 @@ func NewReportService(
|
|||||||
reportConfig.AutoHideThreshold = 3
|
reportConfig.AutoHideThreshold = 3
|
||||||
}
|
}
|
||||||
return &reportServiceImpl{
|
return &reportServiceImpl{
|
||||||
reportRepo: reportRepo,
|
reportRepo: reportRepo,
|
||||||
postRepo: postRepo,
|
postRepo: postRepo,
|
||||||
commentRepo: commentRepo,
|
commentRepo: commentRepo,
|
||||||
messageRepo: messageRepo,
|
messageRepo: messageRepo,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
notifyRepo: notifyRepo,
|
notifyRepo: notifyRepo,
|
||||||
pushService: pushService,
|
pushService: pushService,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
logService: logService,
|
logService: logService,
|
||||||
config: reportConfig,
|
config: reportConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ type SetupService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type setupService struct {
|
type setupService struct {
|
||||||
userRepo repository.UserRepository
|
userRepo repository.UserRepository
|
||||||
roleRepo repository.RoleRepository
|
roleRepo repository.RoleRepository
|
||||||
casbinSvc CasbinService
|
casbinSvc CasbinService
|
||||||
setupSecret string
|
setupSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSetupService(
|
func NewSetupService(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
Reference in New Issue
Block a user