• *公司需要,学了一下微信公众号开发,写这篇博客的时候已经开发结束半个月了 *回来记录一下,采用php语言(现学的)进行开发,不足之处还望提出来共同探讨 */ (原创不易,转载请注明出处!) 提示:目前微信...

    /*

    *公司需要,学了一下微信公众号开发,写这篇博客的时候已经开发结束半个月了

    *回来记录一下,采用php语言(现学的)进行开发,不足之处还望提出来共同探讨

    */


    (原创不易,转载请注明出处!)


    提示:目前微信公众号包含订阅号和服务号两种。详细区别官网有介绍,这里要说的是订阅号如果自己配置服务器url的话,是没有自定义菜单栏功能的,因此配置服务器url后只能接受用户的手动输入内容。而服务号需要企业认证,个人无法申请。


    一、微信开发环境搭建

    1.申请和配置微信公众平台

        微信公众平台已经提供了一个很便捷的开发配置方式,这里不在赘述,展示一下配置完的画面,

        切记,图中画红色圈的地方一定要点击启用,否则微信客户端的请求无法发送到我们配置的服务器地址。我在这里浪费了一早上的时间,醉了。。。。




    2.微信请求的传递流程

        稍微解释一下,微信的服务器只是起到转发的作用,将手机客户端的请求转发到我们配置的服务器地址,就是上图看到URL地址。


    3.服务器地址文件的编写

        这个才是开发的重点啊,下面先贴上本人编写的源码模板,可以直接使用的。

        首先是微信界面菜单栏设计的请求,将下面这个php文件放进随便一个php环境运行一下就可以了,目的是让它发出请求就好了。(订阅号配置url后没有权限,会返回失败

    <?php
    
    /* 以下两项可登陆微信公众平台查看 */
    $appid = "******************";
    $appsecret = "******************";
    
    /* 菜单设计 */
    $menu = array(
    	'button' => array(
    		array('name'=>'菜单一','sub_button'=>array(
                    array('type'=>'click', 'name'=>'二级菜单', 'key'=>'13'),
                    array('type'=>'view','name'=>'百度','url'=>'www.baidu.com'),
    			)
    		),
    		array('name'=>'菜单二','sub_button'=>array(
                array('type'=>'view','name'=>'卡种查询','url'=>'www.baidu.com'),
    			array('type'=>'click', 'name'=>'百度', 'key'=>'21'),
    			)
    		),
    		array('name'=>'菜单三','sub_button'=>array(
    			array('type'=>'view', 'name'=>'百度', 'url'=>'www.baidu.com'),
    			)
    		),
    		)
    	);
        
    /* 发起更新菜单用的curl请求 */
    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;
    }
    
    /* 去除php版本的影响 */
    function json_encode_ex($value)
    {
        if (version_compare(PHP_VERSION,'5.4.0','<'))
        {
            $str = json_encode($value);
            $str = preg_replace_callback(
                "#\\\u([0-9a-f]{4})#i",
                function($matchs)
                {
                     return iconv('UCS-2BE', 'UTF-8', pack('H4', $matchs[1]));
                },
                 $str
                );
            return $str;
        }
        else
        {
            return json_encode($value, JSON_UNESCAPED_UNICODE);
        }
    }
    
    /* 发起请求 */
    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$appsecret}";
    $result = https_request($url);
    
    $jsoninfo = json_decode($result, true);
    $access_token = $jsoninfo["access_token"];
    $url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=".$access_token;
    $jsonmenu = json_encode_ex($menu);
    $result = https_request($url, $jsonmenu);
    var_dump($result);
    
    ?>

    接下来是前面配置的服务器url地址文件,就是那个index.php:(token要改成自己的

    <?php
    
    require "./menu.class.php";                 //引进回复的文件
    define("TOKEN", "******");                  //设置token常量,这里是*号,要改成你自己在微信公众号平台设置的token
    
    $wechatObj = new wechatCallbackapiTest();   //实例化wechatCallbackapiTest对象
    //$wechatObj->valid();                      //验证消息来自微信服务器第一次使用就行
    $wechatObj->responseMsg();                  //处理消息
    
    
    class wechatCallbackapiTest
    {
    
        /**
        * 接受处理微信服务器发送过来的消息
        */
        public function responseMsg()
        {
            /*获取post数据(XML类型)*/
    		$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
    
    		if(!empty($postStr)){//判断获取到的XML数据是否为空
                    
                /* 解析XML数据 */
                $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
                
                /* 获取发信方的用户名 */
                $fromUsername = $postObj->FromUserName;
                
                /* 获取消息类型(event 事件 | text 文本)*/
                $RX_TYPE = trim($postObj->MsgType);
                
                /* 根据 消息类型 进行跳转 */
                switch ($RX_TYPE)
                {
                    //点击事件
                    case "event":
                        $resultStr = $this->receiveEvent($postObj, $user);
                        break;
                        
                    //文本输入事件
                    case "text":
                        $resultStr = $this->receiveText($postObj, $user);
                        break;
                        
                    //其他
                    default:
                        $resultStr = "unknow msg type: ".$RX_TYPE;
                        break;
                }
            }else{
                //XML数据为空,do nothing...
            }
        }
        
        /**
        * 点击事件接口
        */
        protected function receiveEvent($postObj, $user){
            
            /* 获取收发方 */
            $fromUsername = $postObj->FromUserName;     //得到发送方名称
            $toUsername = $postObj->ToUserName;         //得到接收方的名称
            
            /*event事件的类型(subscribe 新用户订阅事件 | CLICK 点击回复请求 | VIEW 界面请求)*/
            $event_type = trim($postObj->Event);
    
            /* 生成一个menu对象,定义在menu.calss.php文件 */
            $menu=new menu();
            
            /*事件处理*/
            if($event_type == "subscribe"){//新用户订阅事件处理
                $sendData = array(
                    "act"           => "asyn_send_adduser" ,
                    "bind_openid"   => $fromUsername ,
                    "bind_key"      => '147258' ,
                );
                $menu->Welcome_Words($fromUsername,$toUsername);//新用户欢迎词(我封装好的接口,后面会附上)
            }
            else if($event_type == "CLICK")
            {
                switch (trim($postObj->EventKey))//对用户请求的key进行筛选
                {
                    case "13":
                        $data="Hi,你好~";
                        $result=$menu->Output_Text_Message($data,$fromUsername,$toUsername);
                        echo $result;
                        break;
                    case "21":
                        $data="Hi,你好~";
                        $result=$menu->Output_Text_Message($data,$fromUsername,$toUsername);
                        echo $result;
                        break;
                    default :
                        $data = "没有该菜单";	
                        $result=$menu->Output_Text_Message($data,$fromUsername,$toUsername);
                        echo $result;
                        break;
                }
                exit(1);
            }
        }
        
        
        /**
        * 文本消息事件接口
        */
        protected function receiveText($postObj, $user){
            
            /* 获取收发方 */
            $fromUsername = $postObj->FromUserName;     //得到发送方名称
            $toUsername = $postObj->ToUserName;         //得到接收方的名称
    
            /*获取当前时间*/
            $time = time();
            
            /* 生成一个menu对象,定义在menu.calss.php文件 */
            $menu=new menu();
            
            
            /*消息内容处理*/
            if(is_numeric($keyword))//用户输入了纯数字
            {
                $data="Hi,你输入了数字!";
                $result=$menu->Output_Text_Message($data,$fromUsername,$toUsername);
                echo $result;
            }
            else {//其他情况
                $data="Hi,你输入了其他内容!";
                $result=$menu->Output_Text_Message($data,$fromUsername,$toUsername);
                echo $result;
            }
        }
        
        
        /**
        * 验证消息是否来自微信服务器(入口)
        */
        protected function valid()
        {
            $echoStr = $_GET["echostr"];    //收集get数据(随机字符串)
            if($this->checkSignature()){    //判断checkSignature()自定义函数结果(验证微信服务器)
                echo $echoStr;              //为真则输出结果(随机字符串)
            }
        }
    
        
        /**
        * 验证消息是否来自微信服务器
        */
        private function checkSignature()
        {       
            $signature = $_GET["signature"];                //微信加密签名
            $timestamp = $_GET["timestamp"];                //时间戳
            $nonce = $_GET["nonce"];                        //随机数         
            $token = TOKEN;                                 //token与上面对应
            $tmpArr = array($token, $timestamp, $nonce);    //组装创建数组
            sort($tmpArr, SORT_STRING);                     //把$tmpArr做字符串处理并排序(升序)
            $tmpStr = implode( $tmpArr );                   //将数组变成字符串
            $tmpStr = sha1( $tmpStr );                      //计算字符串散列   
            if( $tmpStr == $signature ){    //判断是否一致
                return true;                //符合返回真
            }else{
                return false;               //否则返回假
            }
        }
        
    }
    
    ?>
    接下来是我封装好的menu类:

    <?php
    class menu
    {
    	/* 文本输出 */
    	function Output_Text_Message($data,$fromUsername,$toUsername){
    		$time = time();
    		$msgType="text";
    		$textTpl = "<xml>
    					<ToUserName><![CDATA[%s]]></ToUserName>
    					<FromUserName><![CDATA[%s]]></FromUserName>
    					<CreateTime>%s</CreateTime>
    					<MsgType><![CDATA[%s]]></MsgType>
    					<Content><![CDATA[%s]]></Content>
    					<FuncFlag>0</FuncFlag>
    					</xml>";
    		$resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $data);
            return $resultStr;
    	}
        
        /* 新用户关注欢迎词 */
        function Welcome_Words($fromUsername,$toUsername)
        {
            $data = "欢迎关注福建通联支付,点击菜单进入更多功能哦~".$this->bytes_to_emoji(0x1F604);
            $resultStr=$this->Output_Text_Message($data,$fromUsername,$toUsername);
    		echo $resultStr;
        }
        
        /* 错误信息提示 */
    	function Input_Error($fromUsername,$toUsername)
    	{
    		$data="输入内容有误,请检查格式,重新输入哈~~~";
    		$show=$this->Output_Text_Message($data,$fromUsername,$toUsername);
    		return $show;
    
    	}
        
        /* 字节转Emoji表情 */
        function bytes_to_emoji($cp)
        {
            if ($cp > 0x10000){       # 4 bytes
                return chr(0xF0 | (($cp & 0x1C0000) >> 18)).chr(0x80 | (($cp & 0x3F000) >> 12)).chr(0x80 | (($cp & 0xFC0) >> 6)).chr(0x80 | ($cp & 0x3F));
            }else if ($cp > 0x800){   # 3 bytes
                return chr(0xE0 | (($cp & 0xF000) >> 12)).chr(0x80 | (($cp & 0xFC0) >> 6)).chr(0x80 | ($cp & 0x3F));
            }else if ($cp > 0x80){    # 2 bytes
                return chr(0xC0 | (($cp & 0x7C0) >> 6)).chr(0x80 | ($cp & 0x3F));
            }else{                    # 1 byte
                return chr($cp);
            }
        }
    }
    ?>
    到此,一个完整的微信公众平台就搭建完成啦~


    这个只是一个基本的框架,微信公众号还有很多的功能,这里就不一一描述啦,详细的可以看官方的文档,上链接:

    微信公众号开发官方文档


    有什么错误的还希望大伙儿提出来探讨一下哈~~~

    (原创不易,转载请注明出处!)


    展开全文
  • 微信开发之消息模板

    2017-10-20 12:05:38
    正如许多推送一样,微信也友好的给广大开发者提供了“模板消息”,比推送更好的是,它能借助海量用户的微信平台直接通过服务号以短消息形式传达给用户,大大提高了运营的可能性。比如我们现在可以完全抛开银行卡的...

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

     

    上一篇讲到了获取和缓存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封装的,详细的格式可以参考微信官方文档及模板详情!

     

    成功:

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

    展开全文
  • 微信公众平台模板消息开发是子恒老师《子恒说微信开发》视频教程的第14部。详细讲解了用php开发微信公众平台模板消息。内容包含设置模板所属行业,获取微信模板ID,用公众号发送微信模板消息等等。欢迎反馈,微信号...
  • 在我们做微信公众号开发时,发送模板消息往往是必不可少的功能。今天我们就来说说吧! 1、申请模板消息 首先我们应该知道,模板消息是需要申请的。这个申请就其本身来说是很easy的(我前一晚上申请的,显示需要2...

    在我们做微信公众号开发时,发送模板消息往往是必不可少的功能。今天我们就来说说吧!

    1、申请模板消息

    首先我们应该知道,模板消息是需要申请的。这个申请就其本身来说是很easy的(我前一天晚上申请的,显示需要2--3个工作日,结果第二天早上就发现已经开通了,所以说腾讯官方还是比较给力的哈)。

    但是我们在申请时还是有一些东西要注意,这个在官方的文档有非常详细的说明。

    这个我建议你好好看看。选择行业的时候可要谨慎些,因为这个一个月只可以修改一次。

    那么,我们来看看在哪里申请?

    这里我已经申请过了。

    申请之后就耐心等待,审核通过之后再功能这一栏里就会出现模板消息的菜单。你可以看看我上面的截图,就在第三项。

    2、添加模板消息

    审核通过之后,我们就可以添加模板消息,进行开发了。

    这个很简单:

    我们点击模板消息进入后,直接在模板库中选择你需要的消息模板添加就可以了,添加之后就会在我的模板中。会有一个模板id,这个模板id在我们发送消息的时候会用到。

    3、消息发送功能开发

    接下来我们就看看如何发送模板消息:

    这个是官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277

    我呢,也来说说我的实现吧。为了更方便,我会直接将相关代码贴出来。

    文档中我们可以看到接口地址如下:

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

    这里我们首先需要的就是access_token了,这个在这里就不多说了。通过你的appid和secret就可以获取。

    【获取access_token : https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

    关于相关参数,我直接就将官方文档贴来了(文档写的很清楚):

    POST数据示例如下:

          {
               "touser":"OPENID",
               "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
               "url":"http://weixin.qq.com/download",  
               "miniprogram":{
                 "appid":"xiaochengxuappid12345",
                 "pagepath":"index?foo=bar"
               },          
               "data":{
                       "first": {
                           "value":"恭喜你购买成功!",
                           "color":"#173177"
                       },
                       "keyword1":{
                           "value":"巧克力",
                           "color":"#173177"
                       },
                       "keyword2": {
                           "value":"39.8元",
                           "color":"#173177"
                       },
                       "keyword3": {
                           "value":"2014年9月22日",
                           "color":"#173177"
                       },
                       "remark":{
                           "value":"欢迎再次购买!",
                           "color":"#173177"
                       }
               }
           }
    

    参数说明

    参数 是否必填 说明
    touser 接收者openid
    template_id 模板ID
    url 模板跳转链接(海外帐号没有跳转能力)
    miniprogram 跳小程序所需数据,不需跳小程序可不用传该数据
    appid 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
    pagepath 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),暂不支持小游戏
    data 模板数据
    color 模板内容字体颜色,不填默认为黑色

    注:url和miniprogram都是非必填字段,若都不传则模板无跳转;若都传,会优先跳转至小程序。开发者可根据实际需要选择其中一种跳转方式即可。当用户的微信客户端版本不支持跳小程序时,将会跳转至url。

    返回码说明

    在调用模板消息接口后,会返回JSON数据包。正常时的返回JSON数据包示例:

        {
               "errcode":0,
               "errmsg":"ok",
               "msgid":200228332
           }

    相信看完以上文档,基本上没有什么问题了。

    以下是我的部分代码:

    // 获取token
            String token = saveAndFlushAccessTokenUtil.getToken();
    
            String postUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + token;
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("touser", "发送到用户的openid");   // openid
            jsonObject.put("template_id", "你的模板id");
            jsonObject.put("url", "http://www.baidu.com");
    
            JSONObject data = new JSONObject();
            JSONObject first = new JSONObject();
            first.put("value", "hello");
            first.put("color", "#173177");
            JSONObject keyword1 = new JSONObject();
            keyword1.put("value", "hello");
            keyword1.put("color", "#173177");
            JSONObject keyword2 = new JSONObject();
            keyword2.put("value", "hello");
            keyword2.put("color", "#173177");
            JSONObject keyword3 = new JSONObject();
            keyword3.put("value", "hello");
            keyword3.put("color", "#173177");
            JSONObject remark = new JSONObject();
            remark.put("value", "hello");
            remark.put("color", "#173177");
            
            data.put("first",first);
            data.put("keyword1",keyword1);
            data.put("keyword2",keyword2);
            data.put("keyword3",keyword3);
            data.put("remark",remark);
    
            jsonObject.put("data", data);
    
            String string = HttpClientUtils.sendPostJsonStr(postUrl, jsonObject.toJSONString());
            JSONObject result = JSON.parseObject(string);
            int errcode = result.getIntValue("errcode");
            if(errcode == 0){
                // 发送成功
                System.out.println("发送成功");
            } else {
                // 发送失败
                System.out.println("发送失败");
            }

    下面是http请求工具类:

    package car.repair.common.util;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.http.HttpEntity;
    import org.apache.http.ParseException;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.entity.ContentType;
    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 java.io.IOException;
    
    /**
     * @author zhuzhe
     * @date 2017/12/11
     * HttpClient工具类
     */
    @Slf4j
    public class HttpClientUtils {
    
        /**
         * 以jsonString形式发送HttpPost的Json请求,String形式返回响应结果
         *
         * @param url
         * @param jsonString
         * @return
         */
        public static String sendPostJsonStr(String url, String jsonString) throws IOException {
            if (jsonString == null || jsonString.isEmpty()) {
                return sendPost(url);
            }
            String resp = "";
            StringEntity entityStr = new StringEntity(jsonString,
                    ContentType.create("text/plain", "UTF-8"));
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(entityStr);
            CloseableHttpResponse response = null;
            try {
                response = httpClient.execute(httpPost);
                HttpEntity entity = response.getEntity();
                resp = EntityUtils.toString(entity, "UTF-8");
                EntityUtils.consume(entity);
            } catch (ClientProtocolException e) {
                log.error(e.getMessage());
            } catch (IOException e) {
                log.error(e.getMessage());
            } finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (IOException e) {
                        log.error(e.getMessage());
                    }
                }
            }
            if (resp == null || resp.equals("")) {
                return "";
            }
            return resp;
        }
    
        /**
         * 发送不带参数的HttpPost请求
         *
         * @param url
         * @return
         */
        public static String sendPost(String url) throws IOException {
            // 1.获得一个httpclient对象
            CloseableHttpClient httpclient = HttpClients.createDefault();
            // 2.生成一个post请求
            HttpPost httppost = new HttpPost(url);
            CloseableHttpResponse response = null;
            try {
                // 3.执行get请求并返回结果
                response = httpclient.execute(httppost);
            } catch (IOException e) {
                log.error(e.getMessage());
            }
            // 4.处理结果,这里将结果返回为字符串
            HttpEntity entity = response.getEntity();
            String result = null;
            try {
                result = EntityUtils.toString(entity);
            } catch (ParseException | IOException e) {
                log.error(e.getMessage());
            }
            return result;
        }
    }
    
    

     

    收到消息,我就不自己弄图了。这里附上官方图片一张:

     

    转载请务必保留此出处(原作者):https://blog.csdn.net/zhuzhezhuzhe1/article/details/83927016

     

    版权声明:本文为原创文章,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。

    https://blog.csdn.net/zhuzhezhuzhe1

     

    展开全文
  • 微信公众号开发之消息模板 这个消息模板是以订阅号进行模板配置的,如果是认证号就更加简单了,不用自己配置!!! 只需要知道模板ID,和模板的样式就行。 微信公众号发送消息模板,当前首先要关注这个公众号,...

    微信公众号开发之消息模板

    这个消息模板是以订阅号进行模板配置的,如果是认证号就更加简单了,不用自己配置!!!

    只需要知道模板ID,和模板的样式就行。

    微信公众号发送消息模板,当前首先要关注这个公众号,然后这个公众号,可以将消息发送给关注它的用户。

    先上如下效果图:

    1、不管是订阅号还是认证号,都首先需要的是配置JS安全访问地址(注意:配置域名时不要加上https://)

    2、消息模板的选择与配置

     

    2.1、请记下生成的模板ID

    如果是认证号的话,只需要在消息模板库中添加就行

    2.2、找到合适的模板进行添加

     

    3、接下来:看一下官方文档

    消息模板官方文档https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277

    关于接口文档,请注意:

     

    1、模板消息调用时主要需要模板ID和模板中各参数的赋值内容;
    2、模板中参数内容必须以".DATA"结尾,否则视为保留字;
    3、模板保留符号"{{ }}"。

    以下是我使用的模板消息

    食堂名称:{{keyword1.DATA}} 
    支付金额:{{keyword2.DATA}} 
    订单时间:{{keyword3.DATA}} 
    订单编号:{{keyword4.DATA}}
    {{remark.DATA}}

    官方demo

      {
               "touser":"OPENID",
               "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
               "url":"http://weixin.qq.com/download",  
               "miniprogram":{
                 "appid":"xiaochengxuappid12345",
                 "pagepath":"index?foo=bar"
               },          
               "data":{
                       "first": {
                           "value":"恭喜你购买成功!",
                           "color":"#173177"
                       },
                       "keyword1":{
                           "value":"巧克力",
                           "color":"#173177"
                       },
                       "keyword2": {
                           "value":"39.8元",
                           "color":"#173177"
                       },
                       "keyword3": {
                           "value":"2014年9月22日",
                           "color":"#173177"
                       },
                       "remark":{
                           "value":"欢迎再次购买!",
                           "color":"#173177"
                       }
               }
           }

    4、根据官方实例创建参数的Bean

    4.1、创建描述消息内容定义的demo

    /**
     * 微信公众号消息模板内容详情描述
     * @author lhx 2018/04/24
     *
     */
    public class Data{
    	//消息内容
    	private String value;
    	//消息内容颜色
    	private String color;
    	public String getValue() {
    		return value;
    	}
    	public void setValue(String value) {
    		this.value = value;
    	}
    	public String getColor() {
    		return color;
    	}
    	public void setColor(String color) {
    		this.color = color;
    	}
    }

    4.2、创建消息模板的内容的demo

    public class TemplateData implements Serializable {
    	/**
    	 * 食堂名称
    	 */
    	private Data keyword1;
    	
    	/**
    	 * 订单编号
    	 */
    	private Data keyword2;
    	
    	/**
    	 * 下单时间
    	 */
    	private Data keyword3;
    	
    	/**
    	 * 支付金额
    	 */
    	private Data keyword4;
    	
    	/**
    	 * 支付说明
    	 */
    	private Data remark;
    
    	private static final long serialVersionUID = 1358741037685489170L;
    
    
    	public Data getKeyword1() {
    		return keyword1;
    	}
    
    	public void setKeyword1(Data keyword1) {
    		this.keyword1 = keyword1;
    	}
    
    	public Data getKeyword2() {
    		return keyword2;
    	}
    
    	public void setKeyword2(Data keyword2) {
    		this.keyword2 = keyword2;
    	}
    
    	public Data getKeyword3() {
    		return keyword3;
    	}
    
    	public void setKeyword3(Data keyword3) {
    		this.keyword3 = keyword3;
    	}
    
    	public Data getKeyword4() {
    		return keyword4;
    	}
    
    	public void setKeyword4(Data keyword4) {
    		this.keyword4 = keyword4;
    	}
    
    	public Data getRemark() {
    		return remark;
    	}
    
    	public void setRemark(Data remark) {
    		this.remark = remark;
    	}
    }

    4.3、创建消息模板推送内容的demo

    /**
     * 微信公众号消息模板推送
     * @author lhx 2018/04/24
     *
     */
    public class MessageTemplate implements Serializable {
    	
    	private static final long serialVersionUID = 4279971437427397225L;
    	
    	private String touser; //用户OpenID
    	private String template_id; //模板消息ID
    	private String url; //URL详情跳转,如果置空,在发送后,点模板消息进入一个空白页面(ios),或无法点击(android)。
    	private String topcolor; //标题颜色
    	private TemplateData data; //模板详细信息
    	 
    	public String getTouser() {
    		return touser;
    	}
    	public void setTouser(String touser) {
    		this.touser = touser;
    	}
    	public String getTemplate_id() {
    		return template_id;
    	}
    	public void setTemplate_id(String template_id) {
    		this.template_id = template_id;
    	}
    	public String getUrl() {
    		return url;
    	}
    	public void setUrl(String url) {
    		this.url = url;
    	}
    	public String getTopcolor() {
    		return topcolor;
    	}
    	public void setTopcolor(String topcolor) {
    		this.topcolor = topcolor;
    	}
    	public TemplateData getData() {
    		return data;
    	}
    	public void setData(TemplateData data) {
    		this.data = data;
    	}
    }

    4.4、创建微信公众号消息模板推送工具类

    /**
     * 微信公众号消息模板推送
     * @author lhx
     *
     */
    public class WechatMesTemplateSend {
    	private static Log log = LogFactory.getLog(WechatMesTemplateSend.class);
    
    	public static void send_template_message(String openId,String orderCode,String orderTime,String canteenName,String orderPrice){
    		
    		try {
    				//获取access_token
    				String access_token = WechatJsSdk.getToken();
    				
    				MessageTemplate messageTemplate = new MessageTemplate();
    				//消息模板ID
    				messageTemplate.setTemplate_id(IConstants.wxgzMesTemplateId);
    				//用户openId
    				messageTemplate.setTouser(openId);
    				messageTemplate.setTopcolor("#173177");
    				//详情跳转链接:如https://mp.csdn.net/postedit/80910593
    				//一般为消息模板的详情地址,或者跳转项目中的其他地址
    				messageTemplate.setUrl(IConstants.CONSUME_RECORD);
    				
    				Data keyword1 = new Data();
    				keyword1.setValue(canteenName);//食堂名称:如溜溜溜柒
    				keyword1.setColor("#173177");
    				
    				Data keyword2 = new Data();
    				keyword2.setValue(orderPrice+"元");//订单金额:如23.2
    				keyword2.setColor("#173177");
    				
    				Data keyword3 = new Data();
    				keyword3.setValue(orderTime);//订单时间:如2018-05-12 14:23:50
    				keyword3.setColor("#173177");
    				
    				Data keyword4 = new Data();
    				keyword4.setValue(orderCode);//订单编号:如CA_1483158631546831815483333312
    				keyword4.setColor("#173177");
    				
    				Data remark = new Data();
    				remark.setValue("欢迎再次光临");
    				remark.setColor("#173177");
    				
    				TemplateData templateData = new TemplateData();
    				templateData.setKeyword1(keyword1);
    				templateData.setKeyword2(keyword2);
    				templateData.setKeyword3(keyword3);
    				templateData.setKeyword4(keyword4);
    				templateData.setRemark(remark);
    				
    				messageTemplate.setData(templateData);
    				
    				String jsonString = JSONObject.fromObject(messageTemplate).toString();
    				
    				
    				String urlStr = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+access_token;
    				
    				String result = HttpClientManager.postUrlData(urlStr, jsonString);
    				
    				Map<String,Object> data = JSONObject.fromObject(result);
    				
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    	}
    
    }

    4.5、微信公众号推送消息模板的辅助类demo

    /**
     * 微信功能请求类 
     */
    public class WechatJsSdk {
    	public static String getToken() throws Exception {
    		String access_token = null;
    		// 创建HttpClient实例
    		HttpClient httpclient = new DefaultHttpClient();
    		// 创建Get方法实例 IConstants.wxgzAppID:微信公众号appId IConstants.wxgzAppSecret:微信公众号secret
    		HttpGet httpgets = new HttpGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
    				+ IConstants.wxgzAppID + "&secret=" + IConstants.wxgzAppSecret);
    		HttpResponse response = httpclient.execute(httpgets);
    		HttpEntity entity = response.getEntity();
    		Map<String, Object> maData = new HashMap<String, Object>();
    		if (entity != null) {
    			InputStream instreams = entity.getContent();
    			String str = convertStreamToString(instreams);
    			JSONObject jSONObject = JSONObject.fromObject(str);
    			httpgets.abort();
    			access_token = jSONObject.get("access_token");
    		}
    		return access_token;
    	}
    
    	public static String convertStreamToString(InputStream is) {
    		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    		StringBuilder sb = new StringBuilder();
    
    		String line = null;
    		try {
    			while ((line = reader.readLine()) != null) {
    				sb.append(line + "\n");
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				is.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		return sb.toString();
    	}
    }

    项目已经上线,如果有疑问,请留言,讨论!!!

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

    说明

          在公司微信项目开发中,我主要负责消息中心的模板消息接口设计实现。主要是将微信公众号的推送模板消息功能放到公司的消息中心系统中,微信后台项目通过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);
       }
    }

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

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

    展开全文
  • 通过本课程的学习,学员能够入门微信公众平台开发,能够胜任企业级的订阅号、服务号、企业号的应用开发工作。 通过本课程的学习,学员能够对微信公众平台有一个清晰的、系统性的认识。例如,公众号是什么,它有...
  • C#微信开发模板

    2020-04-13 23:30:59
    简单的C#的微信开发,很适合刚开始微信开发的人士。
  • 在公众号开发或者其他应用开发过程中,很多场景下都需要向微信用户发送指定格式的消息,比如购买成功通知,刷卡通知等,笔者采用的是微信模板消息形式。当然这个仁者见仁智者见智,有其他更好的形式则不必拘泥于此...
  • 微信公众平台模板消息开发-微信开发14(PHP) 大秦电商创始人,专注网络技...
  • 微信小程序模板.zip

    2020-06-06 23:30:43
    包含多个精美微信小程序模板,页面精美,模板可套用,使用简单快捷,供微信小程序开发学习使用!
  • 微信开发模板消息为什么首行会自动缩进? 用的是微信测试号,添加的确定是模板正确的。 有没办法设置格式?
  • 微信支持根据模板id给微信某一个公众号的某一个用户发送模板消息 二、注意 发送模板消息使用access_token是普通access_token 下方代码使用的url如果不传,发送模板消息的时候,无法点击跳转。只有传url,才可以...
  • 15套自己收集的微信网站模板,微网站HTML5模板,分享给大家
  • 微信小程序开发网站模板是一款蓝色好看的应用app设计公司网站模板下载。提示:本模板调用到谷歌字体库,可能会出现页面打开比较缓慢。
  • 讲一下开发项目中微信公众号发送模板消息的实现过程(我用的还是Thinkphp5.0)。先看一下效果,如图:就是类似于这样的,下面讲一下实现过程:第一步:微信公众号申请模板消息权限:立即申请:申请过程就不说了,提交...
  • 微信发送模板消息接口文档...温馨提示:请各位开发者使用微信模板消息功能前,仔细阅读上述发送模板消息接口文档,少走弯路; 一、微信公众号开发框架 <!-- 微信框架 参考:https://github.com/Wechat-Gr...
  • 微信小程序模板欢迎下载。。。。。。。。。。。。。。。
  • 为支撑公司业务,需要使用微信模板消息,于是乎进行了下相关的调研,现将调研内容进行如下记录。 首先微信模板消息没有创建模板消息的接口。更新模板消息有两种方式(个人理解): 1、修改行业,然后选择微信...
1 2 3 4 5 ... 20
收藏数 35,074
精华内容 14,029