Files
backend/internal/pkg/jpush/vendor/params.go
lafay 821e066446
All checks were successful
Build Backend / build (push) Successful in 3m4s
Build Backend / build-docker (push) Successful in 1m15s
feat(jpush): add vendor-specific message classification support for push notifications
- Add OPPO category/notify_level support (2024.11.20 regulation: category required when notify_level present)
- Add Honor importance field (NORMAL=service/LOW=marketing) for notification classification
- Add vivo category field (IM/ACCOUNT) for message scenario identification
- Set Xiaomi channel/template defaults in code (system=153609, chat=153608, templates P10761/M10289)
- Add classification option to JPush push payloads (0=operation, 1=system)
- Update xiaomi template keywords mapping to match actual template placeholders
- Add keyword search support for GetFollowers and GetFollowing endpoints
- Bind new config fields to environment variables for OPPO/Honor/vivo
2026-06-18 19:41:32 +08:00

152 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package vendor 提供厂商无关的消息参数抽象与各厂商推送字段转换。
//
// 设计要点:
// - 调用方只需提供厂商无关的 MessageParams不感知厂商差异。
// - 由于无法预知目标设备的具体厂商JPush 要求所有厂商私有配置全数下发,
// 由 JPush 服务端按目标设备厂商路由。
// - 双层架构TextRenderer基础层产出 Android/iOS 通用通知)+
// PrivateConverter附加层产出各厂商 third_party_channel 私有字段)。
package vendor
import (
"with_you/internal/config"
"with_you/internal/pkg/jpush"
)
// 场景常量
const (
// SceneSystem 系统消息/通知场景
SceneSystem = "system"
// SceneChat 私聊/群聊消息场景
SceneChat = "chat"
)
// MessageParams 厂商无关的统一消息参数(超集,用不上的字段厂商可忽略)
type MessageParams struct {
// Scene 决定使用哪类 channel/模板SceneSystem 或 SceneChat
Scene string
// Title 通知标题(如发送者昵称、群名:发送者)
Title string
// Body 通知正文(消息内容预览)
Body string
// SenderName 发送者昵称
SenderName string
// SenderID 发送者 ID
SenderID string
// ConversationName 会话名称(群聊为群名)
ConversationName string
// ConversationID 会话 ID
ConversationID string
// MessageType 消息类型标识chat_message / 系统通知类型),填充到 extras.notification_type
MessageType string
// ActionTime 操作时间RFC3339
ActionTime string
// LargeIcon 大图标 URL群头像/发送者头像)
LargeIcon string
// Extra 兜底扩展字段,合并进 android/ios 的 extras
Extra map[string]any
}
// 消息分类常量(对应 JPush options.classification
const (
// ClassificationOperation 运营消息0营销/公告类,受厂商限速
ClassificationOperation = 0
// ClassificationSystem 系统消息1即时聊天/系统通知/互动通知,高优先级及时送达
// 注:聊天即时消息也归为系统消息,避免被厂商运营消息限速影响送达
ClassificationSystem = 1
)
// PushPayload 工厂统一产物client 据此组装最终推送 payload
type PushPayload struct {
// Notification 含 android + ios由 TextRenderer 产出
Notification *jpush.Notification
// TPC 各厂商私有字段options.third_party_channel由 PrivateConverter 产出
TPC *jpush.ThirdPartyChannel
// Classification 消息分类options.classification0=运营消息1=系统消息
// 聊天和系统通知统一用 1避免厂商运营消息限速
Classification int
}
// TextRenderer 通用文本渲染器(基础层)
// 产出所有厂商共享的通用通知Android 通用通知 + iOS 通用通知
type TextRenderer interface {
// RenderAndroid 渲染 Android 通用通知(所有 Android 厂商共享)
RenderAndroid(params MessageParams, cfg config.ChannelConfig) *jpush.AndroidNotif
// RenderIOS 渲染 iOS 通用通知(按场景设置 thread-id 分组)
RenderIOS(params MessageParams, cfg config.ChannelConfig) *jpush.IOSNotif
}
// PrivateConverter 厂商私有转换器(附加层)
// 产出 third_party_channel.{vendor} 的私有字段channel_id、模板等
type PrivateConverter interface {
// VendorName 返回厂商标识,对应 third_party_channel 的 keyxiaomi/oppo/huawei/...
VendorName() string
// Convert 根据消息参数 + 全局配置产出该厂商私有字段 map
// 厂商专属 channel_id 为空时回退到全局 cfg.System/Chat
// 返回 nil 表示该厂商无需下发(如最终 channel_id 与模板均未配置)
Convert(params MessageParams, cfg config.ChannelConfig) map[string]any
}
// Factory 持有通用渲染器与全部厂商私有转换器
type Factory struct {
renderer TextRenderer
privateConverters []PrivateConverter
}
// NewFactory 创建工厂,注册默认渲染器与全部厂商转换器
func NewFactory() *Factory {
return &Factory{
renderer: newDefaultRenderer(),
privateConverters: []PrivateConverter{
xiaomiConverter{},
huaweiConverter{},
oppoConverter{},
vivoConverter{},
meizuConverter{},
honorConverter{},
fcmConverter{},
},
}
}
// NewFactoryWith 自定义渲染器与转换器创建工厂(便于测试)
func NewFactoryWith(renderer TextRenderer, converters []PrivateConverter) *Factory {
return &Factory{renderer: renderer, privateConverters: converters}
}
// Build 根据统一参数与配置,组装完整的 PushPayload
// 调用通用渲染器产出 Notification调用各私有转换器产出 ThirdPartyChannel
func (f *Factory) Build(params MessageParams, cfg config.ChannelConfig) *PushPayload {
android := f.renderer.RenderAndroid(params, cfg)
ios := f.renderer.RenderIOS(params, cfg)
notification := &jpush.Notification{
Alert: params.Body,
Android: android,
IOS: ios,
}
tpc := &jpush.ThirdPartyChannel{}
for _, c := range f.privateConverters {
m := c.Convert(params, cfg)
if m == nil {
continue
}
tpc.Set(c.VendorName(), m)
}
return &PushPayload{
Notification: notification,
TPC: tpc.Normalize(),
Classification: resolveClassification(params.Scene),
}
}
// resolveClassification 根据场景映射 JPush options.classification
// 聊天即时消息与系统通知统一归为系统消息1避免厂商运营消息限速影响送达
// 未来若引入营销/公告推送可扩展为运营消息0
func resolveClassification(scene string) int {
// system 与 chat 场景均使用系统消息分类
return ClassificationSystem
}