Files
backend/internal/pkg/jpush/vendor/converters.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

221 lines
6.8 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
import (
"encoding/json"
"with_you/internal/config"
)
// resolveChannelID 按场景选择 channel_id厂商专属值优先空则回退到全局默认
// scene 为 SceneSystem 时取 system 配置,其余取 chat 配置
func resolveChannelID(scene, vendorSys, vendorChat, defaultSys, defaultChat string) string {
if scene == SceneSystem {
if vendorSys != "" {
return vendorSys
}
return defaultSys
}
if vendorChat != "" {
return vendorChat
}
return defaultChat
}
// channelOnlyMap 仅 channel_id 厂商的通用转换逻辑(厂商空则回退全局默认)
// 最终 channel_id 为空时返回 nil不下发该厂商
func channelOnlyMap(scene, vendorSys, vendorChat, defaultSys, defaultChat string) map[string]any {
cid := resolveChannelID(scene, vendorSys, vendorChat, defaultSys, defaultChat)
if cid == "" {
return nil
}
return map[string]any{"channel_id": cid}
}
// huaweiConverter 华为厂商转换器(仅 channel_id
type huaweiConverter struct{}
func (huaweiConverter) VendorName() string { return "huawei" }
func (huaweiConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
return channelOnlyMap(params.Scene, cfg.Vendor.Huawei.System, cfg.Vendor.Huawei.Chat, cfg.System, cfg.Chat)
}
// vivoConverter VIVO 厂商转换器
// channel_id + category厂商消息场景标识
// classification=1 时 category 必须为系统消息类,不携带 category 会默认按运营消息下发受频控限制
type vivoConverter struct{}
func (vivoConverter) VendorName() string { return "vivo" }
func (vivoConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
v := cfg.Vendor.VIVO
cid := resolveChannelID(params.Scene, v.System, v.Chat, cfg.System, cfg.Chat)
category := v.VivoChatCategory
if params.Scene == SceneSystem {
category = v.VivoSystemCategory
}
if cid == "" && category == "" {
return nil
}
m := map[string]any{}
if cid != "" {
m["channel_id"] = cid
}
if category != "" {
m["category"] = category
}
return m
}
// meizuConverter 魅族厂商转换器(仅 channel_id
type meizuConverter struct{}
func (meizuConverter) VendorName() string { return "meizu" }
func (meizuConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
return channelOnlyMap(params.Scene, cfg.Vendor.Meizu.System, cfg.Vendor.Meizu.Chat, cfg.System, cfg.Chat)
}
// honorConverter 荣耀厂商转换器
// channel_id + importance通知栏消息智能分类
// importance 取值:"NORMAL"=服务与通讯,"LOW"=资讯营销
// 注classification 优先级更高,会覆盖 importance 设置的值
type honorConverter struct{}
func (honorConverter) VendorName() string { return "honor" }
func (honorConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
h := cfg.Vendor.Honor
cid := resolveChannelID(params.Scene, h.System, h.Chat, cfg.System, cfg.Chat)
importance := h.HonorChatImportance
if params.Scene == SceneSystem {
importance = h.HonorSystemImportance
}
if cid == "" && importance == "" {
return nil
}
m := map[string]any{}
if cid != "" {
m["channel_id"] = cid
}
if importance != "" {
m["importance"] = importance
}
return m
}
// fcmConverter FCM 厂商转换器(仅 channel_id
type fcmConverter struct{}
func (fcmConverter) VendorName() string { return "fcm" }
func (fcmConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
return channelOnlyMap(params.Scene, cfg.Vendor.FCM.System, cfg.Vendor.FCM.Chat, cfg.System, cfg.Chat)
}
// xiaomiConverter 小米厂商转换器
// 配置 mi_template_id 时,私信消息下发时携带 channel_id 及 mi_template_id + mi_template_param
type xiaomiConverter struct{}
func (xiaomiConverter) VendorName() string { return "xiaomi" }
func (xiaomiConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
x := cfg.Vendor.Xiaomi
cid := resolveChannelID(params.Scene, x.System, x.Chat, cfg.System, cfg.Chat)
// 小米模板按场景选择
templateID := x.MiChatTemplateID
if params.Scene == SceneSystem {
templateID = x.MiSystemTemplateID
}
// 最终 channel_id 与模板均未配置则跳过
if cid == "" && templateID == "" {
return nil
}
m := map[string]any{}
if cid != "" {
m["channel_id"] = cid
}
if templateID != "" {
m["mi_template_id"] = templateID
// mi_template_param 为模板参数的 JSON 字符串
m["mi_template_param"] = buildMiTemplateParam(params)
}
return m
}
// buildMiTemplateParam 生成小米消息模板参数 JSON 字符串
// 占位符严格匹配小米模板定义的 keywords
// - 私聊模板M10289 好友聊天): keywords1=title, keywords2=发送者, keywords3=内容
// - 系统模板P10761 系统消息): keywords1=标题, keywords2=内容
func buildMiTemplateParam(params MessageParams) string {
var param map[string]any
if params.Scene == SceneSystem {
param = map[string]any{
"keywords1": params.Title,
"keywords2": params.Body,
}
} else {
param = map[string]any{
"keywords1": params.Title,
"keywords2": params.SenderName,
"keywords3": params.Body,
}
}
b, err := json.Marshal(param)
if err != nil {
return "{}"
}
return string(b)
}
// oppoConverter OPPO 厂商转换器
// 配置 private_msg_template_id 时,私信消息下发时携带 private_msg_template_id + private_title_parameters + private_content_parameters
type oppoConverter struct{}
func (oppoConverter) VendorName() string { return "oppo" }
func (oppoConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
o := cfg.Vendor.OPPO
cid := resolveChannelID(params.Scene, o.System, o.Chat, cfg.System, cfg.Chat)
// OPPO 私信模板按场景选择
templateID := o.OppoChatPrivateTemplateID
category := o.OppoChatCategory
notifyLevel := o.OppoChatNotifyLevel
if params.Scene == SceneSystem {
templateID = o.OppoSystemPrivateTemplateID
category = o.OppoSystemCategory
notifyLevel = o.OppoSystemNotifyLevel
}
if cid == "" && templateID == "" && category == "" && notifyLevel == 0 {
return nil
}
m := map[string]any{}
if cid != "" {
m["channel_id"] = cid
}
if templateID != "" {
m["private_msg_template_id"] = templateID
// private_title_parameters / private_content_parameters 为 JSONObject
m["private_title_parameters"] = map[string]any{
"title": params.Title,
}
m["private_content_parameters"] = map[string]any{
"content": params.Body,
"sender": params.SenderName,
}
}
// OPPO 2024.11.20 新规category 消息场景标识notify_level 提醒等级
// 文档约束:使用 notify_level 时 category 必传,否则 OPPO 校验失败
// 这里做保护notify_level 非零但 category 为空时跳过 notify_level
if category != "" {
m["category"] = category
if notifyLevel != 0 {
m["notify_level"] = notifyLevel
}
}
return m
}