2017-10-17 22:36:19 qq_32737755 阅读数 492
  • 用vue.js高效开发微信小程序

    原生小程序开发有哪些痛点? 频繁调用 setData及 setData过程中页面跳闪 强制将WXSS、WXML和JS代码分离到3个不同的文件中 没有状态管理,参考Vuex和Redux 没有过滤器 不能使用 less、scss 等预编译器 组件化支持能力太弱(几乎没有) 为什么使用第三方框架开发微信小程序? 只要熟悉vue或react即可快速上手,学习成本低 一套代码可在多端编译运行(微信,支付宝,h5,RN)  支付宝小程序暂不完善 组件化开发,完美解决组件隔离,组件嵌套,组件通信等问题 支持使用第三方 npm 资源 使小程序可支持 Promise,解决回调烦恼 可使用 Generator Fu-nction / Class / Async Function 等特性,提升开发效率 对小程序本身的优化,如生命周期的补充,性能的优化等等 支持样式编译器: Scss/Less,模板编译器,代码编译器:Babel/Typescript。 结论:为了组件化开发,核心就是为了高效开发

    303 人正在学习 去看看 何韬

做微信开发,肯定很常用模板消息。一般简便的用法是在微信公众平台直接设置,然后拿来调用:服务号都可以在功能->添加功能插件处看到申请模板消息功能的入口,但只有认证后的服务号才可以申请模板消息的使用权限并获得模板消息权限。

请几天工作需要研究了下微信文档 ,根据模板消息接口写了个发送模板消息的简单程序。
下面将代码贴出:
下面代码可直接拷贝复用 需要更改&appid &secret 还有 接受者openid

/**http函数**/
function https_request($url,$data=null){
        $curl = curl_init();
        curl_setopt($curl,CURLOPT_URL,$url);
        curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,FALSE);
        if(!empty($data)){
            curl_setopt($curl,CURLOPT_POST,1);
            curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
        }
        curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }
$aturl ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=微信appid&secret=微信secret";
    $atres = https_request($aturl);
    $atres = json_decode($atres,true);
    $access_token = $atres['access_token'];
    //获取access_token

//设置所属行业
     $take_url = "https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token={$access_token}";
     //industry_id1 industry_id2 行业id
    $take_data = array('industry_id1'=>1,'industry_id2'=>30);
    $result = https_request($take_url, json_encode($take_data));
    //var_dump($result);

//获取设置的行业信息
    $get_url = "https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token={$access_token}";
    $get_result = https_request($get_url);
    $get_result = json_decode($get_result,true);
    //var_dump($get_result);
//定义模板编号(公众平台可以查到所需要的编号)  获得模板ID
//这里的 模板标题:等待审核通知
    $mb_url = "https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token={$access_token}";
    $mb_data = array('template_id_short'=>'OPENTM408471635');
    $mb_result = https_request($mb_url, json_encode($mb_data));
    $mb_result = json_decode($mb_result,true);

//发送模板消息
    $send_url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={$access_token}";
    $send_data = array(    
    "touser"=>"接收人 openid",
    //接收人 openid
    "template_id"=>$mb_result['template_id'],
    "url"=>"http://weixin.qq.com",            
    "data"=>array(  "first"=>array("value"=>"家属/租客会员卡申请","color"=>"#173177"),
    "keyword1"=>array("value"=>"申请人","color"=>"#173177"),
    "keyword2"=>array("value"=>"申请信息:****************; ","color"=>"#173177"),
    'remark' => array('value' => '请进入成员列表进行审核操作!' , 'color' => '#4a5077')
                    )
           );
    $send_result = https_request($send_url, json_encode($send_data));
    $send_result = json_decode($send_result,true);
    //var_dump($send_result);
    /**我做了个测试,发送成功 返回: array(3) { ["errcode"]=> int(0) ["errmsg"]=> string(2) "ok" ["msgid"]=> int(433240389) }
     */
2017-10-20 12:04:55 xiuwu0423 阅读数 3130
  • 用vue.js高效开发微信小程序

    原生小程序开发有哪些痛点? 频繁调用 setData及 setData过程中页面跳闪 强制将WXSS、WXML和JS代码分离到3个不同的文件中 没有状态管理,参考Vuex和Redux 没有过滤器 不能使用 less、scss 等预编译器 组件化支持能力太弱(几乎没有) 为什么使用第三方框架开发微信小程序? 只要熟悉vue或react即可快速上手,学习成本低 一套代码可在多端编译运行(微信,支付宝,h5,RN)  支付宝小程序暂不完善 组件化开发,完美解决组件隔离,组件嵌套,组件通信等问题 支持使用第三方 npm 资源 使小程序可支持 Promise,解决回调烦恼 可使用 Generator Fu-nction / Class / Async Function 等特性,提升开发效率 对小程序本身的优化,如生命周期的补充,性能的优化等等 支持样式编译器: Scss/Less,模板编译器,代码编译器:Babel/Typescript。 结论:为了组件化开发,核心就是为了高效开发

    303 人正在学习 去看看 何韬

正如许多推送一样,微信也友好的给广大开发者提供了“模板消息”,比推送更好的是,它能借助海量用户的微信平台直接通过服务号以短消息形式传达给用户,大大提高了运营的可能性。比如我们现在可以完全抛开银行卡的短信服务,通过相关银行提供服务号绑定银行卡,当发生交易的时候同样的能收到交易详情短消息,确实是方便了不少!

 

上一篇讲到了获取和缓存access_token,也成功配置了jssdk授权,这些前置条件都准备好了,那么同样的实现一些功能就很快了,这回具体来说说模板消息的发送

 

公众号平台配置

功能-我的模板(或者去模块库中搜索),这里不涉及到代码,不细说

 

 后台restful

实际项目中肯定会存在多种类型的模板,那么肯定需要做一些共用代码封装,我这里 以保单出单 这个模板为例

1,对应模板的信息

2.controller

复制代码
  /**
     * 发送模板消息
     * @return
     */
    @RequestMapping(value = "/sendTemplateMessage", method = RequestMethod.POST)
    public @ResponseBody HttpResult sendTemplateMessage(@RequestParam String dataJson){  
        HttpResult hr = null;
        LOGGER.info("RestFul of sendTemplateMessage parameters dataJson:{}",dataJson);
        
        try {
            hr = wechatService.sendTemplateMessage(dataJson);
            LOGGER.info("Send template message is successful!",hr);
        } catch (Exception e) {
            LOGGER.error("RestFul of sendTemplateMessage is error:{}",e);
        }
        
        return hr;
    }
复制代码

因为我这里是一个通用的接口,不同的模板可能传的参数都不同,时间缘故也没有写持久化bean对象,就用了一个json字符串接收

 

3.serveice

直接通过httpclient调用微信提供的POST请求,https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={ACCESS_TOKEN}

  //保单出单通知
    @Value("${TEMPLATE_THREE}")
    private String TEMPLATE_THREE;

 

复制代码
  /**
     * 发送模板消息
     * @param dataJson
     * @return
     * @throws IOException
     */
    public HttpResult sendTemplateMessage(String dataJson) throws IOException{
        String doUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+getBaseAccessToken();
        
        JSONObject data = JSONObject.parseObject(dataJson);
        Object touser = data.get("touser");//接收者openid
     String templateId = TEAMLATE_THREE;//模板ID Object url = data.get("url");//模板跳转链接,如果置空,则在发送后,点击模板消息会进入一个空白页面(ios),或无法点击(android)。
Object first = data.getString("first");//标题 Object remark = data.getString("remark");//备注 Object keyword1 = data.getString("keyword1"); Object keyword2 = data.getString("keyword2"); Object keyword3 = data.getString("keyword3"); JSONObject parentJSON = new JSONObject(); parentJSON.put("touser", touser);
     parentJSON.put("template_id", templateId); parentJSON.put("url", url); JSONObject json = new JSONObject(); json.put("first", toJson(first)); json.put("keyword1", toJson(keyword1));//对应的车辆信息 json.put("keyword2", toJson(keyword2));//产品信息 json.put("keyword3", toJson(keyword3));//出单状态json.put("remark", toJson(remark)); parentJSON.put("data", json);//模板数据 HttpResult rs = null; try { rs = apiService.doPostJson(doUrl,parentJSON.toJSONString()); } catch (Exception e) { LOGGER.error("RestFul of doLogin is error:{}",e); } return rs; } public JSONObject toJson(String value){ JSONObject json = new JSONObject(); json.put("value", value); json.put("color", "#173177");//消息字体颜色 return json; }
复制代码

为了增强代码可读性,关键字的地方我都添加了注释,那么到这里,后台基本完成,下面我通过一个接口调用工具Advanced REST client来测试一下

数据:

复制代码
{
  "touser": "otjo0wXJZipXdFjxzwDB3DZUjs44",
  "templateType": "3",
  "url": "www.liliangel.cn",
  "first": {
    "value": "测试发送模板消息3",
    "color": "#173177"
  },
  "keyword1": {
    "value": "testCar",
    "color": "#173177"
  },
  "keyword2": {
    "value": "testPro",
    "color": "#173177"
  },
  "keyword3": {
    "value": "successful",
    "color": "#173177"
  },
  "remark": {
    "value": "备注",
    "color": "#173177"
  }
}
复制代码

说明:我这里的数据结构是经过了一层json封装的,详细的格式可以参考微信官方文档及模板详情!

 

成功:

到这里我们已经完成了模板消息的接入,具体会不会比常用的推送更好? 会不会取代手机短信? 看怎么去运营,发挥你的想象,将微信提供的服务最大程度利用!

2018-05-15 15:56:36 sinat_36553913 阅读数 3619
  • 用vue.js高效开发微信小程序

    原生小程序开发有哪些痛点? 频繁调用 setData及 setData过程中页面跳闪 强制将WXSS、WXML和JS代码分离到3个不同的文件中 没有状态管理,参考Vuex和Redux 没有过滤器 不能使用 less、scss 等预编译器 组件化支持能力太弱(几乎没有) 为什么使用第三方框架开发微信小程序? 只要熟悉vue或react即可快速上手,学习成本低 一套代码可在多端编译运行(微信,支付宝,h5,RN)  支付宝小程序暂不完善 组件化开发,完美解决组件隔离,组件嵌套,组件通信等问题 支持使用第三方 npm 资源 使小程序可支持 Promise,解决回调烦恼 可使用 Generator Fu-nction / Class / Async Function 等特性,提升开发效率 对小程序本身的优化,如生命周期的补充,性能的优化等等 支持样式编译器: Scss/Less,模板编译器,代码编译器:Babel/Typescript。 结论:为了组件化开发,核心就是为了高效开发

    303 人正在学习 去看看 何韬

异常信息

通过后台调用微信接口发送模板消息时,一直出现此异常{“errcode”:40103,”errmsg”:”invalid industry index”}

解决方法

检查调用API地址是否出错

http请求方式: POST
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN

开发时,出现后通过检查参数,查阅资料都无法解决,开发文档没有40103错误代码,最后同事发现调用接口错误。这类问题实在不应出现

2018-06-04 15:54:07 sinat_36553913 阅读数 4122
  • 用vue.js高效开发微信小程序

    原生小程序开发有哪些痛点? 频繁调用 setData及 setData过程中页面跳闪 强制将WXSS、WXML和JS代码分离到3个不同的文件中 没有状态管理,参考Vuex和Redux 没有过滤器 不能使用 less、scss 等预编译器 组件化支持能力太弱(几乎没有) 为什么使用第三方框架开发微信小程序? 只要熟悉vue或react即可快速上手,学习成本低 一套代码可在多端编译运行(微信,支付宝,h5,RN)  支付宝小程序暂不完善 组件化开发,完美解决组件隔离,组件嵌套,组件通信等问题 支持使用第三方 npm 资源 使小程序可支持 Promise,解决回调烦恼 可使用 Generator Fu-nction / Class / Async Function 等特性,提升开发效率 对小程序本身的优化,如生命周期的补充,性能的优化等等 支持样式编译器: Scss/Less,模板编译器,代码编译器:Babel/Typescript。 结论:为了组件化开发,核心就是为了高效开发

    303 人正在学习 去看看 何韬

说明

      在公司微信项目开发中,我主要负责消息中心的模板消息接口设计实现。主要是将微信公众号的推送模板消息功能放到公司的消息中心系统中,微信后台项目通过RMI调用接口,实现推送功能。在这里记录总结下当时的设计实现思路。
      公司使用Hessian实现RMI,所以接口设计为一个Hessian接口。

正文

接口的设计要先确定接口的名称,参数,返回值。这里由于考虑到了今后的扩展,可能会运营不止一个公众号,所以在接口参数的设计上,比较具体。

public interface IWechatMessageService {

    public int sendTempMsg(String spid, Integer templateId, String data);
}

通过阅读微信开发文档,我们知道了在推送模板消息时,必须要有Access_Token,templateid,openId和要发送的数据,这些数据组成一个json串发送给微信服务器,微信服务器返回一个json数据包包含对应的信息。

消息推送的设计思路:
      通过微信后台项目远程调用接口,公众号的id,模板的id(id均为在表中的主键),还有推送的数据作为参数传递,消息中心接受到参数后,首先会根据公众号id判断是否存在该公众号信息,再根据模板id判断是否存在模板信息,若都不存在问题,则会将这些参数数据组装成一个DTO,放入消息队列,此时接口方法执行完毕,返回状态码。放入消息队列后,有队列订阅者对消息进行处理(即发送数据到微信服务器),至此推送模板消息的流程结束。

在以上的思路中有几个问题:

  1. 对于公众号和模板信息的校验,这些信息都存储在数据库中,每次调用接口校验时都是否都需要从数据库查询数据?
  2. 对于推送的数据,这个参数是多用户数据,也就是一次传递一批用户的数据,每个用户推送的内容不同,所以数据格式为JsonArray,在往消息队列中投递的时候,是一次投递一批,一个DTO,还是将每个用户的数据查分组成DTO,进行投递?
  3. 消息数据进入队列后,如何消费,该怎样向微信服务器发送数据?
  4. 在推送时,需要Access_Token,这个token与微信后台项目是共用的,而且token是有有效期的,在过期时导致消息推送失败,该如何处理?

接下来针对每个问题记录下当时的解决方法:

1. 公众号、模板标识的校验

      在发送模板消息时,需要Access_Token和对应的模板id,Access_Token的获取需要公众号的appid和secret,定制模板需要向微信官方申请,而且是人工审核,十分严格。当运营不止一个公众号时就需要通过表记录公众号的信息,而且推送的内容不同使用的模板不同,也需要表记录使用的模板信息。所以传递的参数都应该在表中有记录,为了避免每次校验时查询数据库,这里采用了将公众号信息和模板信息加载到内存的方式。具体是使用一个监听器,在项目启动时,从数据库查询数据并存储到内存中,这里使用HashMap作为存储的数据结构,key为主键 value为对应的实体类对象。

//加载模板信息
private static Map<Integer,Template> ALL_TEMPLATE = new HashMap<Integer, Template>();
List<Template> templateList = templateService.getAll();
if (templateList != null && templateList.size() > 0) {
    for (Template template : templateList) {
        nteger templateid = template.getTemplateid();
        ALL_TEMPLATE.put(templateid,template);
    }
}

2.关于多用户数据的投递

在开始设计时,我采用的是直接将数据组装成一个DTO,发送到队列中,消费时是接受一批的用户数据,在发送时对数据做处理,拆分出每个用户的数据,逐个发送。不过在审查的时候,主管说要提前拆分,将单个用户的DTO用list存储,调用方法以list形式发送,消费时以多线程的方式进行发送。为什么要这么做?主管告诉我这样可以防止数据的大批量丢失,如果一次以单个DTO放到队列,当队列出现问题时,会丢很多数据。公司使用的是RabbitMQ,并且封装了管理类RabbitmqManager,通过阅读源码发现,当以list形式发送时,在方法内部还是循环一个个发送到队列中。

//将一批消息中的所有用户消息以list放入队列
        List<WechatTempMsgDTO> dataList = new ArrayList<WechatTempMsgDTO>();
        for(int i = 0; i < dataArray.size(); i++){
            JSONObject dataOfUser = dataArray.getJSONObject(i);
            WechatTempMsgDTO wechatTempMsgDTO = new WechatTempMsgDTO(accessToken,templateId,dataOfUser.toJSONString(),batchid,0,spid);
            dataList.add(wechatTempMsgDTO);
        }

        int status = queueManager.sendMessageObject(GlobalVars.WECHAT_QUEUE_NAME,dataList);

发送到队列的源码

    public <T> int sendMessageObject(String queueName, List<T> messgaes) {
        try {
            MessageProperties properties = messgaes != null && messgaes.size() > 0 ? this.getTypeProperties(messgaes.get(0).getClass()) : null;
            Iterator it = messgaes.iterator();

            while(it.hasNext()) {
                T expectedBody = it.next();
                this.mqTemplate.convertAndSend(queueName, new Message(JSON.toJSONString(expectedBody).getBytes(Charsets.UTF_8), properties));
            }

            return 1;
        } catch (Exception var6) {
            return -1;
        }
    }

3.队列信息消费

这里使用了消息队列的发布订阅模式,通过一个监听器,在项目启动时订阅队列,等待从队列中获取消息,进行处理。这里采用了多线程的方式进行推送,通过阅读源码,发现多线程的实现方式是一个订阅者subscribe有一个线程池,每个线程代表一个消费者consumer

订阅部分的实现源码

queueManager.subscribeQueue(GlobalVars.WECHAT_QUEUE_NAME, new QueueSubscriber<WechatTempMsgDTO>() {
            @Override
            public void onGetMessage(QueueContext<WechatTempMsgDTO> queueContext) {
                WechatTempMsgDTO wechatTempMsgDTO = queueContext.getMessage();
                //此时接收到的是单个用户信息的DTO
                sendMessgae(wechatTempMsgDTO);
            }
        },GlobalVars.WECHAT_CONCURRENT_THREAD_NUM,1);

订阅多线程消费的实现源码

Connection conn = this.factory.newConnection();
ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
((Map)subToThread).put(subscriber, threadPool);

for(int i = 0; i < threadCount; ++i) {
     RabbitConsumer<T> consumer = new RabbitConsumer(conn, queueName, subscriber, maxCount);
     consumer.init();
     threadPool.submit(consumer);
}

4.关于Access_Token的使用

阅读微信开发文档,了解到Access_Token只有两个小时的有效期,且重复获将导致上次获取的token失效。由于公司中的消息系统与微信项目是两个不同的项目,这就导致了token的共享问题及token失效时的获取问题。
这里使用了redis缓存,将token存入到redis中并设置有效期,解决共享问题。
在失效获取时,问题在队列消费推送时可能会出现消息没有推送完,token过期剩余所有消息推送失败的问题,此时就需要消息系统去主动获取token,而此时如果微信项目也同时请求,就会有竞争造成一方失效。为解决此问题使用了分布式锁,用redis实现分布式锁,思路是当token失效时,先从redis缓存中获取,如果没有则设置锁,使用了redis的setnx命令,若成功设置获得锁,则先设置锁的有效期避免死锁,再进行请求token,请求成功后将token存入缓存设置有效期,释放锁返回token;若获取锁失败,则循环获取,仍先从redis中获取。这里要避免死循环,如果是相关参数错误,则会一直获取不到造成死循环。在编写代码时,我觉得这部分锁的设计仍然有缺陷,锁的可重入性并没有得到保证,这里需不需要可重入性?如果是利用redis设计实现一个锁工具,就应该保证可重入性。希望大家不吝赐教
参考资料:分布式锁的作用及实现(Redis)

   //使用redis实现分布式锁 获取Access_Token
    private String getAccessToken(final String spid, String appid, String secret){
        String res = "";
        final String url = GlobalVars.GET_ACCESS_TOKEN_URL.replace("APPID",appid).replace("APPSECRET",secret);
        while("".equals(res)){
            res = tempmanager.getString(GlobalVars.WECHAT_ACCESS_TOKEN_CACHE_PREFIX + spid);
            if(Validator.isNotNull(res)){
               return res;
            }
            res = tempmanager.execute(new IRedisCacheManager.RedisExecutor<String>() {
                @Override
                public String execute(Jedis jedis) throws Exception {
                    String access_token = "";
                    String currentTimeMillis = String.valueOf(System.currentTimeMillis());
                    //加锁
                    Long result = jedis.setnx(GlobalVars.ACCESS_TOKEN_DISTRIBUTE_LOCK_KEY + spid,currentTimeMillis);
                    //加锁成功 进行请求 否则直接返回 循环获取
                    if(result == 1){
                        // 加锁成功后,必须设置锁的有效期,避免死锁
                        jedis.expire(GlobalVars.ACCESS_TOKEN_DISTRIBUTE_LOCK_KEY + spid,2);
                        String strResult = HttpUtil.getRequestURL(url,null);
                        JSONObject jsonResult = JSONObject.parseObject(strResult);
                        access_token = jsonResult.getString("access_token");
                        if(Validator.isNull(access_token)){
                            logger.error("请求Access_Token失败: " + jsonResult.toString());
                            return null;  //当请求微信服务器出错,返回错误的数据包 返回null跳出循环,避免死循环
                        }
                        jedis.set(GlobalVars.WECHAT_ACCESS_TOKEN_CACHE_PREFIX + spid,access_token);
                        //设置access_token缓存的有效期
                        jedis.expire(GlobalVars.WECHAT_ACCESS_TOKEN_CACHE_PREFIX + spid,GlobalVars.WECHAT_ACCESS_TOKEN_CACHE_EXPIRE);
                        //释放锁
                        jedis.del(GlobalVars.ACCESS_TOKEN_DISTRIBUTE_LOCK_KEY + spid);
                    }

                    return access_token;
                }
            });
        }
        logger.info("获取Access_Token为: " + res);

        return res;
    }

对于推送失败后,这里采用了重新放回对列进行重试的机制,重试次数限制为3次。当发送成功时会记录到send_success表中,最后一次重试发送失败则会记录到send_failed表中并记录错误信息

if(Integer.valueOf(code) == 0){
    //将发送成功的信息记录到成功表
    saveDataToSuccess(templateId,toUser,batchid,param,tryCount);
}else{
   //当错误代码代表access_token失效时,需要重新获取
   if(Integer.valueOf(code) == GlobalVars.WECHAT_ACCESS_TOKEN_INVALID){
       accessToken = accessTokenManager.getAccessToken(spid);
   }
   String msg = jsonResp.toString();
   //判断重复次数 若已超过限定次数 则记录到失败表,否则重新放入消息队列
   if(tryCount < GlobalVars.TRY_COUNT){
        sendToQueue(accessToken,templateId,batchid,dataOfUser.toJSONString(),tryCount + 1);
   }else{
        saveDataToFaild(templateId,toUser,batchid,param,tryCount,msg);
   }
}

最后感谢主管和同事们对我完成这次任务的帮助,让我学到了很多

其他优秀博文:
分布式锁的几种实现方式
分布式之消息队列复习精讲

2019-07-30 16:27:07 u014174854 阅读数 997
  • 用vue.js高效开发微信小程序

    原生小程序开发有哪些痛点? 频繁调用 setData及 setData过程中页面跳闪 强制将WXSS、WXML和JS代码分离到3个不同的文件中 没有状态管理,参考Vuex和Redux 没有过滤器 不能使用 less、scss 等预编译器 组件化支持能力太弱(几乎没有) 为什么使用第三方框架开发微信小程序? 只要熟悉vue或react即可快速上手,学习成本低 一套代码可在多端编译运行(微信,支付宝,h5,RN)  支付宝小程序暂不完善 组件化开发,完美解决组件隔离,组件嵌套,组件通信等问题 支持使用第三方 npm 资源 使小程序可支持 Promise,解决回调烦恼 可使用 Generator Fu-nction / Class / Async Function 等特性,提升开发效率 对小程序本身的优化,如生命周期的补充,性能的优化等等 支持样式编译器: Scss/Less,模板编译器,代码编译器:Babel/Typescript。 结论:为了组件化开发,核心就是为了高效开发

    303 人正在学习 去看看 何韬

企业微信开发第一步获取AccessToken,企业微信的AccessToken和公众号的不一样,企业微信所有接口调用只需要一个AccessToken,而公众号授权和jssdk是分开的

一、获取企业微信AccessToken


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bootdo.common.cache.RedisService;
import com.bootdo.common.utils.Errors;
import com.bootdo.wx.entity.WxAgent;
import com.bootdo.wx.mapper.WxAgentMapper;
import com.hyd.weixin.utils.HttpSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 处理企业微信AccessToken
 * @author laowang
 * @email open@xcloud.com
 */
@Slf4j
@Service
public class WeixinAccessTokenService {

    //企业微信
    public static final String URL_PATTERN2 = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" +
            "?corpid={{APPID}}&corpsecret={{SECRET}}";

    @Autowired
    WxAgentMapper wxAgentMapper;

    @Autowired
    RedisService redisService;

    public String getEnterpriseAccessToken(String account) throws IOException {
        String accessToken = "";
        WxAgent wxAgent = selectAgent(account);
        if (wxAgent == null) {
            throw Errors.Account_MissingAccount.ex(account);
        }
        String url = URL_PATTERN2
                .replace("{{APPID}}", wxAgent.getCorpid())
                .replace("{{SECRET}}", wxAgent.getAgentSecret());
        if(redisService.keyExists("enterprise_"+wxAgent.getAgentId())){
            accessToken = redisService.get("enterprise_"+wxAgent.getAgentId());
        }else{
            HttpSender httpSender = new HttpSender(url);
            String json = httpSender.sendAndGetResponse();
            JSONObject jsonObject = JSON.parseObject(json);
            if (jsonObject.getInteger("errcode")==0) {
                accessToken = jsonObject.getString("access_token");
                redisService.set("enterprise_"+wxAgent.getAgentId(),accessToken,7200, TimeUnit.SECONDS);
            }else{
                log.error("企业号获取AccessToken错误 原因{}",jsonObject.getString("errmsg"));
            }
        }
        return accessToken;
    }

    public WxAgent selectAgent(String account) {
        return wxAgentMapper.selectWxAgent(account);
    }
}

注:这里获取AccessToken根据自己业务来,做好AccessToken缓存

二、对所有消息类型的枚举

public interface WxTempConstant {

	public final String TEMP_TEXT = "text";

	public final String TEMP_IMAGE = "image";

	public final String TEMP_VOICE = "voice";

	public final String TEMP_VIDEO = "video";

	public final String TEMP_FILE = "file";

	public final String TEMP_TEXT_CARD = "textcard";

	public final String TEMP_NEWS = "news";

	public final String TEMP_MPNEWS = "mpnews";

	public final String TEMP_MARKDOWN = "markdown";

	public final String TEMP_MINIPROGEAM_NOTICE = "miniprogram_notice";

	public final String TEMP_TASK_CARD = "taskcard";

}

三、封装消息节点结构接口

import java.util.List;
import java.util.Map;

/**
 * (description)
 * created at 2017/9/30
 *
 * @author laowang
 */
public interface MapInitializer<K, V> {

    void init(Map<K, V> m);

    void initListMap(List<Map<K, V>> m);
}

四、封装消息发送类

package com.bootdo.wx.service.wechat;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bootdo.common.utils.MapInitializer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
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.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 处理企业微信消息推送操作
 *
 * @author laowang
 * @email open@xcloud.com
 */
@Slf4j
@Service
public class WeixinMessageService {

    //企业微信
    public static final String URL_PATTERN = "https://qyapi.weixin.qq.com/cgi-bin/message/send" +
            "?access_token={{ACCESS_TOKEN}}";

    @Autowired
    WeixinAccessTokenService weixinAccessTokenService;

    /**
     * 推送企业消息给用户
     *
     * @param agentId     应用ID
     * @param touser      成员ID列表(消息接收者,多个接收者用‘|’分隔,最多支持1000个)。特殊情况:指定为@all,则向关注该企业应用的全部成员发送
     * @param toparty     部门ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为@all时忽略本参数
     * @param totag       标签ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为@all时忽略本参数
     * @param msgtype     消息类型
     * @param initializer 消息主体参数参考 https://work.weixin.qq.com/api/doc#90000/90135/90236
     */
    public void send(String msgtype, String touser, String toparty, String totag, MapInitializer<String, String> initializer, String agentId) {

        log.info("企业应用{}发送{}消息给{}", agentId, msgtype, touser);
        //准备构造json
        JSONObject firstjsonObject = new JSONObject();
        JSONObject msgtypejsonObject = new JSONObject();
        //图文类json构造
        JSONArray jsonArray = new JSONArray();
        JSONObject secandjsonObject = new JSONObject();
        //填充消息参数
        Map<String, String> map = new HashMap<>();
        //填充图文类多节点参数
        List<Map<String, String>> list = new ArrayList<>();
        //构造通用消息参数json
        firstjsonObject.put("touser", touser);
        firstjsonObject.put("toparty", toparty);
        firstjsonObject.put("totag", totag);
        firstjsonObject.put("msgtype", msgtype);
        firstjsonObject.put("agentid", agentId);
        switch (msgtype) {
            case "text":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("text", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "image":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("image", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "voice":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("voice", msgtypejsonObject);
                break;
            case "video":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("video", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "file":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("file", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "textcard":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("textcard", msgtypejsonObject);
                break;
            case "news":
                initializer.initListMap(list);
                //构造消息主体json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("articles", jsonArray);
                firstjsonObject.put("news", msgtypejsonObject);
                break;
            case "mpnews":
                initializer.initListMap(list);
                //构造消息主体json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("articles", jsonArray);
                firstjsonObject.put("mpnews", msgtypejsonObject);
                break;
            case "markdown":
                initializer.init(map);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("markdown", msgtypejsonObject);
                break;
            case "miniprogram_notice":
                initializer.init(map);
                initializer.initListMap(list);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                //构造消息节点json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("content_item", jsonArray);
                firstjsonObject.put("miniprogram_notice", msgtypejsonObject);
                break;
            case "taskcard":
                initializer.init(map);
                initializer.initListMap(list);
                //构造消息主体json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                //构造消息节点json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("btn", jsonArray);
                firstjsonObject.put("taskcard", msgtypejsonObject);
                break;
        }
        sendTo(agentId, firstjsonObject.toJSONString());
    }

    /**
     * 推送消息
     * @param agentId
     * @param tempdata
     */
    public void sendTo(String agentId, String tempdata) {
        String accessToken = null;
        try {
            accessToken = weixinAccessTokenService.getEnterpriseAccessToken(agentId);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String url = URL_PATTERN.replace("{{ACCESS_TOKEN}}", accessToken);
        JSONObject jsonObject = post(url, tempdata);
        if (!StringUtils.isEmpty(jsonObject.toJSONString()) && jsonObject.getInteger("errcode") == 0) {
            log.info("消息推送成功");
        } else {
            log.info("消息推送失败 原因:{}", jsonObject.toJSONString());
        }
    }

    /**
     * POST请求的RAW参数传递
     *
     * @param url
     * @param body
     * @return
     */
    public static JSONObject post(String url, String body) {
        JSONObject jsonObject = null;
        CloseableHttpClient httpClient = null;
        HttpPost httpPost = null;
        try {
            httpClient = HttpClients.createDefault();
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(20000).setConnectTimeout(20000).build();
            httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
            httpPost.setEntity(new StringEntity(body, "utf-8"));
            CloseableHttpResponse response = httpClient.execute(httpPost);
            HttpEntity httpEntity = response.getEntity();
            jsonObject = JSONObject.parseObject(EntityUtils.toString(httpEntity, "utf-8"));
            return jsonObject;
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            return jsonObject;
        }
    }
}

五、各类型消息推送单元测试(消息主体参数参考 https://work.weixin.qq.com/api/doc#90000/90135/90236)

package com.bootdo;


import com.bootdo.common.utils.MapInitializer;
import com.bootdo.common.utils.wechat.WxTempConstant;
import com.bootdo.wx.service.wechat.WeixinMessageService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class BootdoApplicationTests {

    @Autowired
    WeixinMessageService weixinMessageService;

    @Test
    public void contextLoads() throws IOException {

        final String agentId = "1000002";
        final String touser = "WangLinJun";
        final String toparty = "";
        final String totag = "";

        //文本消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_TEXT, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("content", "你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看<a href=\"http://work.weixin.qq.com\">邮件中心视频实况</a>,聪明避开排队。");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //图片消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_IMAGE, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("media_id", "MEDIA_ID");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //语音消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_VOICE, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("media_id", "MEDIA_ID");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //视频消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_VIDEO, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("media_id", "MEDIA_ID");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //文件消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_FILE, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("media_id", "MEDIA_ID");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //文本卡片消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_TEXT_CARD, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("title", "领奖通知");
                            m.put("description", "<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>");
                            m.put("url", "URL");
                            m.put("btntxt", "更多");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //图文消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_NEWS, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                            Map<String, String> m1 = new HashMap<>();
                            m1.put("title", "中秋节礼品领取1");
                            m1.put("description", "今年中秋节公司有豪礼相送");
                            m1.put("url", "http://www.baidu.com");
                            m1.put("picurl", "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png");
                            m.add(m1);

                            Map<String, String> m2 = new HashMap<>();
                            m2.put("title", "中秋节礼品领取2");
                            m2.put("description", "今年中秋节公司有豪礼相送");
                            m2.put("url", "http://www.baidu.com");
                            m2.put("picurl", "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png");
                            m.add(m2);
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //图文消息(mpnews)
        try {
            weixinMessageService.send(WxTempConstant.TEMP_MPNEWS, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                            Map<String, String> m1 = new HashMap<>();
                            m1.put("title", "Title");
                            m1.put("thumb_media_id", "MEDIA_ID");
                            m1.put("author", "Author");
                            m1.put("content_source_url", "URL");
                            m1.put("content", "content");
                            m1.put("digest", "Digest description");
                            m.add(m1);

                            Map<String, String> m2 = new HashMap<>();
                            m2.put("title", "Title");
                            m2.put("thumb_media_id", "MEDIA_ID");
                            m2.put("author", "Author");
                            m2.put("content_source_url", "URL");
                            m2.put("content", "content");
                            m2.put("digest", "Digest description");
                            m.add(m2);
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //markdown消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_MARKDOWN, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("content", "markdown content");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //小程序通知消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_MINIPROGEAM_NOTICE, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("appid", "appid");
                            m.put("page", "pages/index?userid=zhangsan&orderid=123123123");
                            m.put("title", "会议室预订成功通知");
                            m.put("description", "appid");
                            m.put("emphasis_first_item", "true");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                            Map<String, String> m1 = new HashMap<>();
                            m1.put("key", "会议室");
                            m1.put("value", "402");
                            m.add(m1);

                            Map<String, String> m2 = new HashMap<>();
                            m2.put("key", "会议室1");
                            m2.put("value", "403");
                            m.add(m2);
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

        //任务卡片消息
        try {
            weixinMessageService.send(WxTempConstant.TEMP_TASK_CARD, touser, toparty, totag,
                    new MapInitializer<String, String>() {
                        @Override
                        public void init(Map<String, String> m) {
                            m.put("appid", "appid");
                            m.put("page", "pages/index?userid=zhangsan&orderid=123123123");
                            m.put("title", "会议室预订成功通知");
                            m.put("description", "appid");
                            m.put("emphasis_first_item", "true");
                        }

                        @Override
                        public void initListMap(List<Map<String, String>> m) {
                            Map<String, String> m1 = new HashMap<>();
                            m1.put("key", "会议室");
                            m1.put("value", "402");
                            m.add(m1);

                            Map<String, String> m2 = new HashMap<>();
                            m2.put("key", "会议室1");
                            m2.put("value", "403");
                            m.add(m2);
                        }
                    }, agentId);
        } catch (Exception e) {
            log.error("发送模板消息失败: " + e.getMessage());
            e.printStackTrace();
        }

    }
}

以上代码请根据自己的业务进行更改

微信模板消息推送

博文 来自: bao_smart

微信模板消息

阅读数 30

没有更多推荐了,返回首页