refactor: unify code formatting and improve push/search implementations
All checks were successful
Build Backend / build (push) Successful in 3m54s
Build Backend / build-docker (push) Successful in 1m9s

- 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:
lafay
2026-04-28 14:53:04 +08:00
parent 179e468131
commit 67a5660952
67 changed files with 356 additions and 351 deletions

View File

@@ -2,10 +2,12 @@ package jpush
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strconv"
@@ -16,8 +18,8 @@ import (
const (
PushAPIBaseURL = "https://api.jpush.cn/v3"
DeviceAPIBaseURL = "https://device.jpush.cn/v3"
ReportAPIBaseURL = "https://report.jpush.cn/v3"
DeviceAPIBaseURL = "https://device.jpush.cn/v3"
ReportAPIBaseURL = "https://report.jpush.cn/v3"
)
type Client struct {
@@ -26,14 +28,25 @@ type Client struct {
production bool
httpClient *http.Client
logger *zap.Logger
lastRateLimit *RateLimitInfo
}
func NewClient(appKey, masterSecret string, production bool, logger *zap.Logger) *Client {
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
}
return &Client{
appKey: appKey,
masterSecret: masterSecret,
production: production,
httpClient: &http.Client{Timeout: 30 * time.Second},
httpClient: &http.Client{Timeout: 30 * time.Second, Transport: transport},
logger: logger,
}
}
@@ -79,9 +92,9 @@ type IOSNotif struct {
}
type PushOptions struct {
TimeToLive int `json:"time_to_live,omitempty"`
ApnsProduction bool `json:"apns_production"`
ApnsCollapseID string `json:"apns_collapse_id,omitempty"`
TimeToLive int `json:"time_to_live,omitempty"`
ApnsProduction bool `json:"apns_production"`
ApnsCollapseID string `json:"apns_collapse_id,omitempty"`
}
type PushResponse struct {
@@ -96,7 +109,7 @@ type RateLimitInfo struct {
}
type APIError struct {
ErrCode int `json:"code"`
ErrCode int `json:"code"`
ErrMessage string `json:"message"`
ErrDetail struct {
Code int `json:"code"`
@@ -199,6 +212,14 @@ func (c *Client) Push(payload map[string]any) (*PushResponse, *RateLimitInfo, er
return nil, rateLimit, err
}
c.lastRateLimit = rateLimit
if rateLimit != nil && rateLimit.Limit > 0 && rateLimit.Remaining < rateLimit.Limit/10 {
c.logger.Warn("jpush rate limit approaching",
zap.Int("remaining", rateLimit.Remaining),
zap.Int("limit", rateLimit.Limit),
)
}
result, parseErr := c.parseResponse(resp, respBody)
if parseErr != nil {
c.logger.Error("jpush push response parse failed",
@@ -214,7 +235,7 @@ func (c *Client) Push(payload map[string]any) (*PushResponse, *RateLimitInfo, er
return result, rateLimit, parseErr
}
func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *Notification, extras map[string]any) (*PushResponse, error) {
func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *Notification) (*PushResponse, error) {
if len(registrationIDs) == 0 {
return nil, fmt.Errorf("registration IDs cannot be empty")
}
@@ -228,8 +249,6 @@ func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *N
zap.String("alert", notification.Alert),
)
mergeExtras(notification, extras)
payload := map[string]any{
"platform": "all",
"audience": map[string]any{"registration_id": registrationIDs},
@@ -256,7 +275,7 @@ func (c *Client) PushByRegistrationIDs(registrationIDs []string, notification *N
return result, nil
}
func (c *Client) PushByAliases(aliases []string, notification *Notification, extras map[string]any) (*PushResponse, error) {
func (c *Client) PushByAliases(aliases []string, notification *Notification) (*PushResponse, error) {
if len(aliases) == 0 {
return nil, fmt.Errorf("aliases cannot be empty")
}
@@ -270,8 +289,6 @@ func (c *Client) PushByAliases(aliases []string, notification *Notification, ext
zap.String("alert", notification.Alert),
)
mergeExtras(notification, extras)
payload := map[string]any{
"platform": "all",
"audience": map[string]any{"alias": aliases},
@@ -295,7 +312,7 @@ func (c *Client) PushByAliases(aliases []string, notification *Notification, ext
zap.Int("count", len(aliases)),
zap.String("msg_id", result.MsgID),
)
return result, err
return result, nil
}
func (c *Client) PushAll(notification *Notification) (*PushResponse, error) {
@@ -325,29 +342,7 @@ func (c *Client) PushAll(notification *Notification) (*PushResponse, error) {
c.logger.Info("jpush push all success",
zap.String("msg_id", result.MsgID),
)
return result, err
}
func mergeExtras(notification *Notification, extras map[string]any) {
if extras == nil {
return
}
if notification.Android != nil {
if notification.Android.Extras == nil {
notification.Android.Extras = make(map[string]any)
}
for k, v := range extras {
notification.Android.Extras[k] = v
}
}
if notification.IOS != nil {
if notification.IOS.Extras == nil {
notification.IOS.Extras = make(map[string]any)
}
for k, v := range extras {
notification.IOS.Extras[k] = v
}
}
return result, nil
}
func BuildNotification(title, body string, notificationType string, extras map[string]any) *Notification {
@@ -394,7 +389,7 @@ func (c *Client) GetDeviceInfo(registrationID string) (*DeviceInfo, error) {
zap.String("registration_id", registrationID),
)
path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID)
path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID)
resp, body, _, err := c.doRequest("GET", path, nil)
if err != nil {
return nil, err
@@ -427,7 +422,7 @@ func (c *Client) SetDeviceAlias(registrationID string, alias string) error {
zap.String("alias", alias),
)
path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID)
path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID)
payload := map[string]any{
"alias": alias,
}
@@ -461,7 +456,7 @@ func (c *Client) SetDeviceTags(registrationID string, addTags, removeTags []stri
zap.Strings("remove_tags", removeTags),
)
path := DeviceAPIBaseURL + "/v3/devices/" + url.PathEscape(registrationID)
path := DeviceAPIBaseURL + "/devices/" + url.PathEscape(registrationID)
tags := map[string]any{}
if len(addTags) > 0 {
tags["add"] = addTags
@@ -499,7 +494,7 @@ func (c *Client) GetAliasRegistrationIDs(alias string) ([]string, error) {
zap.String("alias", alias),
)
path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias)
path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias)
resp, body, _, err := c.doRequest("GET", path, nil)
if err != nil {
return nil, err
@@ -532,7 +527,7 @@ func (c *Client) DeleteAlias(alias string) error {
zap.String("alias", alias),
)
path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias)
path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias)
resp, body, _, err := c.doRequest("DELETE", path, nil)
if err != nil {
return err
@@ -559,7 +554,7 @@ func (c *Client) RemoveAliasDevices(alias string, registrationIDs []string) erro
zap.Int("count", len(registrationIDs)),
)
path := DeviceAPIBaseURL + "/v3/aliases/" + url.PathEscape(alias)
path := DeviceAPIBaseURL + "/aliases/" + url.PathEscape(alias)
payload := map[string]any{
"registration_ids": map[string]any{
"remove": registrationIDs,