记录一次企业微信消息推送经历

背景:

系统监控功能,当监控项达到阈值,企业微信发送消息给指定的人

1.选型

企业微信消息推送分两种:
1.通过应用推送给指定的人
2.往群里推送消息(需要把相关人员加入群里)
选择第一种通过应用推送,感觉更灵活

2.实现

获取三个重要参数(企业id,应用号码,密钥)

image.png

image.png

调用官方两个核心接口

1.通过企业id和密钥获取token(官网地址:developer.work.weixin.qq.com/document/path/91039)
2.通过token发送消息(官网地址:developer.work.weixin.qq.com/document/path/90236)


获取token

发送消息

代码:

package cn.vpclub.moses.servermonitor.weChat;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class WeChatSender {

    @Value("${wechat.tokenUrl}")
    private String tokenUrl;//获取token的url
    @Value("${wechat.sendUrl}")
    private String sendUrl;//发送消息url
    @Value("${wechat.agentId}")
    private String agentId;//应用id
    @Value("${wechat.cropId}")
    private String cropId;//公司id
    @Value("${wechat.cropSecret}")
    private String cropSecret;//密钥

    private static Gson gson = new Gson();

    public void sendMessage(String contentValue, String touser) {
        try {
            // 发送消息
            log.info("wechat send message begin message:{},touser:{}", contentValue, touser);
            String response = post(createPostData(touser, contentValue), this.getToken());
            log.info("wechat send message end return:{}", response);
        } catch (IOException e) {
            log.error("wechat push message error,message:{}", contentValue);
        }
    }

    private String getToken() throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(tokenUrl + "?corpid=" + cropId + "&corpsecret=" + cropSecret);
        CloseableHttpResponse response = httpClient.execute(httpGet);
        log.info("wechatSender getToken return", response.toString());
        String resp;
        try {
            HttpEntity entity = response.getEntity();
            System.out.println(response.getAllHeaders());
            resp = EntityUtils.toString(entity, "utf-8");
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
        Map<String, Object> map = gson.fromJson(resp,
                new TypeToken<Map<String, Object>>() {
                }.getType());
        return map.get("access_token").toString();
    }

    private String createPostData(String touser, String contentValue) {
        Map<String, Object> weChatData = new HashMap<>();
        weChatData.put("touser", touser);
        weChatData.put("agentid", agentId);
        weChatData.put("msgtype", "text");
        Map<Object, Object> content = new HashMap<>();
        content.put("content", contentValue);
        weChatData.put("text", content);
        log.info("wechat createPostData:{}", gson.toJson(weChatData));
        return gson.toJson(weChatData);
    }

    private String post(String data, String token) throws IOException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(sendUrl + token);
        httpPost.setHeader("Content-Type", "Content-Type");
        httpPost.setEntity(new StringEntity(data, "utf-8"));
        CloseableHttpResponse response = httpclient.execute(httpPost);
        log.info("wechat send return:{}", response);
        String resp;
        try {
            HttpEntity entity = response.getEntity();
            resp = EntityUtils.toString(entity, "utf-8");
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
        return resp;
    }
}

3.设置接收消息服务器URL

做完第二步后我以为结束了,结果消息发不出去,报错我的本机ip没有发消息的权限

解决:要在企业微信加可信ip
企业微信1.0版本可以直接加(不用管)
企业微信2.0版本要求先加【可信域名】或【接收消息服务器URL】才能加可信ip


image.png
image.png

【可信域名】或【接收消息服务器URL】本质上都是企业微信会去请求对应的【域名】或【url】,并且得到预期的值才会推送消息。就是附加了一层保障,确认消息推送的请求是我们发出去的。

因为【可信域名】需要使用80端口,所以采用了【接收消息服务器URL】的方式,感觉更灵活

【接收消息服务器URL】大体流程:调用企业微信推送消息后,企业微信会回调我们设置的接口,我们解密后对比签名是否一致,如果一致再解密消息参数,然后把解密出来的消息返回,企业微信确认消息是最初的消息则进行消息推送。
加密解密一大堆,官网:developer.work.weixin.qq.com/document/path/90968?vid=1688857843457002&deviceid=76843fa6-db53-4ed9-a7fe-4462f5952416&version=4.1.3.6008&platform=win#%E5%8A%A0%E5%AF%86%E5%87%BD%E6%95%B0
但是:官方有代码示例,上面说的都不用你管。把官方提供的加密解密代码搞下来丢进自己的项目,然后直接调用就好了,地址:developer.work.weixin.qq.com/document/path/90307)

官方加密解密下载地点

解压后密解密代码路径

调用示例

代码

@RestController
@RequestMapping("/wechat")
public class WeChatController {

    @Autowired
    Callback callback;

    /**
     * @param msg_signature 企业微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
     * @param timestamp     时间戳
     * @param nonce         随机数
     * @param echostr       加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、CorpID四个字段,其中msg即为消息内容明文
     * @return
     * @throws AesException
     */
    @RequestMapping("/messageConfirm")
    public String messageConfirm(@RequestParam String msg_signature, @RequestParam String timestamp, @RequestParam String nonce, @RequestParam String echostr) {
        return callback.messageConfirm(msg_signature, timestamp, nonce, echostr);
    }
}

@Slf4j
@Component
public class Callback {
    @Value("${wechat.callbackToken}")
    private String callbackToken;//回调token。可以自己定义也可以去企业微信随机生成
    @Value("${wechat.callbackEncodingAESKey}")
    private String callbackEncodingAESKey;//回调密钥。可以自己定义也可以去企业微信随机生成
    @Value("${wechat.cropId}")
    private String cropId;//公司id

    public String messageConfirm(String msgSignature, String timestamp, String nonce, String echostr) {
        try {
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(callbackToken, callbackEncodingAESKey, cropId);
            return wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);
        } catch (AesException e) {
            log.info("wechat messageConfirm error,code:{},message:{}", e.getCode(), e.getMessage());
        }
        return null;
    }
}

回调模式验证

接口写好后,先发到测试环境(【接收消息服务器URL】需要公网地址(你的本机ip是不可以的,那是局域网),如果是内网系统没有公网地址就要去做内网穿透了--一般安全性要求这么高的系统也不会用企业微信去推系统消息),然后用【测试回调模式】去验证
地址:open.work.weixin.qq.com/wwopen/devtool/interface/combine?vid=1688857843457002&cst=04EAB36DA5210A5B2E3A0F85189683906D8406D9DBD12EFF64BE38560124CC421CBF753B7980E7BBF5A9D7971B401E3E4F77E7EAED15D0D2C854EB915877E7CD&deviceid=76843fa6-db53-4ed9-a7fe-4462f5952416&version=4.1.3.6008&platform=win


image.png

测试成功后,去配置【接收消息服务器URL】


image.png

保存成功后记得把你的本机ip加入可信ip(在报错-ip没有发送消息权限时,报错信息里会有个ip,把这个ip加入可信ip)

发给谁

touser 可以写@all表示发给所有人,发送给指定的人可以用userid1|userId2的方式发送,但是userId怎么来的,在企业微信的应用里找了半天也没找到,联系本司企业微信管理员也不知道是什么。一番寻找发现蛛丝马迹,官网的服务端API有个手机号获取userid(地址:developer.work.weixin.qq.【delete】com/document/path/95402)。通过多个手机号验证发现userId是姓.名的方式,如张三的userId就是zhang.san


image.png

剧终!!等待下一次的折磨!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容