feat(server): implement chat file TTL cleanup and enhance JPush vendor channel support
Add FileCleanupWorker for automatic expiration of chat files with configurable retention period and batch processing. Files uploaded via `/api/v1/uploads/files` are tracked in `uploaded_files` table with expiration timestamps, then deleted from S3 and database upon expiry. Expired file URLs are injected as `"expired": true` in message responses. Extend JPush push notification configuration with vendor-specific channel_id support (xiaomi, huawei, oppo, vivo, meizu, honor, fcm) for differentiating system vs chat notifications. Add iOS APNs thread-id grouping configuration for notification categorization.
This commit is contained in:
156
internal/pkg/jpush/vendor/converters.go
vendored
Normal file
156
internal/pkg/jpush/vendor/converters.go
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
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)
|
||||
type vivoConverter struct{}
|
||||
|
||||
func (vivoConverter) VendorName() string { return "vivo" }
|
||||
func (vivoConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
|
||||
return channelOnlyMap(params.Scene, cfg.Vendor.VIVO.System, cfg.Vendor.VIVO.Chat, cfg.System, cfg.Chat)
|
||||
}
|
||||
|
||||
// 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)
|
||||
type honorConverter struct{}
|
||||
|
||||
func (honorConverter) VendorName() string { return "honor" }
|
||||
func (honorConverter) Convert(params MessageParams, cfg config.ChannelConfig) map[string]any {
|
||||
return channelOnlyMap(params.Scene, cfg.Vendor.Honor.System, cfg.Vendor.Honor.Chat, cfg.System, cfg.Chat)
|
||||
}
|
||||
|
||||
// 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 字符串
|
||||
// 默认占位符:sender / title / content,可按实际小米模板占位符在集中处调整
|
||||
func buildMiTemplateParam(params MessageParams) string {
|
||||
param := map[string]any{
|
||||
"sender": params.SenderName,
|
||||
"title": params.Title,
|
||||
"content": 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
|
||||
if params.Scene == SceneSystem {
|
||||
templateID = o.OppoSystemPrivateTemplateID
|
||||
}
|
||||
|
||||
if cid == "" && templateID == "" {
|
||||
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,
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user