From 3764b6ef5720e184f4db62504a77134068e70eb7 Mon Sep 17 00:00:00 2001 From: zpc Date: Sun, 29 Mar 2026 21:22:01 +0800 Subject: [PATCH] feat(wechat): Add template field mapping configuration for notification templates - Add field mapping properties to NotificationTemplatesConfig interface for unlock, favorite, message, and daily recommend templates - Add UI form fields in system config view for configuring JSON-based field mappings with helper text - Implement dynamic template data construction using configured field mappings in NotificationService - Add fallback to traditional first/keyword format when field mapping is not configured - Support flexible field mapping with value types: title, name, content, time, remark - Update SystemConfigService to persist and retrieve field mapping configurations - Enable backend-driven template field customization without code changes --- admin/src/api/config.ts | 4 + admin/src/views/system/config.vue | 52 ++++++++++++- .../Services/NotificationService.cs | 77 +++++++++++++++++-- .../Services/SystemConfigService.cs | 38 ++++++++- 4 files changed, 160 insertions(+), 11 deletions(-) diff --git a/admin/src/api/config.ts b/admin/src/api/config.ts index 0c09497..c7abbd1 100644 --- a/admin/src/api/config.ts +++ b/admin/src/api/config.ts @@ -192,9 +192,13 @@ export interface NotificationTemplatesConfig { token?: string encodingAESKey?: string unlockTemplateId?: string + unlockFieldMapping?: string favoriteTemplateId?: string + favoriteFieldMapping?: string messageTemplateId?: string + messageFieldMapping?: string dailyRecommendTemplateId?: string + dailyRecommendFieldMapping?: string } /** diff --git a/admin/src/views/system/config.vue b/admin/src/views/system/config.vue index b7abdfe..663b2db 100644 --- a/admin/src/views/system/config.vue +++ b/admin/src/views/system/config.vue @@ -340,6 +340,15 @@
有用户解锁我时,服务号发送相应通知
+ + +
JSON格式,key为模板字段名,value为数据类型:title=标题, name=名称, content=内容, time=时间, remark=备注
+
+ 有用户收藏我时,服务号发送相应通知 + + +
JSON格式,key为模板字段名,value为数据类型:title=标题, name=名称, content=内容, time=时间, remark=备注
+
+ 首次沟通通知、5分钟未回复提醒共用此模板 + + +
JSON格式,key为模板字段名,value为数据类型:title=标题, name=名称, content=内容, time=时间, remark=备注
+
+ 每天早上8~10点随机时间发送推荐更新通知 + + +
JSON格式,key为模板字段名,value为数据类型:title=标题, name=名称, content=内容, time=时间, remark=备注
+
+ 保存模板配置 @@ -438,9 +474,13 @@ const templateForm = ref({ token: '', encodingAESKey: '', unlockTemplateId: '', + unlockFieldMapping: '', favoriteTemplateId: '', + favoriteFieldMapping: '', messageTemplateId: '', - dailyRecommendTemplateId: '' + messageFieldMapping: '', + dailyRecommendTemplateId: '', + dailyRecommendFieldMapping: '' }) const saving = ref(false) @@ -718,9 +758,13 @@ const loadNotificationTemplates = async () => { templateForm.value.token = res.token || '' templateForm.value.encodingAESKey = res.encodingAESKey || '' templateForm.value.unlockTemplateId = res.unlockTemplateId || '' + templateForm.value.unlockFieldMapping = res.unlockFieldMapping || '' templateForm.value.favoriteTemplateId = res.favoriteTemplateId || '' + templateForm.value.favoriteFieldMapping = res.favoriteFieldMapping || '' templateForm.value.messageTemplateId = res.messageTemplateId || '' + templateForm.value.messageFieldMapping = res.messageFieldMapping || '' templateForm.value.dailyRecommendTemplateId = res.dailyRecommendTemplateId || '' + templateForm.value.dailyRecommendFieldMapping = res.dailyRecommendFieldMapping || '' } } catch (error) { console.error('加载模板配置失败:', error) @@ -734,9 +778,13 @@ const saveNotificationTemplates = async () => { token: templateForm.value.token || undefined, encodingAESKey: templateForm.value.encodingAESKey || undefined, unlockTemplateId: templateForm.value.unlockTemplateId || undefined, + unlockFieldMapping: templateForm.value.unlockFieldMapping || undefined, favoriteTemplateId: templateForm.value.favoriteTemplateId || undefined, + favoriteFieldMapping: templateForm.value.favoriteFieldMapping || undefined, messageTemplateId: templateForm.value.messageTemplateId || undefined, - dailyRecommendTemplateId: templateForm.value.dailyRecommendTemplateId || undefined + messageFieldMapping: templateForm.value.messageFieldMapping || undefined, + dailyRecommendTemplateId: templateForm.value.dailyRecommendTemplateId || undefined, + dailyRecommendFieldMapping: templateForm.value.dailyRecommendFieldMapping || undefined }) ElMessage.success('模板配置保存成功') } catch (error) { diff --git a/server/src/XiangYi.Application/Services/NotificationService.cs b/server/src/XiangYi.Application/Services/NotificationService.cs index 753487c..31b6135 100644 --- a/server/src/XiangYi.Application/Services/NotificationService.cs +++ b/server/src/XiangYi.Application/Services/NotificationService.cs @@ -544,6 +544,43 @@ public class NotificationService : INotificationService return false; } + // 从数据库读取字段映射 + var fieldMapping = await GetServiceAccountFieldMappingAsync(templateType); + Dictionary data; + + if (fieldMapping != null && fieldMapping.Count > 0) + { + // 使用后台配置的字段映射动态构建 + // 映射格式: {"thing1":"title","thing2":"name","time3":"time","thing4":"content","thing5":"remark"} + // 值映射: title->标题, name->名称, content->内容, time->时间, remark->备注 + data = new Dictionary(); + foreach (var (fieldName, valueKey) in fieldMapping) + { + var value = valueKey?.ToLower() switch + { + "title" => title, + "name" => name, + "content" => content, + "time" => time, + "remark" => "点击查看详情", + _ => valueKey ?? "" + }; + data[fieldName] = new TemplateDataItem { Value = value }; + } + } + else + { + // 兜底:使用传统的 first/keyword 格式 + data = new Dictionary + { + ["first"] = new TemplateDataItem { Value = title }, + ["keyword1"] = new TemplateDataItem { Value = name }, + ["keyword2"] = new TemplateDataItem { Value = content }, + ["keyword3"] = new TemplateDataItem { Value = time }, + ["remark"] = new TemplateDataItem { Value = "点击查看详情" } + }; + } + var request = new ServiceAccountTemplateMessageRequest { ToUser = serviceAccountOpenId, @@ -553,14 +590,7 @@ public class NotificationService : INotificationService AppId = _weChatOptions.MiniProgram.AppId, PagePath = page }, - Data = new Dictionary - { - ["first"] = new TemplateDataItem { Value = title }, - ["keyword1"] = new TemplateDataItem { Value = name }, - ["keyword2"] = new TemplateDataItem { Value = content }, - ["keyword3"] = new TemplateDataItem { Value = time }, - ["remark"] = new TemplateDataItem { Value = "点击查看详情" } - } + Data = data }; var success = await _weChatService.SendServiceAccountTemplateMessageAsync(request); @@ -576,6 +606,37 @@ public class NotificationService : INotificationService } } + /// + /// 获取服务号模板字段映射 + /// + private async Task?> GetServiceAccountFieldMappingAsync(NotificationTemplateType templateType) + { + string? configKey = templateType switch + { + NotificationTemplateType.Unlock => SystemConfigService.SaUnlockFieldMappingKey, + NotificationTemplateType.Favorite => SystemConfigService.SaFavoriteFieldMappingKey, + NotificationTemplateType.FirstMessage => SystemConfigService.SaMessageFieldMappingKey, + NotificationTemplateType.MessageReminder => SystemConfigService.SaMessageFieldMappingKey, + NotificationTemplateType.DailyRecommend => SystemConfigService.SaDailyRecommendFieldMappingKey, + _ => null + }; + + if (configKey == null) return null; + + var json = await _configService.GetConfigValueAsync(configKey); + if (string.IsNullOrEmpty(json)) return null; + + try + { + return System.Text.Json.JsonSerializer.Deserialize>(json); + } + catch + { + _logger.LogWarning("解析字段映射失败: Key={Key}, Value={Value}", configKey, json); + return null; + } + } + #endregion diff --git a/server/src/XiangYi.Application/Services/SystemConfigService.cs b/server/src/XiangYi.Application/Services/SystemConfigService.cs index 888ea75..b468092 100644 --- a/server/src/XiangYi.Application/Services/SystemConfigService.cs +++ b/server/src/XiangYi.Application/Services/SystemConfigService.cs @@ -101,9 +101,13 @@ public class SystemConfigService : ISystemConfigService public const string SaTokenKey = "sa_token"; public const string SaEncodingAesKeyKey = "sa_encoding_aes_key"; public const string SaUnlockTemplateIdKey = "sa_unlock_template_id"; + public const string SaUnlockFieldMappingKey = "sa_unlock_field_mapping"; public const string SaFavoriteTemplateIdKey = "sa_favorite_template_id"; + public const string SaFavoriteFieldMappingKey = "sa_favorite_field_mapping"; public const string SaMessageTemplateIdKey = "sa_message_template_id"; + public const string SaMessageFieldMappingKey = "sa_message_field_mapping"; public const string SaDailyRecommendTemplateIdKey = "sa_daily_recommend_template_id"; + public const string SaDailyRecommendFieldMappingKey = "sa_daily_recommend_field_mapping"; public SystemConfigService( IRepository configRepository, @@ -360,9 +364,13 @@ public class SystemConfigService : ISystemConfigService Token = await GetConfigValueAsync(SaTokenKey), EncodingAESKey = await GetConfigValueAsync(SaEncodingAesKeyKey), UnlockTemplateId = await GetConfigValueAsync(SaUnlockTemplateIdKey), + UnlockFieldMapping = await GetConfigValueAsync(SaUnlockFieldMappingKey), FavoriteTemplateId = await GetConfigValueAsync(SaFavoriteTemplateIdKey), + FavoriteFieldMapping = await GetConfigValueAsync(SaFavoriteFieldMappingKey), MessageTemplateId = await GetConfigValueAsync(SaMessageTemplateIdKey), - DailyRecommendTemplateId = await GetConfigValueAsync(SaDailyRecommendTemplateIdKey) + MessageFieldMapping = await GetConfigValueAsync(SaMessageFieldMappingKey), + DailyRecommendTemplateId = await GetConfigValueAsync(SaDailyRecommendTemplateIdKey), + DailyRecommendFieldMapping = await GetConfigValueAsync(SaDailyRecommendFieldMappingKey) }; } @@ -377,12 +385,20 @@ public class SystemConfigService : ISystemConfigService await SetConfigValueAsync(SaEncodingAesKeyKey, templates.EncodingAESKey, "服务号消息加解密密钥"); if (!string.IsNullOrEmpty(templates.UnlockTemplateId)) await SetConfigValueAsync(SaUnlockTemplateIdKey, templates.UnlockTemplateId, "服务号解锁通知模板ID"); + if (!string.IsNullOrEmpty(templates.UnlockFieldMapping)) + await SetConfigValueAsync(SaUnlockFieldMappingKey, templates.UnlockFieldMapping, "服务号解锁通知字段映射"); if (!string.IsNullOrEmpty(templates.FavoriteTemplateId)) await SetConfigValueAsync(SaFavoriteTemplateIdKey, templates.FavoriteTemplateId, "服务号收藏通知模板ID"); + if (!string.IsNullOrEmpty(templates.FavoriteFieldMapping)) + await SetConfigValueAsync(SaFavoriteFieldMappingKey, templates.FavoriteFieldMapping, "服务号收藏通知字段映射"); if (!string.IsNullOrEmpty(templates.MessageTemplateId)) await SetConfigValueAsync(SaMessageTemplateIdKey, templates.MessageTemplateId, "服务号消息通知模板ID"); + if (!string.IsNullOrEmpty(templates.MessageFieldMapping)) + await SetConfigValueAsync(SaMessageFieldMappingKey, templates.MessageFieldMapping, "服务号消息通知字段映射"); if (!string.IsNullOrEmpty(templates.DailyRecommendTemplateId)) await SetConfigValueAsync(SaDailyRecommendTemplateIdKey, templates.DailyRecommendTemplateId, "服务号每日推荐通知模板ID"); + if (!string.IsNullOrEmpty(templates.DailyRecommendFieldMapping)) + await SetConfigValueAsync(SaDailyRecommendFieldMappingKey, templates.DailyRecommendFieldMapping, "服务号每日推荐通知字段映射"); return true; } catch (Exception ex) @@ -439,18 +455,38 @@ public class NotificationTemplatesDto /// public string? UnlockTemplateId { get; set; } + /// + /// 解锁通知字段映射JSON,如 {"thing1":"title","thing2":"name","time3":"time"} + /// + public string? UnlockFieldMapping { get; set; } + /// /// 收藏通知模板ID /// public string? FavoriteTemplateId { get; set; } + /// + /// 收藏通知字段映射JSON + /// + public string? FavoriteFieldMapping { get; set; } + /// /// 消息通知模板ID(首次消息/未回复提醒共用) /// public string? MessageTemplateId { get; set; } + /// + /// 消息通知字段映射JSON + /// + public string? MessageFieldMapping { get; set; } + /// /// 每日推荐通知模板ID /// public string? DailyRecommendTemplateId { get; set; } + + /// + /// 每日推荐通知字段映射JSON + /// + public string? DailyRecommendFieldMapping { get; set; } }