- 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
152 lines
5.5 KiB
Go
152 lines
5.5 KiB
Go
// 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.classification):0=运营消息,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 的 key(xiaomi/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
|
||
}
|