// 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 }