feat(jpush): add vendor-specific message classification support for push notifications
All checks were successful
Build Backend / build (push) Successful in 3m4s
Build Backend / build-docker (push) Successful in 1m15s

- 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
This commit is contained in:
lafay
2026-06-18 19:41:32 +08:00
parent 5b83f98fb8
commit 821e066446
13 changed files with 637 additions and 66 deletions

View File

@@ -198,8 +198,8 @@ func Load(configPath string) (*Config, error) {
// 各厂商 channel_id 通过环境变量配置(见下方 BindEnv
viper.SetDefault("jpush.channel.system", "")
viper.SetDefault("jpush.channel.chat", "")
viper.SetDefault("jpush.channel.vendor.xiaomi.system", "")
viper.SetDefault("jpush.channel.vendor.xiaomi.chat", "")
viper.SetDefault("jpush.channel.vendor.xiaomi.system", "153609")
viper.SetDefault("jpush.channel.vendor.xiaomi.chat", "153608")
viper.SetDefault("jpush.channel.vendor.huawei.system", "")
viper.SetDefault("jpush.channel.vendor.huawei.chat", "")
viper.SetDefault("jpush.channel.vendor.oppo.system", "")
@@ -213,11 +213,29 @@ func Load(configPath string) (*Config, error) {
viper.SetDefault("jpush.channel.vendor.fcm.system", "")
viper.SetDefault("jpush.channel.vendor.fcm.chat", "")
// 小米/OPPO 模板 id 默认值
viper.SetDefault("jpush.channel.vendor.xiaomi.mi_system_template_id", "")
viper.SetDefault("jpush.channel.vendor.xiaomi.mi_chat_template_id", "")
viper.SetDefault("jpush.channel.vendor.xiaomi.mi_system_template_id", "P10761")
viper.SetDefault("jpush.channel.vendor.xiaomi.mi_chat_template_id", "M10289")
viper.SetDefault("jpush.channel.vendor.oppo.oppo_system_private_template_id", "")
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_private_template_id", "")
// iOS 通知分组 thread-id 默认值
// OPPO 消息分类默认值category / notify_level
// OPPO 消息分类默认值2024.11.20 新规)
// chat: category=IM即时通讯, notify_level=16通知栏+锁屏+横幅+震动+铃声,强提醒)
// system: category=ACCOUNT账号动态/互动通知), notify_level=2通知栏+锁屏,避免打扰)
// 注:使用 notify_level 时 category 必传,此处两者同时配置
viper.SetDefault("jpush.channel.vendor.oppo.oppo_system_category", "ACCOUNT")
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_category", "IM")
viper.SetDefault("jpush.channel.vendor.oppo.oppo_system_notify_level", 2)
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_notify_level", 16)
// 荣耀 importance 默认值
// chat: NORMAL服务与通讯及时送达, system: LOW资讯营销避免打扰
// 注classification 优先级更高,会覆盖 importance
viper.SetDefault("jpush.channel.vendor.honor.honor_system_importance", "LOW")
viper.SetDefault("jpush.channel.vendor.honor.honor_chat_importance", "NORMAL")
// vivo category 默认值classification=1 时必须为系统消息类)
// chat: IM即时通讯, system: ACCOUNT账号动态/互动通知)
viper.SetDefault("jpush.channel.vendor.vivo.vivo_system_category", "ACCOUNT")
viper.SetDefault("jpush.channel.vendor.vivo.vivo_chat_category", "IM")
// iOS 通知分组 thread-id 默认值(留空,不分组)
viper.SetDefault("jpush.channel.ios.chat_thread_id", "")
viper.SetDefault("jpush.channel.ios.system_thread_id", "")
// JPush 厂商 channel_id 环境变量显式绑定
@@ -244,6 +262,17 @@ func Load(configPath string) (*Config, error) {
viper.BindEnv("jpush.channel.vendor.xiaomi.mi_chat_template_id", "APP_JPUSH_CHANNEL_XIAOMI_MI_CHAT_TEMPLATE_ID")
viper.BindEnv("jpush.channel.vendor.oppo.oppo_system_private_template_id", "APP_JPUSH_CHANNEL_OPPO_SYSTEM_PRIVATE_TEMPLATE_ID")
viper.BindEnv("jpush.channel.vendor.oppo.oppo_chat_private_template_id", "APP_JPUSH_CHANNEL_OPPO_CHAT_PRIVATE_TEMPLATE_ID")
// OPPO category / notify_level 环境变量绑定
viper.BindEnv("jpush.channel.vendor.oppo.oppo_system_category", "APP_JPUSH_CHANNEL_OPPO_SYSTEM_CATEGORY")
viper.BindEnv("jpush.channel.vendor.oppo.oppo_chat_category", "APP_JPUSH_CHANNEL_OPPO_CHAT_CATEGORY")
viper.BindEnv("jpush.channel.vendor.oppo.oppo_system_notify_level", "APP_JPUSH_CHANNEL_OPPO_SYSTEM_NOTIFY_LEVEL")
viper.BindEnv("jpush.channel.vendor.oppo.oppo_chat_notify_level", "APP_JPUSH_CHANNEL_OPPO_CHAT_NOTIFY_LEVEL")
// 荣耀 importance 环境变量绑定
viper.BindEnv("jpush.channel.vendor.honor.honor_system_importance", "APP_JPUSH_CHANNEL_HONOR_SYSTEM_IMPORTANCE")
viper.BindEnv("jpush.channel.vendor.honor.honor_chat_importance", "APP_JPUSH_CHANNEL_HONOR_CHAT_IMPORTANCE")
// vivo category 环境变量绑定
viper.BindEnv("jpush.channel.vendor.vivo.vivo_system_category", "APP_JPUSH_CHANNEL_VIVO_SYSTEM_CATEGORY")
viper.BindEnv("jpush.channel.vendor.vivo.vivo_chat_category", "APP_JPUSH_CHANNEL_VIVO_CHAT_CATEGORY")
// iOS thread-id 环境变量绑定
viper.BindEnv("jpush.channel.ios.chat_thread_id", "APP_JPUSH_CHANNEL_IOS_CHAT_THREAD_ID")
viper.BindEnv("jpush.channel.ios.system_thread_id", "APP_JPUSH_CHANNEL_IOS_SYSTEM_THREAD_ID")

View File

@@ -60,4 +60,24 @@ type VendorChannelID struct {
// 见OPUSH 私信模版校验能力接入说明
OppoSystemPrivateTemplateID string `mapstructure:"oppo_system_private_template_id"` // 系统消息私信模板 id
OppoChatPrivateTemplateID string `mapstructure:"oppo_chat_private_template_id"` // 私聊消息私信模板 id
// OPPO 消息分类可选2024.11.20 新规)
// category 消息场景标识,使用 notify_level 时必传notify_level 提醒等级
// 取值category 如 IM/ACCOUNTnotify_level 1=通知栏, 2=通知栏+锁屏, 16=通知栏+锁屏+横幅+震动+铃声
OppoSystemCategory string `mapstructure:"oppo_system_category"` // 系统消息 category如 ACCOUNT
OppoChatCategory string `mapstructure:"oppo_chat_category"` // 私聊消息 category如 IM
OppoSystemNotifyLevel int `mapstructure:"oppo_system_notify_level"` // 系统消息提醒等级(如 2
OppoChatNotifyLevel int `mapstructure:"oppo_chat_notify_level"` // 私聊消息提醒等级(如 16
// 荣耀通知栏消息智能分类可选importance 字段)
// 取值:"NORMAL"=服务与通讯,"LOW"=资讯营销
// 注classification 优先级更高,会覆盖 importance 设置的值
HonorSystemImportance string `mapstructure:"honor_system_importance"` // 系统消息 importance如 LOW
HonorChatImportance string `mapstructure:"honor_chat_importance"` // 私聊消息 importance如 NORMAL
// vivo 厂商消息场景标识可选category 字段)
// 用于标识消息类型确定提醒方式classification=1 时 category 必须为系统消息类
// 不携带 category 会默认按运营消息下发,受频控限制
VivoSystemCategory string `mapstructure:"vivo_system_category"` // 系统消息 category如 ACCOUNT 账号动态)
VivoChatCategory string `mapstructure:"vivo_chat_category"` // 私聊消息 category如 IM 即时通讯)
}

View File

@@ -0,0 +1,82 @@
package config
import (
"path/filepath"
"testing"
)
// TestXiaomiDefaultValuesFromViper 验证config.yaml 中小米字段留空时,
// viper.SetDefault 设置的默认值153608/153609 + M10289/P10761能正确填充到 Config 结构体。
// 这证明:不配置环境变量/yaml 值时,代码内置默认值生效。
func TestXiaomiDefaultValuesFromViper(t *testing.T) {
// 加载仓库内的 config.yaml小米字段已留空依赖代码默认值
yamlPath := filepath.Join("..", "..", "configs", "config.yaml")
cfg, err := Load(yamlPath)
if err != nil {
t.Fatalf("Load config failed: %v", err)
}
x := cfg.JPush.Channel.Vendor.Xiaomi
if x.System != "153609" {
t.Errorf("xiaomi.system default = %q, want 153609", x.System)
}
if x.Chat != "153608" {
t.Errorf("xiaomi.chat default = %q, want 153608", x.Chat)
}
if x.MiSystemTemplateID != "P10761" {
t.Errorf("xiaomi.mi_system_template_id default = %q, want P10761", x.MiSystemTemplateID)
}
if x.MiChatTemplateID != "M10289" {
t.Errorf("xiaomi.mi_chat_template_id default = %q, want M10289", x.MiChatTemplateID)
}
t.Logf("小米默认值生效: system=%s chat=%s mi_sys=%s mi_chat=%s",
x.System, x.Chat, x.MiSystemTemplateID, x.MiChatTemplateID)
}
// TestVendorDefaultValuesFromViper 验证OPPO/vivo/荣耀的分类字段在 yaml 留空时,
// viper.SetDefault 设置的默认值能正确填充到 Config 结构体。
// channel_id 类字段需厂商后台注册,不在默认值范围(留空)。
func TestVendorDefaultValuesFromViper(t *testing.T) {
yamlPath := filepath.Join("..", "..", "configs", "config.yaml")
cfg, err := Load(yamlPath)
if err != nil {
t.Fatalf("Load config failed: %v", err)
}
// OPPO: chat=IM/16, system=ACCOUNT/2
oppo := cfg.JPush.Channel.Vendor.OPPO
if oppo.OppoChatCategory != "IM" {
t.Errorf("oppo.oppo_chat_category default = %q, want IM", oppo.OppoChatCategory)
}
if oppo.OppoSystemCategory != "ACCOUNT" {
t.Errorf("oppo.oppo_system_category default = %q, want ACCOUNT", oppo.OppoSystemCategory)
}
if oppo.OppoChatNotifyLevel != 16 {
t.Errorf("oppo.oppo_chat_notify_level default = %d, want 16", oppo.OppoChatNotifyLevel)
}
if oppo.OppoSystemNotifyLevel != 2 {
t.Errorf("oppo.oppo_system_notify_level default = %d, want 2", oppo.OppoSystemNotifyLevel)
}
// vivo: chat=IM, system=ACCOUNT
vivo := cfg.JPush.Channel.Vendor.VIVO
if vivo.VivoChatCategory != "IM" {
t.Errorf("vivo.vivo_chat_category default = %q, want IM", vivo.VivoChatCategory)
}
if vivo.VivoSystemCategory != "ACCOUNT" {
t.Errorf("vivo.vivo_system_category default = %q, want ACCOUNT", vivo.VivoSystemCategory)
}
// 荣耀: chat=NORMAL, system=LOW
honor := cfg.JPush.Channel.Vendor.Honor
if honor.HonorChatImportance != "NORMAL" {
t.Errorf("honor.honor_chat_importance default = %q, want NORMAL", honor.HonorChatImportance)
}
if honor.HonorSystemImportance != "LOW" {
t.Errorf("honor.honor_system_importance default = %q, want LOW", honor.HonorSystemImportance)
}
t.Logf("OPPO: chat=%s/%d system=%s/%d", oppo.OppoChatCategory, oppo.OppoChatNotifyLevel, oppo.OppoSystemCategory, oppo.OppoSystemNotifyLevel)
t.Logf("vivo: chat=%s system=%s", vivo.VivoChatCategory, vivo.VivoSystemCategory)
t.Logf("荣耀: chat=%s system=%s", honor.HonorChatImportance, honor.HonorSystemImportance)
}