• SpringBoot中企业微信的API调用


    说明

    企业微信官方提供的均为API接口,没有提供集成SDK。因此无需引入Maven依赖,直接以Https方式请求即可。
    有些第三方提供了集成的Java SDK,可根据需求自行选用。
    本文采用直接调用官方API的方式。

    基础配置

    企业微信注册后,可得到corpIdagentIdcorpSecret的信息。
    而企业微信的所有接口均以https://qyapi.weixin.qq.com/cgi-bin开头。
    综上,在yml中定义配置:

    qywx:
      endpoint: https://qyapi.weixin.qq.com/cgi-bin
      corpId: wx00000000000000ba
      agentId: 1000000
      corpSecret: V0000000000_00000000000000000000000000000wc
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后定义一个配置类:

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Component;
    
    
    @Component
    @Configuration
    @ConfigurationProperties(prefix = QywxConfig.prefix)
    @Data
    public class QywxConfig {
    
        public final static String prefix = "qywx";
    
        /**
         * 企业微信请求地址
         */
        private String endpoint;
    	
        /**
         * 企业id
         */
        private String corpId;
    
        private String agentId;
    
        private String corpSecret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    这样即可在容器类中引用配置。

    发送请求

    服务端需要向企业微信发送请求。这里使用Forest来进行请求。
    首先引入依赖:

    
    <dependency>
        <groupId>com.dtflys.forestgroupId>
        <artifactId>forest-spring-boot-starterartifactId>
        <version>1.5.32version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    考虑到所有的请求都需拼接共同的企业微信请求地址,因此为Forest添加拦截器来统一处理:

    import com.dtflys.forest.http.ForestRequest;
    import com.dtflys.forest.interceptor.Interceptor;
    import com.dtflys.forest.reflection.ForestMethod;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class QywxForestInterceptor implements Interceptor<String> {
    
        @Autowired
        private QywxConfig qywxConfig;
    
        @Override
        public void onInvokeMethod(ForestRequest req, ForestMethod method, Object[] args) {
            req.setBasePath(qywxConfig.getEndpoint());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    然后定义请求的接口类:

    import com.cosmoplat.hyida.core.result.Result;
    import com.cosmoplat.qingyin.safety.qywx.controller.model.dto.MessageTextDto;
    import com.cosmoplat.qingyin.safety.qywx.controller.model.dto.QyWxUserDetailDto;
    import com.cosmoplat.qingyin.safety.qywx.inteceptor.SafetyForestInterceptor;
    import com.dtflys.forest.annotation.*;
    
    import java.util.Map;
    
    @BaseRequest(interceptor = QywxForestInterceptor.class, headers = {"Accept: */*", "Content-Type: application/json"})
    public interface QywxForestClient {
    
        /**
         * 获取access_token
         * @param corpId
         * @param corpSecret
         * @return
         */
        @Get(url = "/gettoken?corpid={corpId}&corpsecret={corpSecret}")
        Map<String,Object> getToken(@Var("corpId") String corpId, @Var("corpSecret") String corpSecret);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当调用时,引入QywxConfigQywxForestClient

    @Resource
    private QywxConfig qywxConfig;
    
    @Resource
    private QywxForestClient qywxForestClient;
    
    private String getAccessToken() {
        // 根据corpId和corpSecret获取 access_token
        String corpId = qywxConfig.getCorpId();
        String corpSecret = qywxConfig.getCorpSecret();
        
        Map tokenResult = qywxForestClient.getToken(corpId, corpSecret);
    	
    	if (!result.get("errcode").equals(0) || StringUtils.isEmpty(result.get("access_token"))) {
            System.out.println("获取企业微信access_token失败!" + result.get("errmsg"));            
        }
    	
        return String.valueOf(result.get("access_token"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    关于access_token

    access_token的官方文档地址:

    https://developer.work.weixin.qq.com/document/path/91039

    里面明确提到了3个注意事项:

    • 为了安全考虑,开发者 请勿 将 access_token 返回给前端,需要开发者保存在后台,所有访问企业微信api的请求由后台发起。
    • 开发者需要缓存access_token,用于后续接口的调用(注意:不能频繁调用gettoken接口,否则会受到频率拦截)。当access_token失效或过期时,需要重新获取。
    • access_token的有效期通过返回的expires_in来传达,正常情况下为7200秒(2小时),有效期内重复获取返回相同结果,过期后获取会返回新的access_token。

    因此,最佳实践应使用Redis缓存access_token。当获取access_token时先从Redis中取。若取不到,则向企业微信发起请求获取,并写入到Redis中。

    携带参数

    以发送应用消息为例,现在要发送一个文本消息。
    参考官方文档:
    请求方式:POST(HTTPS)
    请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN
    参数示例为:

    {
       "touser" : "UserID1|UserID2|UserID3",
       "toparty" : "PartyID1|PartyID2",
       "totag" : "TagID1 | TagID2",
       "msgtype" : "text",
       "agentid" : 1,
       "text" : {
           "content" : "你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。"
       },
       "safe":0,
       "enable_id_trans": 0,
       "enable_duplicate_check": 0,
       "duplicate_check_interval": 1800
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    返回示例为:

    {
      "errcode" : 0,
      "errmsg" : "ok",
      "invaliduser" : "userid1|userid2",
      "invalidparty" : "partyid1|partyid2",
      "invalidtag": "tagid1|tagid2",
      "unlicenseduser" : "userid3|userid4",
      "msgid": "xxxx",
      "response_code": "xyzxyz"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    现在根据文档的信息来添加接口。
    首先定义一个MessageTextDto对象来承载发送时的参数。其中text属性又是一个对象。

    import io.swagger.annotations.ApiModel;
    import lombok.Data;
    
    @Data
    public class MessageTextDto {
    
        /**
         * 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
         * 特殊情况:指定为"@all",则向该企业应用的全部成员发送
         */
        private String touser;
    
        /**
         * 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
         * 当touser为"@all"时忽略本参数
         */
        private String toparty;
    
        /**
         * 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
         * 当touser为"@all"时忽略本参数
         */
        private String totag;
    
        /**
         * 消息类型,此时固定为:text
         * 必填
         */
        private String msgtype = "text";
    
        /**
         * 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值
         * 必填
         */
        private String agentid;
    
        /**
         * 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
         * 必填
         */
        private MessageText text = new MessageText();
    
        /**
         * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0
         */
        private String safe;
    
        /**
         * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。
         */
        private String enable_id_trans;
    
        /**
         * 	表示是否开启重复消息检查,0表示否,1表示是,默认0
         */
        private String enable_duplicate_check;
    
        /**
         * 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时
         */
        private String duplicate_check_interval;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    MessageText对象:

    import lombok.Data;
    
    @Data
    public class MessageText {
    
        /**
         * 消息内容,最长不超过2048个字节,超过将截断(支持id转译)
         * 必填
         */
        private String content;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后在QywxForestClient中添加接口:

    @BaseRequest(interceptor = QywxForestInterceptor.class, headers = {"Accept: */*", "Content-Type: application/json"})
    public interface QywxForestClient {
    
        /**
         * 获取访问用户敏感信息
         * @param messageTextDto
         * @param accessToken
         * @return
         */
        @Post(url = "/message/send?access_token={accessToken}")
        Map<String, Object> messageSend(@Body MessageTextDto messageTextDto, @Var("accessToken") String accessToken);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这样即可进行调用:

    private Map<String, Object> sendMessage(String touser, String content) {
        if (StrUtil.isEmpty(touser)) {
    		// 消息接收者为空
    		return Maps.newHashMap();
        }
    
        MessageTextDto messageTextDto = new MessageTextDto();
        messageTextDto.setAgentid(safetyConfig.getAgentId());
        messageTextDto.setTouser(touser);
        messageTextDto.getText().setContent(content);
    
        String accessToken = getAccessToken();
    
        Map<String, Object> result = qywxForestClient.messageSend(messageTextDto, accessToken);
    
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    其中getAccessToken()调用了前面封装的getAccessToken方法。

  • 相关阅读:
    组件封装使用?
    Python基础数据结构入门必读指南
    面试官:TCC解决方案是什么?如何解决TCC幂等问题?
    图Graph
    如何进行日志管理?有好用的日志管理软件吗?
    Spring【五大类注解的存储和读取Bean方法注解】
    Unity VFX Output 模块详解
    SPI学习笔记:DAC与ACD
    Cocos creator实现《滑雪趣挑战》滑雪小游戏资源及代码
    麻雀算法(SSA)优化xgboost的分类预测模型,多输入单输出模型,SSA-xgboost分类预测。
  • 原文地址:https://blog.csdn.net/fyyyr/article/details/134509464