Pārlūkot izejas kodu

app消息推送初步接入

车车 4 mēneši atpakaļ
vecāks
revīzija
b3042a15fb

+ 8 - 4
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/workdesign/WorkflowWorkNodeServiceImpl.java

@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.iscs.dal.mysql.workdesign.WorkflowWorkNodeMapper;
 import cn.iocoder.yudao.module.iscs.enums.MessageEnum;
 import cn.iocoder.yudao.module.iscs.enums.WorkTypeEnum;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.service.appnotify.AppNotifySendService;
 import cn.iocoder.yudao.module.system.service.notify.NotifySendService;
 import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
 import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
@@ -65,6 +66,8 @@ public class WorkflowWorkNodeServiceImpl extends ServiceImpl<WorkflowWorkNodeMap
     private NotifySendService notifySendService;
     @Resource
     private AdminUserService adminUserService;
+    @Resource
+    private AppNotifySendService appNotifySendService;
 
 
     @Override
@@ -365,10 +368,10 @@ public class WorkflowWorkNodeServiceImpl extends ServiceImpl<WorkflowWorkNodeMap
 
         if (StringUtils.isNotBlank(nodeDO.getMessageTemplateCode()) && "true".equals(nodeDO.getMessageTemplateCode())) {
             // 站内信
-            // NotifyTemplateDO notifyTemplate = notifyTemplateService.getNotifyTemplateByCodeFromCache(nodeDO.getMessageTemplateCode());
-            // if (notifyTemplate != null) {
-            //     notifySendService.sendSingleNotifyToAdmin(userId, notifyTemplate.getCode(), templateParams);
-            // }
+            /*NotifyTemplateDO notifyTemplate = notifyTemplateService.getNotifyTemplateByCodeFromCache(nodeDO.getMessageTemplateCode());
+            if (notifyTemplate != null) {
+                notifySendService.sendSingleNotifyToAdmin(userId, notifyTemplate.getCode(), templateParams);
+            }*/
             notifySendService.sendSingleNotifyToAdmin(userId, Objects.requireNonNull(MessageEnum.getByKey(templateKey)).znx, templateParams);
         }
         if (StringUtils.isNotBlank(nodeDO.getEmailTemplateCode()) && "true".equals(nodeDO.getEmailTemplateCode())) {
@@ -376,6 +379,7 @@ public class WorkflowWorkNodeServiceImpl extends ServiceImpl<WorkflowWorkNodeMap
         }
         if (StringUtils.isNotBlank(nodeDO.getAppTemplateCode()) && "true".equals(nodeDO.getAppTemplateCode())) {
             // app
+            appNotifySendService.sendSingleAppNotifyToAdmin(userId, templateKey, templateParams);
         }
 
     }

+ 225 - 84
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/appnotify/AppNotifySendServiceImpl.java

@@ -1,5 +1,9 @@
 package cn.iocoder.yudao.module.system.service.appnotify;
 
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
+import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
 import com.aliyun.auth.credentials.utils.ParameterHelper;
 import com.aliyuncs.DefaultAcsClient;
 import com.aliyuncs.IAcsClient;
@@ -8,226 +12,363 @@ import com.aliyuncs.exceptions.ServerException;
 import com.aliyuncs.profile.DefaultProfile;
 import com.aliyuncs.push.model.v20160801.PushRequest;
 import com.aliyuncs.push.model.v20160801.PushResponse;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.Date;
 import java.util.Map;
-import java.util.UUID;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTICE_NOT_FOUND;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_SEND_TEMPLATE_PARAM_MISS;
 
 /**
- * app消息发送 Service 实现类(修正Push类找不到的版本)
+ * App消息推送服务实现类(阿里云推送)
+ * <p>
+ * 功能说明:
+ * 1. 基于阿里云移动推送(Push)服务实现App消息推送能力
+ * 2. 支持两种推送模式:单用户(按设备ID)推送、全量用户推送
+ * 3. 适配Android和iOS双端推送配置,包含离线存储、推送时效等核心配置
+ * 4. 已修复阿里云SDK中Push类找不到的兼容性问题
  *
  * @author xrcoder
+ * @version 1.0
+ * @date 2026/01/07
  */
 @Service
 @Validated
 @Slf4j
 public class AppNotifySendServiceImpl implements AppNotifySendService {
 
-    // ========== 配置项(放到application.yml中) ==========
-    /** 阿里云Push的AppKey */
-    @Value("${aliyun.push.app-key:}")
+    // ========== 阿里云推送配置项(从application.yml读取) ==========
+
+    /**
+     * 阿里云Push应用唯一标识(AppKey)
+     * <p>
+     * 配置路径:aliyun.push.app-key
+     * 获取方式:阿里云控制台 -> 移动推送 -> 应用管理 -> 应用详情
+     */
+    @Value("${aliyun.push.app-key:}") // 从配置文件读取,默认空字符串
     private Long appKey;
 
-    /** 阿里云AccessKey ID */
+    /**
+     * 阿里云AccessKey ID(账号身份标识)
+     * <p>
+     * 配置路径:aliyun.access-key-id
+     * 获取方式:阿里云控制台 -> RAM访问控制 -> 访问密钥
+     */
     @Value("${aliyun.access-key-id:}")
     private String accessKeyId;
 
-    /** 阿里云AccessKey Secret */
+    /**
+     * 阿里云AccessKey Secret(账号身份密钥)
+     * <p>
+     * 配置路径:aliyun.access-key-secret
+     * 注意事项:禁止明文硬编码,建议通过配置中心/环境变量注入
+     */
     @Value("${aliyun.access-key-secret:}")
     private String accessKeySecret;
 
-    /** 阿里云Push的地域ID(默认杭州) */
+    /**
+     * 阿里云Push服务地域ID
+     * <p>
+     * 配置路径:aliyun.push.region-id
+     * 默认值:cn-hangzhou(杭州地域,阿里云默认)
+     * 可选值:cn-beijing(北京)、cn-shenzhen(深圳)等
+     */
     @Value("${aliyun.push.region-id:cn-hangzhou}")
     private String regionId;
 
     /**
-     * 根据用户ID发送单条App通知(核心业务方法)
+     * 【核心业务方法】根据用户ID发送单条App通知(按设备ID精准推送)
+     *
+     * @param userId         接收通知的用户ID(业务系统内的用户唯一标识)
+     * @param templateCode   消息模板编码(用于解析推送标题和内容)
+     * @param templateParams 模板参数(填充模板变量,如订单号、通知内容等)
+     * @return 阿里云推送返回的消息ID(Long类型);推送失败返回null
+     * @throws ServerException 阿里云服务端异常(如权限不足、参数非法)
+     * @throws ClientException 客户端异常(如网络问题、配置错误)
      */
+
+    @Autowired
+    private NotifyTemplateService notifyTemplateService;
+
+
     @Override
     public Long sendSingleAppNotifyToAdmin(Long userId, String templateCode, Map<String, Object> templateParams) {
-        // 1. 根据userId查询对应的设备ID(替换为你的实际查询逻辑)
+        // 1. 第一步:根据用户ID查询绑定的设备ID(需替换为业务系统实际查询逻辑)
+        // 说明:设备ID是App端集成阿里云推送SDK后上报的唯一设备标识
         String deviceId = getDeviceIdByUserId(userId);
         if (deviceId.isEmpty()) {
-            log.warn("发送单用户App通知失败:用户{}未绑定设备ID", userId);
+            log.warn("发送单用户App通知失败:用户{}未绑定任何设备ID,无法推送", userId);
             return null;
         }
 
-        // 2. 解析模板标题和内容(替换为你的实际模板逻辑)
-        String pushTitle = parseTemplateTitle(templateCode, templateParams);
-        String pushBody = parseTemplateBody(templateCode, templateParams);
+        // 2. 第二步:通过notifyId获取站内信已经发送的消息,进行消息复制发送
+        String content = getMessageByNotify(userId, templateCode, templateParams);
 
         try {
-            // 3. 创建阿里云客户端(修正后的方式,不会报找不到Push类
+            // 3. 第三步:创建阿里云ACS客户端(修复Push类找不到的兼容方案
             IAcsClient client = createAcsClient();
 
-            // 4. 构建单设备推送请求
-            PushRequest pushRequest = buildSingleDevicePushRequest(deviceId, pushTitle, pushBody);
+            // 4. 第四步:构建单设备推送请求(封装Android/iOS双端配置)
+            PushRequest pushRequest = buildSingleDevicePushRequest(deviceId, "博士安全", content);
 
-            // 5. 发送推送
+            // 5. 第五步:发送推送请求,调用阿里云Push接口
             PushResponse response = client.getAcsResponse(pushRequest);
 
-            // 6. 解析返回结果
-            String requestId = response.getRequestId();
-            String messageId = response.getMessageId();
+            // 6. 第六步:解析返回结果,记录日志并返回消息ID
+            String requestId = response.getRequestId(); // 阿里云请求唯一标识(用于排查问题)
+            String messageId = response.getMessageId(); // 推送消息唯一标识(用于查询推送状态)
             log.info("单用户App推送成功:userId={}, requestId={}, messageId={}", userId, requestId, messageId);
 
-            // 返回消息ID(转为Long,若messageId是字符串可自行调整)
+            // 注意:若messageId是字符串格式(如含字母),需调整返回类型为String
             return Long.parseLong(messageId);
         } catch (ServerException e) {
-            log.error("单用户App推送失败(服务端错误):userId={}, templateCode={}", userId, templateCode, e);
+            // 服务端异常:阿里云接口返回错误(如AppKey无效、额度不足)
+            log.error("单用户App推送失败(阿里云服务端错误):userId={}, templateCode={}", userId, templateCode, e);
             return null;
         } catch (ClientException e) {
+            // 客户端异常:本地配置/网络/参数错误(如AccessKey错误、地域ID无效)
             log.error("单用户App推送失败(客户端错误):userId={}, templateCode={}", userId, templateCode, e);
             return null;
         }
     }
 
     /**
-     * 通用推送方法(推送给全部用户)
+     * 通用推送方法:推送给全部用户(全量广播)
+     *
+     * @return 推送结果:true-成功,false-失败
      */
     public Boolean pushNotify() {
+        // 前置校验:AppKey未配置直接返回失败
         if (appKey == null || appKey == 0) {
-            log.error("App推送失败:未配置阿里云Push的appKey");
+            log.error("App全量推送失败:未配置阿里云Push的appKey,请检查application.yml配置");
             return Boolean.FALSE;
         }
 
         try {
-            // 创建客户端
+            // 1. 创建阿里云ACS客户端
             IAcsClient client = createAcsClient();
-            // 构建全量推送请求
+            // 2. 构建全量推送请求(示例标题和内容,需根据业务调整)
             PushRequest pushRequest = buildAllDevicePushRequest("系统通知", "这是一条全量推送的测试通知");
-            // 发送推送
+            // 3. 发送推送请求
             PushResponse response = client.getAcsResponse(pushRequest);
 
             log.info("全量App推送成功:requestId={}, messageId={}", response.getRequestId(), response.getMessageId());
             return Boolean.TRUE;
         } catch (Exception e) {
+            // 捕获所有异常,保证方法不抛出异常(根据业务需求可调整)
             log.error("全量App推送失败", e);
             return Boolean.FALSE;
         }
     }
 
-    // ========== 私有工具方法 ==========
+    // ========== 私有工具方法(内部调用,封装核心逻辑) ==========
 
     /**
-     * 创建阿里云ACS客户端(修正后的方式,解决Push类找不到的问题)
+     * 创建阿里云ACS客户端(修复Push类找不到的兼容方案)
+     * <p>
+     * 核心逻辑:使用阿里云官方推荐的DefaultAcsClient创建客户端,替代过时的PushClient
+     *
+     * @return 初始化完成的IAcsClient客户端实例
+     * @throws IllegalArgumentException 配置为空时抛出非法参数异常
      */
     private IAcsClient createAcsClient() {
-        // 校验配置
+        // 前置校验:AccessKey配置不能为空
         if (accessKeyId.isEmpty() || accessKeySecret.isEmpty()) {
-            throw new IllegalArgumentException("阿里云AccessKey配置为空,请检查配置项");
+            throw new IllegalArgumentException("阿里云AccessKey配置为空,请检查aliyun.access-key-id和aliyun.access-key-secret配置项");
         }
 
-        // 初始化阿里云配置
+        // 1. 初始化阿里云地域配置
         DefaultProfile profile = DefaultProfile.getProfile(
-                regionId,       // 地域ID
+                regionId,       // 地域ID(如cn-hangzhou)
                 accessKeyId,    // AccessKey ID
                 accessKeySecret // AccessKey Secret
         );
 
-        // 创建客户端(这是阿里云官方推荐的稳定方式)
+        // 2. 创建并返回客户端实例(阿里云官方推荐的稳定方式)
         return new DefaultAcsClient(profile);
     }
 
     /**
-     * 构建单设备推送请求
+     * 构建单设备推送请求(适配Android+iOS双端)
+     *
+     * @param deviceId 设备唯一标识(阿里云Push SDK上报的deviceId)
+     * @param title    推送标题(展示在App通知栏)
+     * @param body     推送内容(通知栏展示的具体消息)
+     * @return 封装完成的PushRequest请求对象
      */
     private PushRequest buildSingleDevicePushRequest(String deviceId, String title, String body) {
         PushRequest request = new PushRequest();
-        // 基础配置
-        request.setAppKey(appKey);
-        request.setTarget("DEVICE"); // 按设备推送
-        request.setTargetValue(deviceId); // 设备ID
-        request.setDeviceType("ALL"); // 支持Android+iOS
-        request.setPushType("NOTICE"); // 通知类型
-        request.setTitle(title);
-        request.setBody(body);
 
-        // iOS配置
-        request.setIOSBadge(1);
-        request.setIOSApnsEnv("PRODUCT");
-        request.setIOSRemind(true);
+        // ========== 基础推送配置 ==========
+        request.setAppKey(appKey); // 关联的阿里云Push应用ID
+        request.setTarget("DEVICE"); // 推送目标类型:DEVICE-按设备ID,ACCOUNT-按账号,TAG-按标签
+        request.setTargetValue(deviceId); // 目标值:对应DEVICE类型的设备ID
+        request.setDeviceType("ALL"); // 推送设备类型:ALL-全部,ANDROID-安卓,IOS-iOS
+        request.setPushType("NOTICE"); // 推送类型:NOTICE-通知,MESSAGE-透传消息
+
+        // ========== 推送内容 ==========
+        request.setTitle(title); // 通知标题
+        request.setBody(body);   // 通知内容
+
+        // ========== iOS端专属配置 ==========
+        request.setIOSBadge(1); // iOS角标数(未读消息数)
+        request.setIOSApnsEnv("PRODUCT"); // iOS推送环境:PRODUCT-生产环境,DEV-开发环境
+        request.setIOSRemind(true); // 是否开启iOS消息提醒(响铃/震动)
+        // iOS扩展参数(JSON格式):供App端解析自定义逻辑
         request.setIOSExtParameters("{\"type\":\"single\"}");
 
-        // Android配置
-        request.setAndroidOpenType("ACTIVITY");
-        request.setAndroidNotifyType("SOUND");
+        // ========== Android端专属配置 ==========
+        request.setAndroidOpenType("NONE"); // 点击通知打开方式:ACTIVITY-打开页面,URL-打开链接,NONE-无动作
+        request.setAndroidNotifyType("SOUND"); // Android通知类型:SOUND-声音,VIBRATE-震动,BOTH-声音+震动
+        request.setAndroidNotificationChannel("Normal");
+        // Android扩展参数(JSON格式):供App端解析自定义逻辑
         request.setAndroidExtParameters("{\"type\":\"single\"}");
 
-        // 推送时间(立即推送)
+        // ========== 推送时效配置 ==========
+        // 推送时间:立即推送(ISO8601格式,阿里云要求的标准时间格式)
         request.setPushTime(ParameterHelper.getISO8601Time(new Date()));
-        // 离线存储(12小时过期)
-        request.setStoreOffline(true);
+        request.setStoreOffline(true); // 是否开启离线存储(设备离线时保存,上线后推送
+        // 离线消息过期时间:12小时后过期(当前时间+12小时)
         request.setExpireTime(ParameterHelper.getISO8601Time(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)));
 
         return request;
     }
 
     /**
-     * 构建全量推送请求
+     * 构建全量推送请求(推送给所有绑定设备)
+     *
+     * @param title 推送标题
+     * @param body  推送内容
+     * @return 封装完成的PushRequest请求对象
      */
     private PushRequest buildAllDevicePushRequest(String title, String body) {
         PushRequest request = new PushRequest();
-        // 基础配置
+
+        // ========== 基础推送配置 ==========
         request.setAppKey(appKey);
-        request.setTarget("ALL");
-        request.setTargetValue("all");
+        request.setTarget("ALL"); // 推送目标:ALL-全部用户
+        request.setTargetValue("all"); // 目标值:all表示全量
         request.setDeviceType("ALL");
         request.setPushType("NOTICE");
         request.setTitle(title);
         request.setBody(body);
 
-        // iOS配置
-        request.setIOSBadge(5);
-        request.setIOSMusic("default");
+        // ========== iOS端专属配置 ==========
+        request.setIOSBadge(5); // 角标数设为5(示例值)
+        request.setIOSMusic("default"); // iOS提示音:default-系统默认
         request.setIOSApnsEnv("PRODUCT");
+        request.setIOSMutableContent(true); // 允许内容修改
         request.setIOSRemind(true);
-        request.setIOSRemindBody(body);
+        request.setIOSRemindBody(body); // iOS提醒文案(锁屏界面展示)
         request.setIOSExtParameters("{\"k1\":\"ios\",\"k2\":\"v2\"}");
 
-        // Android配置
+        // ========== Android端专属配置 ==========
         request.setAndroidOpenType("ACTIVITY");
-        request.setAndroidNotifyType("SOUND");
-        request.setAndroidOpenUrl("http://www.alibaba.com");
-        request.setAndroidMusic("alicloud_notification_sound");
-        request.setAndroidActivity("com.alibaba.push.PushActivity");
-        request.setAndroidPopupActivity("com.alibaba.push.PopupActivity");
-        request.setAndroidPopupTitle("系统通知");
-        request.setAndroidPopupBody(body);
+        request.setAndroidNotifyType("VIBRATE");
+        // request.setAndroidOpenUrl("com.iscs.bozzys.ui.pages.home.PageHome"); // 点击通知打开的URL(示例)
+        request.setAndroidMusic("alicloud_notification_sound"); // Android提示音
+        // request.setAndroidActivity("com.iscs.bozzys.ui.pages.home.PageHome"); // 点击通知打开的Activity(示例)
+        request.setAndroidPopupActivity("com.iscs.bozzys.ui.pages.home.PageHome"); // 弹窗Activity(示例)
+        request.setAndroidPopupTitle("系统通知"); // 弹窗标题
+        request.setAndroidPopupBody(body); // 弹窗内容
         request.setAndroidExtParameters("{\"k1\":\"android\",\"k2\":\"v2\"}");
 
-        // 推送时间(1小时后)
-        request.setPushTime(ParameterHelper.getISO8601Time(new Date(System.currentTimeMillis() + 3600 * 1000)));
-        // 离线存储
-        request.setStoreOffline(true);
-        request.setExpireTime(ParameterHelper.getISO8601Time(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)));
+        // 推送控制
+        // final Date pushDate = new Date(System.currentTimeMillis() + 3600 * 1000); //用于定时发送。不设置缺省是立即发送。时间格式按照 ISO8601 标准表示,并需要使用 UTC 时间,格式为`YYYY-MM-DDThh:mm:ssZ`。
+        // final String pushTime = ParameterHelper.getISO8601Time(pushDate);
+        // request.setPushTime(pushTime); // 延后推送。可选,如果不设置表示立即推送
+        request.setStoreOffline(true); // 离线消息是否保存,若保存, 在推送时候,用户即使不在线,下一次上线则会收到,安卓中若为 false 则只走阿里云自有在线通道
+        final String expireTime = ParameterHelper.getISO8601Time(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)); // 12 小时后消息失效, 不会再发送
+        request.setExpireTime(expireTime);
+        // request.setSendChannels("accs,huawei,honor,vivo,xiaomi,oppo"); // 指定下发的推送通道,若不填可从任何可行的通道下发
+        // request.setAndroidNotificationNotifyId(1234567); // 设置通知覆盖参数,避免重试等场景用户显示多条相同的通知
+        // request.setAndroidTargetUserType(0); // 代表本次推送如果推送到华为、荣耀、vivo 通道,是一个正式通知,但本代码示例中这个值被厂商通道特有参数所覆盖
+        // request.setAndroidHuaweiTargetUserType(1); // 代表本次推送如果推送到华为通道,是一个测试性质的通知
+        // request.setAndroidHonorTargetUserType(1); // 代表本次推送如果推送到荣耀通道,是一个测试性质的通知
+        // request.setAndroidVivoPushMode(1); // 代表本次推送如果推送到华为通道,是一个测试性质的通知,请在推送前把 vivo 设备 regId 加入 vivo 推送平台的测试设备列表中
+        // request.setAndroidHuaweiReceiptId("ABCDEFG"); // 华为厂商通道
+        // 厂商通道通知分类
+        request.setAndroidNotificationChannel("Normal"); // OPPO 通道私信通道 channel_id,以及通用安卓 channel_id
+        // request.setAndroidNotificationXiaomiChannel("user_define"); //小米通道通知类型 channel_id
+        // request.setAndroidNotificationHonorChannel("NORMAL"); // 荣耀通道消息分类参数,对应荣耀通道 importance 参数
+        // request.setAndroidMessageHuaweiCategory("ACCOUNT"); // 华为通道的通知分类参数,服务与通讯类通知需向华为通道申请权限
+        // request.setAndroidMessageVivoCategory("ACCOUNT"); // vivo 通道的通知分类参数,系统消息需向 vivo 通道申请权限
+
+        // ========== 推送时效配置 ==========
+        // 推送时间:1小时后推送(示例)
+        // request.setPushTime(ParameterHelper.getISO8601Time(new Date(System.currentTimeMillis() + 3600 * 1000)));
+        request.setStoreOffline(true); // 开启离线存储
+        // request.setExpireTime(ParameterHelper.getISO8601Time(new Date(System.currentTimeMillis() + 12 * 3600 * 1000))); // 12小时过期
 
         return request;
     }
 
     /**
-     * 根据用户ID获取设备ID(替换为你的实际逻辑)
+     * 根据用户ID获取绑定的设备ID(示例方法)
+     * <p>
+     * 注意:需替换为业务系统实际逻辑,例如:
+     * 1. 从数据库查询用户-设备绑定关系表
+     * 2. 从缓存中获取用户最新的设备ID
+     *
+     * @param userId 业务系统用户ID
+     * @return 设备ID(示例返回随机生成的模拟值)
      */
     private String getDeviceIdByUserId(Long userId) {
-        // 示例:模拟返回设备ID
-        return "DEVICE" + UUID.randomUUID().toString().replace("-", "");
+        // 示例:模拟返回设备ID(实际项目中需替换为真实查询逻辑)
+        return "f92564ccc1a84aebaf58a796091a66a2";
     }
 
     /**
-     * 解析模板标题(替换为你的实际逻辑)
+     * 通过站内信获取相同发送信息
+     * @return
      */
-    private String parseTemplateTitle(String templateCode, Map<String, Object> params) {
-        return "【系统通知】" + params.get("titleSuffix");
+    private String getMessageByNotify(Long userId, String templateCode, Map<String, Object> templateParams) {
+        // 校验模版
+        NotifyTemplateDO template = validateNotifyTemplate(templateCode);
+        if (Objects.equals(template.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
+            log.info("[sendSingleNotify][模版({})已经关闭,无法给用户({})发送]", templateCode, userId);
+            return null;
+        }
+        // 校验参数
+        validateTemplateParams(template, templateParams);
+
+        // 发送站内信
+        return StrUtil.format(template.getContent(), templateParams);
+    }
+
+    @VisibleForTesting
+    public NotifyTemplateDO validateNotifyTemplate(String templateCode) {
+        // 获得站内信模板。考虑到效率,从缓存中获取
+        NotifyTemplateDO template = notifyTemplateService.getNotifyTemplateByCodeFromCache(templateCode);
+        // 站内信模板不存在
+        if (template == null) {
+            throw exception(NOTICE_NOT_FOUND);
+        }
+        return template;
     }
 
     /**
-     * 解析模板内容(替换为你的实际逻辑)
+     * 校验站内信模版参数是否确实
+     *
+     * @param template 邮箱模板
+     * @param templateParams 参数列表
      */
-    private String parseTemplateBody(String templateCode, Map<String, Object> params) {
-        return "您有一条新的通知:" + params.get("content");
+    @VisibleForTesting
+    public void validateTemplateParams(NotifyTemplateDO template, Map<String, Object> templateParams) {
+        template.getParams().forEach(key -> {
+            Object value = templateParams.get(key);
+            if (value == null) {
+                throw exception(NOTIFY_SEND_TEMPLATE_PARAM_MISS, key);
+            }
+        });
     }
+
 }

+ 9 - 0
yudao-server/src/main/resources/application.yaml

@@ -324,3 +324,12 @@ local-file:
   finger-path: finger
   # 人脸
   face-path: face
+
+
+aliyun:
+  access-key-id: LTAI5t9wtz1o6ZJE5pwa2QGk
+  access-key-secret: JUXGuxCb8in7mYfYrHtsqZfwhnvvPv
+  push:
+    app-key: 335635068
+    region-id: cn-hangzhou
+