1. 消息对象定义
1.1 通用消息对象定义
package com.yj.notice.message; import com.yj.commons.tools.utils.DateUtils; import com.yj.commons.tools.utils.StringUtil; import com.yj.notice.costant.NoticeMethodEnum; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date;/** * @author : dyg * @className : SmsMessageDTO * @description : SMS短信平台 实体类 * @date : 2023/8/30 10:35 */ @AllArgsConstructor @NoArgsConstructor @Builder @Data public class NoticeMessage implements Serializable { private static final long serialVersionUID = 5081758462088563857L;
/**
* 消息类型
**/
private String messageType; /** * 通知消息类型 */ protected NoticeMethodEnum noticeMethod; /** * 接收者-手机号码 */ protected String receiverPhone; protected String receiverId; /** * 接收者-用户名 */ protected String receiverUserName; /** * 消息内容 */ protected String content; /** * 消息内容 */ protected String title; /** * 消息时间 */ protected String time; /** * 发送结果 */ protected String result; /** * 错误信息 */ protected String error; public String wrapperMessage() { StringBuilder sub = new StringBuilder(); String time = StringUtil.isEmpty(this.getTime()) ? format(new Date(),"yyyy-MM-dd HH:mm:ss") : this.getTime(); sub.append("消息标题: ").append(this.getTitle()).append("\n" ) .append("消息内容: ").append(this.getContent()).append("\n") .append("消息时间: ").append(time).append("\n" ); return sub.toString(); }
public static String format(Date date, String pattern) {
if (date != null) {
SimpleDateFormat df = new SimpleDateFormat(pattern);
return df.format(date);
}
return null;
}
}
1.2 告警消息对象定义
package com.yj.notice.message; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author : dyg * @className : AlarmMessage * @description : 告警信息 * @date : 2023/9/5 11:19 */ @Data @AllArgsConstructor @NoArgsConstructor public class AlarmMessage extends NoticeMessage implements Serializable { private static final long serialVersionUID = -8390792683552907827L; /** * 告警级别 */ protected String level; /** * 告警类型 */ protected String type; /** * 告警备注 */ protected String remark; public String wrapperMessage() { if(StringUtil.isEmpty(this.getTime()) && StringUtil.isEmpty(this.getLevel()) && StringUtil.isEmpty(this.getType())&& StringUtil.isEmpty(this.getRemark())) { return this.getContent(); } StringBuilder sub = new StringBuilder(); sub.append("告警时间: ").append(this.getTime()).append("\n") .append("告警级别: ").append(this.getLevel()).append("\n") .append("告警类别: ").append(this.getType()).append("\n") .append("告警消息: ").append(this.getContent()).append("\n") .append("备注信息: ").append(this.getRemark()); return sub.toString(); } }
1.3 邮件消息对象定义
package com.yj.notice.message; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.File; import java.io.Serializable; /** * @author : dyg * @className : MailMessage * @description : 描述说明该类的功能 * @date : 2023/9/5 14:33 */ @Data @AllArgsConstructor @NoArgsConstructor public class MailMessage extends AlarmMessage implements Serializable { private static final long serialVersionUID = 4370431527898082801L; /** * 附件 */ private File attachFile; /** * html内容 */ private String html; }
1.4 消息发送类型枚举定义
package com.yj.notice.costant; import lombok.AllArgsConstructor; import lombok.Getter; /** * 通知方式 * * @author changXT * @date 2022-8-10 **/ @AllArgsConstructor @Getter public enum NoticeMethodEnum { WECHAT("微信", "WECHAT"), EMAIL("邮件", "EMAIL"), TENCENT_SMS("腾讯短信", "TENCENT_SMS"), IN_MSG("站内消息", "IN_MSG"), DINGTALK("钉钉", "DINGTALK"); private String cnName; private String name;
public static NoticeMethodEnum getEnum(String name) { for (NoticeMethodEnum noticeMethodEnum : NoticeMethodEnum.values()) { if (name.equals(noticeMethodEnum.name())) { return noticeMethodEnum; } } return null; } }
2. 消息发送接口定义
2.1 消息发送接口定义
package com.yj.notice.service; import com.yj.notice.MessageSenderManager; import com.yj.notice.message.NoticeMessage; import org.springframework.beans.factory.InitializingBean; /** * @author : dyg * @className : MessageService * @description : 消息发送 * @date : 2023/8/30 10:34 */ public interface MessageService<T extends NoticeMessage> extends InitializingBean { /** * 发送消息 * @param message * @return */ String send(NoticeMessage message); /** * 获取发送方法 * * @return 发送方法 */ String getNoticeMethod(); @Override default void afterPropertiesSet() { MessageSenderManager.registrySender(getNoticeMethod(), this); } }
2.2 消息发送收集器定义
package com.yj.notice; import com.yj.notice.message.NoticeMessage; import com.yj.notice.costant.NoticeMethodEnum; import com.yj.notice.service.MessageService; import org.springframework.stereotype.Component; import java.util.concurrent.ConcurrentHashMap; /** * 消息发送者管理器 * * @author donglanlan * @date 2021/7/22 2:33 下午 **/ @Component public class MessageSenderManager { private static final ConcurrentHashMap<String, MessageService<? extends NoticeMessage>> SENDER_MAP = new ConcurrentHashMap<>(); public static void registrySender(String sendMethod, MessageService<? extends NoticeMessage> messageSender) { SENDER_MAP.put(sendMethod, messageSender); } public MessageService getMessageSender(NoticeMessage messsage) { if (messsage.getNoticeMethod() == null) { throw new RuntimeException("没有指定消息的发送方式!"); } return getMessageSender(messsage.getNoticeMethod()); } public MessageService getMessageSender(NoticeMethodEnum methodEnum) { return SENDER_MAP.get(methodEnum.getName()); } }
3. 微信公众号消息发送实现类
3.1 实现类定义
package com.yj.notice.service.impl; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import com.yj.commons.tools.utils.DateUtils; import com.yj.commons.tools.utils.JsonUtil; import com.yj.commons.tools.utils.StringUtil; import com.yj.notice.message.NoticeMessage; import com.yj.notice.costant.NoticeMethodEnum; import com.yj.notice.message.AlarmMessage; import com.yj.notice.service.MessageService; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.util.*; import static com.yj.commons.tools.utils.DateUtils.DATE_TIME_PATTERN; /** * @author : dyg * @className : WxMessageServiceImpl * @description : 描述说明该类的功能 * @date : 2023/9/5 9:35 */ @Service("WechatMessageService") @Slf4j @Data public class WechatMessageServiceImpl implements MessageService { /** * 账号app_id */ @Value(value = "${notice.wechat.bk_app_id}") private String bkAppKey; /** * 账户密钥 */ @Value(value = "${notice.wechat.bk_app_secret}") private String bkAppSecret; /** * 告警消息模板id */ @Value(value = "${notice.wechat.alarm_template_id}") private String alarmTemplateId; /** * 普通消息模板id */ @Value(value = "${notice.wechat.common_template_id}") private String commonTemplateId; /** * 获取token * "+ appId +"&secret=" + appIdSecret */ @Value(value = "${notice.wechat.token_uri}") private String tokenUri; /** * + accessToken; */ @Value(value = "${notice.wechat.user_list_uri}") private String userListUri; /** * + accessToken; */ @Value(value = "${notice.wechat.send_message_uri}") private String sendMessageUri; @Autowired RestTemplate restTemplate; /** * 用户token */ private String enterpriseToken = null; private Long tokenFreshTimeSt = 0L; @Override public String send(NoticeMessage message) { String result = null; try { result = this.sendMessage(message); }catch (Exception e){ log.error(e.getMessage()); result = e.getMessage()+ " " + e; } if (com.yj.cmp.commons.util.StringUtil.isNotEmpty(result)) { log.error(result); } else { result = "发送成功"; } return result; } @Override public String getNoticeMethod() { return NoticeMethodEnum.WECHAT.getName(); } /** * 获取或者刷新token */ private void getOrRefreshToken() { try { String requestUrl = this.tokenUri + this.bkAppKey +"&secret=" + this.bkAppSecret; String res = HttpUtil.get(requestUrl); JSONObject jsonObject = JSONObject.parseObject(res); String accessToken = jsonObject.getString("access_token"); this.enterpriseToken = accessToken; this.tokenFreshTimeSt = System.currentTimeMillis()/1000; } catch (Exception e) { log.error("---获取token出现异常{} {} ",e.getMessage(),e); } } /** * 获取用户列表openid */ public void getUserList(){ RestTemplate restTemplate = new RestTemplate(); String requestUrl = this.userListUri+ this.enterpriseToken; ResponseEntity<String> response = restTemplate.postForEntity(requestUrl, null, String.class); log.info("结果是: {}",response.getBody()); com.alibaba.fastjson.JSONObject result = com.alibaba.fastjson.JSONObject.parseObject(response.getBody()); com.alibaba.fastjson.JSONArray openIdJsonArray = result.getJSONObject("data").getJSONArray("openid"); Iterator iterator = openIdJsonArray.iterator(); if (iterator.hasNext()){ log.debug("用户openid:"+iterator.next()); } } @Data public class WeChatTemplateMsg { /** * 消息 */ private String value; /** * 消息颜色 */ private String color; public WeChatTemplateMsg(String value) { this.value = value; this.color = "#173177"; } public WeChatTemplateMsg(String value, String color) { this.value = value; this.color = color; } } /** * 获取用户id * @param alarmMessage * @return */ private JSONObject getWechatUserId(NoticeMessage alarmMessage){ JSONObject result = new JSONObject(); String openId = alarmMessage.getReceiverId(); if(StringUtil.isEmpty(openId)){ // todo 根据手机号码获取微信id --对应数据估计得手动维护 String receiverPhone = alarmMessage.getReceiverPhone(); if(StringUtil.isEmpty(receiverPhone)){ result.put("message","消息接收者手机号码+微信id都为空,消息无法发送"); } openId = getWechatUserId(receiverPhone); } result.put("userId",openId); return result; } /** * 发送消息 * @param noticeMessage * @return */ public String sendMessage(NoticeMessage noticeMessage){ String result = null; // 模板参数 Map<String, WeChatTemplateMsg> sendMag = new HashMap<String, WeChatTemplateMsg>(); // openId代表一个唯一微信用户,即微信消息的接收人 JSONObject wechatUserId = getWechatUserId(noticeMessage); String openId = null; if(wechatUserId.containsKey("message")){ String message = wechatUserId.getString("message"); log.error(message); return message; }else{ Object userId = wechatUserId.get("userId"); if(Objects.isNull(userId)){ String message = "未能根据手机号码"+noticeMessage.getReceiverPhone()+"成功获取用户的微信id"; return message; } openId = wechatUserId.getString("userId"); } // 公众号的模板id(也有相应的接口可以查询到) validateToken(); String requestUrl = this.sendMessageUri + this.enterpriseToken; //拼接base参数 Map<String, Object> sendBody = new HashMap<>(); sendBody.put("touser", openId); sendBody.put("data", sendMag); if(noticeMessage instanceof AlarmMessage){ AlarmMessage alarmMessage = (AlarmMessage)noticeMessage; sendMag.put("message", new WeChatTemplateMsg(alarmMessage.getContent())); sendMag.put("time",new WeChatTemplateMsg(alarmMessage.getTime())); sendMag.put("level",new WeChatTemplateMsg(alarmMessage.getLevel(),"#FF69B4" )); sendMag.put("type",new WeChatTemplateMsg(alarmMessage.getType() ,"#173177")); sendMag.put("remark",new WeChatTemplateMsg(alarmMessage.getRemark(),"#173177")); sendBody.put("template_id", this.alarmTemplateId); }else{ sendMag.put("content", new WeChatTemplateMsg(noticeMessage.getContent())); sendMag.put("title",new WeChatTemplateMsg(noticeMessage.getTitle())); String time = StringUtil.isEmpty(noticeMessage.getTime()) ? DateUtils.format(new Date(),DATE_TIME_PATTERN) : noticeMessage.getTime(); sendMag.put("time",new WeChatTemplateMsg(time,"#FF69B4")); sendBody.put("template_id", this.commonTemplateId); } try { // 1.创建httpclient对象 CloseableHttpClient client = HttpClients.createDefault(); // 2.创建post对象 HttpPost post = new HttpPost(requestUrl); StringEntity postingString = new StringEntity(JsonUtil.entityToString(sendBody), "utf-8"); post.setEntity(postingString); // 3.执行post方法:得到结果 CloseableHttpResponse response = client.execute(post); // 4.处理结果 // 1.得到状态码 int statusCode = response.getStatusLine().getStatusCode(); log.info("----http code : {}", statusCode); if (statusCode == 200) { // 2.得到实体内容 org.apache.http.HttpEntity entity = response.getEntity(); String content = EntityUtils.toString(entity, "utf-8"); JSONObject jsonObject = JsonUtil.StringToEntity(content, JSONObject.class); String messageCode = jsonObject.getString("errcode"); String msgId = jsonObject.getString("msgid"); result = "messageCode : " + messageCode + ", msgId: " +msgId; } // 5.关闭连接 client.close(); } catch (IOException e) { log.error("------exception : {} {} ",e.getMessage(),e); } return result; } /** * 根据手机号码获取用户id * @param receiverPhone * @return */ private String getWechatUserId(String receiverPhone) { String userId = null; return userId; } /** * 验证并刷新token */ private void validateToken() { if (com.yj.cmp.commons.util.StringUtil.isEmpty(this.enterpriseToken)) { this.getOrRefreshToken(); } Long now = System.currentTimeMillis()/1000; if(this.tokenFreshTimeSt == 0L){ this.getOrRefreshToken(); }else{ Long diff = (now - tokenFreshTimeSt)/60; if(diff > 600){ // 超过十分钟重新获取一下 this.getOrRefreshToken(); } } } }
3.2 配置参数
# 消息推送相关 notice: # 微信消息相关 wechat: bk_app_id: xxx bk_app_secret: xxx alarm_template_id: xxx common_template_id: xxxxxh token_uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid= user_list_uri: https://api.weixin.qq.com/cgi-bin/user/get?access_token= send_message_uri: https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=
3.3 消息模板
4. 钉钉群消息发送实现类
4.1 引入依赖
<!-- 钉钉对接机器人 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>dingtalk</artifactId> <version>1.5.24</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>alibaba-dingtalk-service-sdk</artifactId> <version>2.0.0</version> </dependency>
4.2 实现类定义
package com.yj.notice.service.impl; import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.request.OapiGettokenRequest; import com.dingtalk.api.response.OapiGettokenResponse; import com.yj.cmp.commons.util.StringUtil; import com.yj.commons.tools.utils.JsonUtil; import com.yj.notice.message.AlarmMessage; import com.yj.notice.message.NoticeMessage; import com.yj.notice.costant.NoticeMethodEnum; import com.yj.notice.service.MessageService; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.aliyun.dingtalkrobot_1_0.Client; import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOHeaders; import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTORequest; import com.aliyun.tea.TeaException; import com.aliyun.teaopenapi.models.Config; import com.aliyun.teautil.Common; import com.aliyun.teautil.models.RuntimeOptions; import com.dingtalk.api.request.OapiV2UserGetbymobileRequest; import com.dingtalk.api.response.OapiV2UserGetbymobileResponse; import java.util.Arrays; import java.util.Map; /** * @author : dyg * @className : DingMessageServiceImpl * @description : 钉钉消息发送 * @date : 2023/9/4 17:14 */ @Service("DingTalkMessageService") @Slf4j @Data public class DingTalkMessageServiceImpl implements MessageService { @Value(value = "${notice.dingtalk.token_url}") private String tokenUrl; @Value(value = "${notice.dingtalk.get_user_by_phone_url}") private String getUserByPhoneUrl; @Value(value = "${notice.dingtalk.bk_app_key}") private String bkAppKey; @Value(value = "${notice.dingtalk.bk_app_secret}") private String bkAppSecret; /** * token 数据 */ private String enterpriseToken = ""; /** * 获取/刷新token时间戳 */ private Long tokenFreshTimeSt = 0L; @Override public String send(NoticeMessage message) { String result = null; try { String receiverId = message.getReceiverId(); if (StringUtil.isNotEmpty(receiverId)) { // 用户id不为空根据id发送 result = this.sendDingTalkNotify(receiverId, message); } else { // 否则根据手机号码查询用户 String receiverPhone = message.getReceiverPhone(); if (StringUtil.isNotEmpty(receiverPhone)) { receiverId = this.getDingdingUserIdByPhone(receiverPhone); if (StringUtil.isNotEmpty(receiverId)) { result = this.sendDingTalkNotify(receiverId, message); } else { result = "根据接收者用户手机号码获取的用户id都为空,无法完成消息发送需求"; } } else { result = "接收者用户id和手机号码都为空,无法完成消息发送需求"; } } }catch (Exception e){ log.error(e.getMessage()); result = e.getMessage()+ " " + e; } if (StringUtil.isNotEmpty(result)) { log.error(result); } else { result = "发送成功"; } return result; } @Override public String getNoticeMethod() { return NoticeMethodEnum.DINGTALK.getName(); } /** * 使用 Token 初始化账号Client * * @return Client * @throws Exception */ private Client createClient() throws Exception { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; return new Client(config); } /** * 获取token,每两小时失效 */ private void getOrRefreshToken() { try { DingTalkClient client = new DefaultDingTalkClient(this.tokenUrl); OapiGettokenRequest req = new OapiGettokenRequest(); req.setAppkey(this.bkAppKey); req.setAppsecret(this.bkAppSecret); req.setHttpMethod("GET"); OapiGettokenResponse rsp = client.execute(req); log.info("token:" + rsp.getBody()); Map json = JsonUtil.StringToEntity(rsp.getBody(), Map.class); this.enterpriseToken = json.get("access_token").toString(); this.tokenFreshTimeSt = System.currentTimeMillis()/1000; } catch (Exception e) { log.error("---获取token出现异常{} {} ",e.getMessage(),e); } } /** * 根据手机号码获取钉钉用户id * -- 备注: 必须将用户拉入组织/群中之后才可以根据手机号码获取用户 * @param phone 用户手机号 * @return void */ private String getDingdingUserIdByPhone(String phone) { try { if (StringUtil.isEmpty(this.enterpriseToken)) { this.getOrRefreshToken(); } DingTalkClient client = new DefaultDingTalkClient(getUserByPhoneUrl); OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest(); req.setMobile(phone); OapiV2UserGetbymobileResponse rsp = client.execute(req, this.enterpriseToken); if (rsp != null) { OapiV2UserGetbymobileResponse.UserGetByMobileResponse result = rsp.getResult(); return result == null ? null : result.getUserid(); } } catch (Exception e) { log.error(e.getMessage(), e); } return null; } /** * 发送钉钉消息 * @param dingUserId 钉钉用户id * @param message 发送消息 * @return void */ public String sendDingTalkNotify(String dingUserId, NoticeMessage message) throws Exception { String result = null; validateToken(); Client client = createClient(); BatchSendOTOHeaders batchSendOTOHeaders = new BatchSendOTOHeaders(); batchSendOTOHeaders.xAcsDingtalkAccessToken = this.enterpriseToken; String content = null; if(message instanceof AlarmMessage){ AlarmMessage alarmMessage = (AlarmMessage)message; content = alarmMessage.wrapperMessage(); }else{ content = message.wrapperMessage(); } String finalContent = "{\"content\": \"" + content + "\"}"; BatchSendOTORequest batchSendOTORequest = new BatchSendOTORequest() .setRobotCode(this.bkAppKey)//机器人appkey .setUserIds(Arrays.asList(dingUserId)) .setMsgKey("officialTextMsg") .setMsgParam(finalContent); try { client.batchSendOTOWithOptions(batchSendOTORequest, batchSendOTOHeaders, new RuntimeOptions()); } catch (TeaException err) { if (!Common.empty(err.code) && !Common.empty(err.message)) // err 中含有 code 和 message 属性,可帮助开发定位问题 log.error(err.code + ":" + err.message); } catch (Exception e) { TeaException err = new TeaException(e.getMessage(), e); if (!Common.empty(err.code) && !Common.empty(err.message)) // err中含有code和message 属性,可帮助开发定位问题 log.error(err.code + ":" + err.message); result = err.code + ":" + err.message; } return result; } /** * 验证并刷新token */ private void validateToken() { if (StringUtil.isEmpty(this.enterpriseToken)) { this.getOrRefreshToken(); } Long now = System.currentTimeMillis()/1000; if(this.tokenFreshTimeSt == 0L){ this.getOrRefreshToken(); }else{ Long diff = (now - tokenFreshTimeSt)/60; if(diff > 600){ // 超过十分钟重新获取一下 this.getOrRefreshToken(); } } } }
4.3 配置参数
# 消息推送相关 notice: # 钉钉消息相关 dingtalk: bk_app_key: xxx bk_app_secret: xxxx token_url: https://oapi.dingtalk.com/gettoken get_user_by_phone_url: https://oapi.dingtalk.com/topapi/v2/user/getbymobile
4.4 创建应用获取appKey+appSecret
1. 使用钉钉账户作为管理员创建一个组织/群
2. 登录钉钉后台管理页面,工作台--->应用管理--->创建应用(委托服务商开发), 创建一个H5微应用
3. 登录钉钉后台管理页面,工作台--->应用管理--->点击应用, 查看 凭证与基础信息 获取配置文件中所需要的appKey/appSecret
4. 为当前应用创建机器人,工作台--->应用管理--->点击应用-->添加应用能力--->机器人添加
5. 为应用申请相应的权限,例如根据手机号码获取用户,企业内机器人发送消息权限
6. 将当前应用发布 ,当添加完毕机器人+权限之后,当前应用显示 开发中,如下图所示,需要在版本与管理中创建一个发布版本(只有发布成功之后才可以使用)
7. 将需要接收消息的钉钉用户拉入群组
5. 邮件发送实现类
5.1 定义实现类
package com.yj.notice.service.impl;import com.yj.commons.tools.utils.JsonUtil; import com.yj.commons.tools.utils.StringUtil; import com.yj.notice.message.MailMessage; import com.yj.notice.message.NoticeMessage; import com.yj.notice.costant.NoticeMethodEnum; import com.yj.notice.service.MessageService; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; import javax.mail.internet.MimeMessage; import java.io.File; /** * @author : dyg * @className : MailMessageServiceImpl * @description : 邮件发送器 * @date : 2023/9/5 14:31 */ @Service("MailMessageService") @Slf4j @Data public class MailMessageServiceImpl implements MessageService { @Value("${spring.mail.username}") private String from; @Autowired private JavaMailSender mailSender; @Override public String getNoticeMethod() { return NoticeMethodEnum.EMAIL.getName(); }
/** * 发送邮件 * @param message * @return */ @Override public String send(NoticeMessage message) { String result = null; try{ if(message instanceof MailMessage){ MailMessage mailMessage = (MailMessage)message; File attachFile = mailMessage.getAttachFile(); String html = mailMessage.getHtml(); if(null != attachFile){ // 带附件的邮件 result = sendAttachFileMail(mailMessage,attachFile); }else{ if(StringUtil.isNotEmpty(html)){ // html内容的邮件 result = sendHtmlMail(mailMessage,html); }else{ // 普通邮件 result = sendSimpleMail(mailMessage); } } } if(StringUtil.isNotEmpty(result)){ log.error("发送邮件: {} 发生错误: {}", JsonUtil.entityToString(message),result); } }catch (Exception e){ log.error(e.getMessage()); result = e.getMessage(); } return result; } /** * 带附件的邮件发送 * @param mailMessage * @param attachFile * @return */ private String sendAttachFileMail(MailMessage mailMessage,File attachFile) { String result = null; try { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper messageHelper = new MimeMessageHelper(message, true); //邮件发送人 messageHelper.setFrom(from); //邮件接收人 messageHelper.setTo(mailMessage.getReceiverId()); //邮件主题 message.setSubject(mailMessage.getTitle()); //邮件内容 messageHelper.setText(mailMessage.wrapperMessage()); //添加附件 messageHelper.addAttachment(attachFile.getName(), attachFile); //发送 mailSender.send(message); }catch(Exception e) { log.error("发送附件邮件报错: {} {}",e.getMessage(),e); result = e.getMessage(); } return result; } /** * html内容的邮件发送 * @param mailMessage * @param html * @return */ private String sendHtmlMail(MailMessage mailMessage, String html) { String result = null; try { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper messageHelper = new MimeMessageHelper(message, true); //邮件发送人 messageHelper.setFrom(from); //邮件接收人 messageHelper.setTo(mailMessage.getReceiverId()); //邮件主题 message.setSubject(mailMessage.getTitle()); //邮件内容 messageHelper.setText(html,true); //发送 mailSender.send(message); }catch(Exception e) { log.error("发送html内容邮件报错: {} {}",e.getMessage(),e); result = e.getMessage(); } return result; } /** * 普通邮件发送 * @param mailMessage * @return */ private String sendSimpleMail(MailMessage mailMessage) { String result = null; try { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(from); message.setTo(mailMessage.getReceiverId()); message.setCc(from); message.setSubject(mailMessage.getTitle()); message.setText(mailMessage.wrapperMessage()); mailSender.send(message); }catch(Exception e) { log.error("发送普通邮件报错: {} {}",e.getMessage(),e); result = e.getMessage(); } return result; } }
5.2 配置参数
spring: # 邮件相关 mail: host: smtp.qq.com port: 465 username: [email protected] password: xxx properties: mail: smtp: auth: true starttls: enable: true required: true
6. 腾讯短信发送实现类
6.1 定义实现类
package com.yj.notice.service.impl; import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.exception.TencentCloudSDKException; import com.tencentcloudapi.common.profile.ClientProfile; import com.tencentcloudapi.common.profile.HttpProfile; import com.tencentcloudapi.sms.v20210111.SmsClient; import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; import com.yj.cmp.commons.json.JsonUtil; import com.yj.notice.costant.NoticeMethodEnum; import com.yj.notice.message.NoticeMessage; import com.yj.notice.service.MessageService; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; /** * @author : dyg * @className : TencentMessageServiceImpl * @description : 描述说明该类的功能 * @date : 2024/4/16 10:10 */ @Service("TencentMessageService") @Slf4j @Data public class TencentMessageServiceImpl implements MessageService { /** * 短信发送API */ private final String SMS_API = "sms.tencentcloudapi.com"; private final String SMS_ACTION = "SendSms"; private final String SMS_VERSION = "2021-01-11"; private final String SMS_REGION= "ap-beijing"; /** * 短信客户端 */ private SmsClient smsClient; /** * 短信发送AppId */ @Value(value = "${notice.tencent.appId}") private String smsSdkAppId; /** * 短信发送模板Id */ @Value(value = "${notice.tencent.templateId}") private String smsTemplateId ; /** * 腾讯云认证信息 */ @Value(value = "${notice.tencent.secretId}") private String secretId ; @Value(value = "${notice.tencent.secretKey}") private String secretKey ; /** * 获取客户端 * @return */ public SmsClient getSmsClient() { synchronized (this){ if(null == smsClient){ Credential credential = new Credential(secretId,secretKey); // 实例化一个http选项,可选的,没有特殊需求可以跳过 HttpProfile httpProfile = new HttpProfile(); // 推荐使用北极星,相关指引可访问如下链接 // https://git.woa.com/tencentcloud-internal/tencentcloud-sdk-java#%E5%8C%97%E6%9E%81%E6%98%9F httpProfile.setEndpoint(SMS_API); // 实例化一个client选项,可选的,没有特殊需求可以跳过 ClientProfile clientProfile = new ClientProfile(); clientProfile.setHttpProfile(httpProfile); // 实例化要请求产品的client对象,clientProfile是可选的 smsClient = new SmsClient(credential,SMS_REGION,clientProfile); } } return smsClient; } @Override public String send(NoticeMessage message) { String result = null; SendSmsRequest sendSmsRequest = new SendSmsRequest(); sendSmsRequest.setSmsSdkAppId(smsSdkAppId); sendSmsRequest.setPhoneNumberSet(new String[]{message.getReceiverPhone()}); sendSmsRequest.setSignName("腾讯云"); sendSmsRequest.setTemplateId(smsTemplateId); sendSmsRequest.setTemplateParamSet(new String[]{message.getTitle(),message.getContent(),message.getTime()}); try { SendSmsResponse sendSmsResponse = smsClient.SendSms(sendSmsRequest); result = "发送腾讯短信平台结果: "+ JsonUtil.objectToJson(sendSmsResponse); } catch (TencentCloudSDKException e) { log.error(e.getMessage(),e); result ="发送腾讯短信平台失败: "+ e.getMessage()+ " " + e; } return result; } @Override public String getNoticeMethod() { return NoticeMethodEnum.TENCENT_SMS.getName(); } }
6.2 配置参数
# 消息推送相关 notice: # 腾讯短信配置 tencent: appId: xx templateId: xx secretId: xx secretKey: xx
7. 系统内部消息发送实现类
7.1 系统内部消息表定义
-- 系统用户-站内消息表 DROP TABLE IF EXISTS `system_user_message`; create table `system_user_message` ( `id` int(16) NOT NULL AUTO_INCREMENT, `message_type` varchar(200) COMMENT '消息类型 public-公告 private-个人消息 ticket-工单消息', `source_msg_id` int(16) COMMENT '消息id' , `title` varchar(200) COMMENT '标题', `content` text COMMENT '内容', `receiver_id` varchar(200) COMMENT '接收者id 用户id', `send_time` datetime NOT NULL default NOW() COMMENT '发送时间', `bus_type` varchar(200) COMMENT '业务类型', `is_read` int(1) default 0 COMMENT '是否已读 0-no 1-yes', `read_time` datetime COMMENT '读取时间', `properties` varchar(1000) comment '其他数据', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '系统用户-站内消息表';
6.2 系统内部消息实体定义
package com.yj.notice.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.yj.notice.costant.MessageSource; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * @author : dyg * @className : SystemMessage * @description : 内部通知消息 * @date : 2023/9/7 10:04 */ @AllArgsConstructor @NoArgsConstructor @Builder @Data @TableName("system_user_message") public class SystemUserMessage implements Serializable { private static final long serialVersionUID = 228169618996650529L; @TableId(type = IdType.AUTO) private Integer id; /** * 消息类型 * {@link MessageSource} */ @TableField("message_type") private String messageType; /** * 源头消息id * public-公告 yg_alarm-云管告警 cloud_alarm-插件告警 使用到 * {@link MessageSource} */ @TableField("source_msg_id") private Integer sourceMsgId; /** * 消息标题 */ @TableField("title") private String title; /** *消息内容 */ @TableField("content") private String content; /** * 接收者id */ @TableField("receiver_id") private String receiverId; /** * 发送时间 */ @TableField("send_time") private Date sendTime; /** * 消息业务类型 */ @TableField("bus_type") private String busType; /** * 是否已读 * 0-no 1-yes */ @TableField("is_read") private Integer isRead; /** * 读取时间 */ @TableField("read_time") private Date readTime; @TableField("properties") private String properties; }
7.3 实现类定义
package com.yj.notice.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yj.cmp.bss.constant.RoleGroup; import com.yj.cmp.bss.entity.APIPermission; import com.yj.cmp.bss.entity.Audit; import com.yj.cmp.bss.entity.Request; import com.yj.cmp.bss.entity.User; import com.yj.cmp.bss.service.RequestService; import com.yj.cmp.bss.service.UserService; import com.yj.cmp.commons.constant.BssConstant; import com.yj.commons.security.user.SecurityUser; import com.yj.commons.security.user.UserDetail; import com.yj.commons.tools.page.PageData; import com.yj.commons.tools.utils.Result; import com.yj.commons.tools.utils.StringUtil; import com.yj.helper.user.UserHelper; import com.yj.notice.costant.MessageSource; import com.yj.notice.costant.NoticeMethodEnum; import com.yj.notice.dao.SystemUserMessageDao; import com.yj.notice.entity.SystemUserMessage; import com.yj.notice.entity.PublicSystemNotice; import com.yj.notice.message.NoticeMessage; import com.yj.notice.service.MessageService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import javax.servlet.http.HttpServletRequest; import java.util.*; import java.util.stream.Collectors; /** * @author : dyg * @className : NoticeInnerMessageServiceImpl * @description : 描述说明该类的功能 * @date : 2023/9/7 10:42 */ @Service @Slf4j public class SystemUserMessageServiceImpl extends ServiceImpl<SystemUserMessageDao, SystemUserMessage> implements MessageService { @Override public String send(NoticeMessage message) { SystemUserMessage build = SystemUserMessage.builder().messageType(message.getMessageType()) .title(message.getTitle()) .content(message.getContent()) .receiverId(message.getReceiverId()) .sendTime(new Date()) .busType(message.getMessageType()) .isRead(0) .build(); this.save(build); return "success"; } @Override public String getNoticeMethod() { return NoticeMethodEnum.IN_MSG.getName(); } }
8. 消息推送测试
8.1 钉钉推送测试
package com.yj.notice.demo; import com.aliyun.dingtalkrobot_1_0.Client; import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOHeaders; import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTORequest; import com.aliyun.tea.TeaException; import com.aliyun.teaopenapi.models.Config; import com.aliyun.teautil.Common; import com.aliyun.teautil.models.RuntimeOptions; import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.request.*; import com.dingtalk.api.response.*; import com.taobao.api.ApiException; import com.yj.commons.tools.utils.JsonUtil; import com.yj.commons.tools.utils.StringUtil; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.Map; @Slf4j public class DingTalkMessageSend { // 企业凭证,两小时一更新 private static String enterpriseToken = ""; // 应用凭证 private static String appkey = "xxxx"; private static String appsecret = "xxxx"; // 获取token,每两小时失效 public static void getToken() { try { DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken"); OapiGettokenRequest req = new OapiGettokenRequest(); req.setAppkey(appkey); req.setAppsecret(appsecret); req.setHttpMethod("GET"); OapiGettokenResponse rsp = client.execute(req); log.info("token:" + rsp.getBody()); Map json = JsonUtil.StringToEntity(rsp.getBody(), Map.class); enterpriseToken = json.get("access_token").toString(); } catch (ApiException e) { e.printStackTrace(); } } /** * 使用 Token 初始化账号Client * * @return Client * @throws Exception */ private static Client createClient() throws Exception { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; return new Client(config); } /** * 发送钉钉消息 * * @param dingUserId 钉钉用户id * @param content 发送消息内容 * @return void */ public static void createDingNotify(String dingUserId, String content) throws Exception { if (StringUtil.isEmpty(enterpriseToken)) { getToken(); } Client client = createClient(); BatchSendOTOHeaders batchSendOTOHeaders = new BatchSendOTOHeaders(); batchSendOTOHeaders.xAcsDingtalkAccessToken = enterpriseToken; content = "{\"content\": \"" + content + "\"}"; BatchSendOTORequest batchSendOTORequest = new BatchSendOTORequest() .setRobotCode(appkey)//机器人appkey .setUserIds(Arrays.asList(dingUserId)) .setMsgKey("officialTextMsg") .setMsgParam(content); try { client.batchSendOTOWithOptions(batchSendOTORequest, batchSendOTOHeaders, new RuntimeOptions()); } catch (TeaException err) { if (!Common.empty(err.code) && !Common.empty(err.message)) // err 中含有 code 和 message 属性,可帮助开发定位问题 log.error(err.code + ":" + err.message); // 企业凭证enterpriseToken不合法导致出错时获取新企业凭证并重试 if (err.code.equals("InvalidAuthentication")) { getToken(); } } catch (Exception e) { TeaException err = new TeaException(e.getMessage(), e); if (!Common.empty(err.code) && !Common.empty(err.message)) // err中含有code和message 属性,可帮助开发定位问题 log.error(err.code + ":" + err.message); } } /** * 发送钉钉消息 * * @param phone 用户手机号 * @return void */ public static String getDingdingUserIdByPhone(String phone) { try { if (StringUtil.isEmpty(enterpriseToken)) { getToken(); } DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getbymobile"); OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest(); req.setMobile(phone); OapiV2UserGetbymobileResponse rsp = client.execute(req, enterpriseToken); if (rsp != null) { OapiV2UserGetbymobileResponse.UserGetByMobileResponse result = rsp.getResult(); return result == null ? null : result.getUserid(); } } catch (ApiException e) { log.error(e.getErrMsg(), e); } return null; } /** * 获取组内用户列表 * * @return void */ public static void getUserList( ) { try { if (StringUtil.isEmpty(enterpriseToken)) { getToken(); } DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/listid"); OapiUserListRequest req = new OapiUserListRequest(); OapiUserListResponse rsp = client.execute(req, enterpriseToken); if (rsp != null) { List<OapiUserListResponse.Userlist> userlist = rsp.getUserlist(); userlist.stream().forEach(System.out::println); } } catch (ApiException e) { log.error(e.getErrMsg(), e); } } /** * 获取组内用户列表 * * @return void */ public static void getDepartmentList( ) { try { if (StringUtil.isEmpty(enterpriseToken)) { getToken(); } DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/listsub"); OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest(); OapiV2DepartmentListsubResponse rsp = client.execute(req, enterpriseToken); if (rsp != null) { List<OapiV2DepartmentListsubResponse.DeptBaseResponse> result = rsp.getResult(); result.stream().forEach(System.out::println); } } catch (ApiException e) { log.error(e.getErrMsg(), e); } } public static void main(String[] args) throws Exception{ String dingdingUserIdByPhone = getDingdingUserIdByPhone("17352253381"); System.out.println(dingdingUserIdByPhone); createDingNotify(dingdingUserIdByPhone,"告警时间: 2023-09-05 16:33:36\n告警级别: 中级\n告警类别: 虚拟机\n告警消息: 虚拟机告警信息\n备注: 虚拟机告警信息"); getDepartmentList(); getUserList(); } }
8.2 微信推送测试
package com.yj.notice.demo; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import com.yj.commons.tools.utils.DateUtils; import com.yj.commons.tools.utils.JsonUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import static com.yj.commons.tools.utils.DateUtils.DATE_TIME_PATTERN; /** * @author : dyg * @className : WechatMessageSend * @description : 描述说明该类的功能 * @date : 2023/9/5 10:01 */ @Slf4j public class WechatMessageSend { @Data public static class WeChatTemplateMsg implements Serializable { /** * 消息 */ private String value; /** * 消息颜色 */ private String color; public WeChatTemplateMsg(String value) { this.value = value; this.color = "#173177"; } public WeChatTemplateMsg(String value, String color) { this.value = value; this.color = color; } } public static String getAccessToken(){ String appId = "xxxx"; String appIdSecret = "xxxx"; String requestUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ appId +"&secret=" + appIdSecret; String res = HttpUtil.get(requestUrl); JSONObject jsonObject = JSONObject.parseObject(res); String accessToken = jsonObject.getString("access_token"); log.info("accessToken:{}", accessToken); return accessToken; } public static void getUserList(){ RestTemplate restTemplate = new RestTemplate(); String accessToken = getAccessToken(); String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/get?access_token="+ accessToken; ResponseEntity<String> response = restTemplate.postForEntity(requestUrl, null, String.class); log.info("结果是: {}",response.getBody()); JSONObject result = JSONObject.parseObject(response.getBody()); com.alibaba.fastjson.JSONArray openIdJsonArray = result.getJSONObject("data").getJSONArray("openid"); Iterator iterator = openIdJsonArray.iterator(); if (iterator.hasNext()){ String userId = iterator.next().toString(); log.debug("用户openid:"+userId); String userDetailUri = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+accessToken+"&openid="+userId+"&lang=zh_CN"; ResponseEntity<String> response2 = restTemplate.postForEntity(userDetailUri, null, String.class); log.info("结果是: {}",response2.getBody()); JSONObject result2 = JSONObject.parseObject(response2.getBody()); log.info("用户详细信息: {}",result2.toJSONString()); } } public static void sendMessage(String type){ // 模板参数 Map<String, WeChatTemplateMsg> sendMag = new HashMap<String, WeChatTemplateMsg>(); // openId代表一个唯一微信用户,即微信消息的接收人 String openId = "okUjK6P908Zwjc8BOSdinAO8iG5o"; // 公众号的模板id(也有相应的接口可以查询到) String alarmTemplateId = "mfriJWNssZYtIbpfzxH-4FlxgK4ZQ0ID_xNvEO0xDOY"; String commonTemplateId = "q7bSOYPPDHn-Lz_vTShzsbU4WzKX3lH9nxuasnngj8A"; // 微信的基础accessToken String accessToken = getAccessToken(); String requestUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken; //消息主题显示相关map //根据自己的模板定义内容和颜色 //拼接base参数 Map<String, Object> sendBody = new HashMap<>(); sendBody.put("touser", openId); // openId // sendBody.put("url", "https://www.baidu.com"); //跳转网页url sendBody.put("data", sendMag); // 模板参数 if("alarm".equals(type)){ sendMag.put("message", new WeChatTemplateMsg("虚拟机-102.23.26.36 CPU使用占比超过80%")); sendMag.put("time",new WeChatTemplateMsg(DateUtils.format(new Date(),DATE_TIME_PATTERN),"#173177")); sendMag.put("level",new WeChatTemplateMsg("中等","#FF69B4" )); sendMag.put("type",new WeChatTemplateMsg("虚拟机CPU" ,"#173177")); sendMag.put("remark",new WeChatTemplateMsg("虚拟机CPU使用触发告警","#173177")); sendBody.put("template_id", alarmTemplateId); // 模板Id }else{ sendMag.put("content", new WeChatTemplateMsg("根据兰州市气象局发布的最新天气预警信息,未来8-24小时内,城关区、七里河区、西固区、安宁区、榆中县、皋兰县大部将有依次明显的降水活动,并伴有短时5-6级大风,请注意及时关注最新天气情况!")); sendMag.put("title",new WeChatTemplateMsg("暴雨蓝色预警")); sendMag.put("time",new WeChatTemplateMsg(DateUtils.format(new Date(),DATE_TIME_PATTERN),"#FF69B4")); sendBody.put("template_id", commonTemplateId); // 模板Id } try { // 1.创建httpclient对象 CloseableHttpClient client = HttpClients.createDefault(); // 2.创建post对象 HttpPost post = new HttpPost(requestUrl); StringEntity postingString = new StringEntity(JsonUtil.entityToString(sendBody), "utf-8"); post.setEntity(postingString); // 3.执行post方法:得到结果 CloseableHttpResponse response = client.execute(post); // 4.处理结果 // 1.得到状态码 int statusCode = response.getStatusLine().getStatusCode(); log.info("----http code : {}", statusCode); if (statusCode == 200) { // 2.得到实体内容 org.apache.http.HttpEntity entity = response.getEntity(); String content = EntityUtils.toString(entity, "utf-8"); System.out.println(content); } // 5.关闭连接 client.close(); } catch (IOException e) { log.error("------exception : {} {} ",e.getMessage(),e); } } public static void main(String[] args) { // getAccessToken(); // getUserList(); sendMessage("a"); } }
标签:yj,java,String,微信,new,import,message,推送,com From: https://www.cnblogs.com/dduo/p/18140862