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:
@@ -19,6 +19,7 @@ type Config struct {
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
||||
Upload UploadConfig `mapstructure:"upload"`
|
||||
FileCleanup FileCleanupConfig `mapstructure:"file_cleanup"`
|
||||
OpenAI OpenAIConfig `mapstructure:"openai"`
|
||||
TencentTMS TencentTMSConfig `mapstructure:"tencent_tms"`
|
||||
Email EmailConfig `mapstructure:"email"`
|
||||
@@ -95,6 +96,11 @@ func Load(configPath string) (*Config, error) {
|
||||
viper.SetDefault("rate_limit.requests_per_minute", 60)
|
||||
viper.SetDefault("upload.max_file_size", 10485760)
|
||||
viper.SetDefault("upload.allowed_types", []string{"image/jpeg", "image/png", "image/gif", "image/webp"})
|
||||
// 文件过期清理默认值
|
||||
viper.SetDefault("file_cleanup.enabled", true)
|
||||
viper.SetDefault("file_cleanup.retention_days", 7)
|
||||
viper.SetDefault("file_cleanup.interval_minutes", 360)
|
||||
viper.SetDefault("file_cleanup.batch_size", 100)
|
||||
viper.SetDefault("s3.endpoint", "")
|
||||
viper.SetDefault("s3.access_key", "")
|
||||
viper.SetDefault("s3.secret_key", "")
|
||||
@@ -174,6 +180,59 @@ func Load(configPath string) (*Config, error) {
|
||||
viper.SetDefault("jpush.app_key", "")
|
||||
viper.SetDefault("jpush.master_secret", "")
|
||||
viper.SetDefault("jpush.production", false)
|
||||
// JPush 厂商通道 channel_id 默认值
|
||||
// 各厂商 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.huawei.system", "")
|
||||
viper.SetDefault("jpush.channel.vendor.huawei.chat", "")
|
||||
viper.SetDefault("jpush.channel.vendor.oppo.system", "")
|
||||
viper.SetDefault("jpush.channel.vendor.oppo.chat", "")
|
||||
viper.SetDefault("jpush.channel.vendor.vivo.system", "")
|
||||
viper.SetDefault("jpush.channel.vendor.vivo.chat", "")
|
||||
viper.SetDefault("jpush.channel.vendor.meizu.system", "")
|
||||
viper.SetDefault("jpush.channel.vendor.meizu.chat", "")
|
||||
viper.SetDefault("jpush.channel.vendor.honor.system", "")
|
||||
viper.SetDefault("jpush.channel.vendor.honor.chat", "")
|
||||
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.oppo.oppo_system_private_template_id", "")
|
||||
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_private_template_id", "")
|
||||
// iOS 通知分组 thread-id 默认值
|
||||
viper.SetDefault("jpush.channel.ios.chat_thread_id", "")
|
||||
viper.SetDefault("jpush.channel.ios.system_thread_id", "")
|
||||
// JPush 厂商 channel_id 环境变量显式绑定
|
||||
// AutomaticEnv 对深层嵌套 key 不可靠,故逐个 BindEnv
|
||||
// 命名规则:APP_JPUSH_CHANNEL_{VENDOR}_{SYSTEM|CHAT}
|
||||
viper.BindEnv("jpush.channel.system", "APP_JPUSH_CHANNEL_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.chat", "APP_JPUSH_CHANNEL_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.xiaomi.system", "APP_JPUSH_CHANNEL_XIAOMI_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.xiaomi.chat", "APP_JPUSH_CHANNEL_XIAOMI_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.huawei.system", "APP_JPUSH_CHANNEL_HUAWEI_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.huawei.chat", "APP_JPUSH_CHANNEL_HUAWEI_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.oppo.system", "APP_JPUSH_CHANNEL_OPPO_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.oppo.chat", "APP_JPUSH_CHANNEL_OPPO_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.vivo.system", "APP_JPUSH_CHANNEL_VIVO_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.vivo.chat", "APP_JPUSH_CHANNEL_VIVO_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.meizu.system", "APP_JPUSH_CHANNEL_MEIZU_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.meizu.chat", "APP_JPUSH_CHANNEL_MEIZU_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.honor.system", "APP_JPUSH_CHANNEL_HONOR_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.honor.chat", "APP_JPUSH_CHANNEL_HONOR_CHAT")
|
||||
viper.BindEnv("jpush.channel.vendor.fcm.system", "APP_JPUSH_CHANNEL_FCM_SYSTEM")
|
||||
viper.BindEnv("jpush.channel.vendor.fcm.chat", "APP_JPUSH_CHANNEL_FCM_CHAT")
|
||||
// 小米/OPPO 模板 id 环境变量绑定
|
||||
viper.BindEnv("jpush.channel.vendor.xiaomi.mi_system_template_id", "APP_JPUSH_CHANNEL_XIAOMI_MI_SYSTEM_TEMPLATE_ID")
|
||||
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")
|
||||
// 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")
|
||||
viper.SetDefault("setup_secret", "")
|
||||
// WebSocket 默认值
|
||||
viper.SetDefault("websocket.mode", "standalone")
|
||||
|
||||
@@ -1,8 +1,63 @@
|
||||
package config
|
||||
|
||||
type JPushConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
AppKey string `mapstructure:"app_key"`
|
||||
MasterSecret string `mapstructure:"master_secret"`
|
||||
Production bool `mapstructure:"production"`
|
||||
}
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
AppKey string `mapstructure:"app_key"`
|
||||
MasterSecret string `mapstructure:"master_secret"`
|
||||
Production bool `mapstructure:"production"`
|
||||
// Channel 厂商通道 channel_id 配置
|
||||
// 通过 options.third_party_channel 下发到各厂商(小米/华为/OPPO 等)
|
||||
Channel ChannelConfig `mapstructure:"channel"`
|
||||
}
|
||||
|
||||
// ChannelConfig 厂商通道 channel_id 配置
|
||||
// 区分「系统消息」与「私聊/群聊消息」两类 channel
|
||||
type ChannelConfig struct {
|
||||
// System 系统消息/通知默认 channel_id,未单独配置 Vendor 时各厂商共用
|
||||
System string `mapstructure:"system"`
|
||||
// Chat 私聊/群聊消息默认 channel_id,未单独配置 Vendor 时各厂商共用
|
||||
Chat string `mapstructure:"chat"`
|
||||
// Vendor 各厂商 channel_id 覆盖配置(留空则回退到 System/Chat)
|
||||
Vendor VendorChannel `mapstructure:"vendor"`
|
||||
// iOS APNs 通知场景区分配置(按 thread-id 分组)
|
||||
IOS IOSChannelConfig `mapstructure:"ios"`
|
||||
}
|
||||
|
||||
// IOSChannelConfig iOS APNs 通知场景区分配置
|
||||
// iOS 走 APNs 无 channel_id 概念,通过 thread-id 实现通知分组
|
||||
type IOSChannelConfig struct {
|
||||
// ChatThreadID 私聊/群聊消息通知分组 thread-id
|
||||
ChatThreadID string `mapstructure:"chat_thread_id"`
|
||||
// SystemThreadID 系统消息/通知分组 thread-id
|
||||
SystemThreadID string `mapstructure:"system_thread_id"`
|
||||
}
|
||||
|
||||
// VendorChannel 各厂商通道 channel_id 覆盖
|
||||
// 每个厂商区分 System(系统消息)与 Chat(私聊消息)两个 channel_id
|
||||
type VendorChannel struct {
|
||||
Xiaomi VendorChannelID `mapstructure:"xiaomi"`
|
||||
Huawei VendorChannelID `mapstructure:"huawei"`
|
||||
OPPO VendorChannelID `mapstructure:"oppo"`
|
||||
VIVO VendorChannelID `mapstructure:"vivo"`
|
||||
Meizu VendorChannelID `mapstructure:"meizu"`
|
||||
Honor VendorChannelID `mapstructure:"honor"`
|
||||
FCM VendorChannelID `mapstructure:"fcm"`
|
||||
}
|
||||
|
||||
// VendorChannelID 单个厂商的两类 channel 配置
|
||||
// 每个 channel 可携带厂商私有模板配置(如小米 mi_template_id、OPPO private_msg_template_id)
|
||||
type VendorChannelID struct {
|
||||
// channel_id
|
||||
System string `mapstructure:"system"`
|
||||
Chat string `mapstructure:"chat"`
|
||||
|
||||
// 小米消息模板(可选,配置后私信消息下发时携带 channel_id 及 mi_template_id)
|
||||
// 见:小米关于消息模板推送新规的更新通知
|
||||
MiSystemTemplateID string `mapstructure:"mi_system_template_id"` // 系统消息模板 id
|
||||
MiChatTemplateID string `mapstructure:"mi_chat_template_id"` // 私聊消息模板 id
|
||||
|
||||
// OPPO 私信模板(可选,仅 OPPO 厂商,配置后下发私信时携带)
|
||||
// 见:OPUSH 私信模版校验能力接入说明
|
||||
OppoSystemPrivateTemplateID string `mapstructure:"oppo_system_private_template_id"` // 系统消息私信模板 id
|
||||
OppoChatPrivateTemplateID string `mapstructure:"oppo_chat_private_template_id"` // 私聊消息私信模板 id
|
||||
}
|
||||
|
||||
@@ -61,3 +61,11 @@ type UploadConfig struct {
|
||||
MaxFileSize int64 `mapstructure:"max_file_size"`
|
||||
AllowedTypes []string `mapstructure:"allowed_types"`
|
||||
}
|
||||
|
||||
// FileCleanupConfig 聊天文件 TTL 过期清理配置
|
||||
type FileCleanupConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"` // 是否启用清理 worker
|
||||
RetentionDays int `mapstructure:"retention_days"` // 保留天数,默认 7
|
||||
IntervalMinutes int `mapstructure:"interval_minutes"` // 扫描间隔(分钟),默认 360(6 小时)
|
||||
BatchSize int `mapstructure:"batch_size"` // 单次扫描处理上限,默认 100
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user