• 今天跟大家分享一下微信自动回复功能开发,在这这次需要准备好自己的服务器(可以在外网上面访问的),以及在公众号上面进行服务器认证,并且开启服务器配置。 用户在给公众号发消息的时候,微信会把这些消息以xml...

          今天跟大家分享一下微信自动回复功能开发,在这这次需要准备好自己的服务器(可以在外网上面访问的),以及在公众号

    上面进行服务器认证,并且开启服务器配置。

      用户在给公众号发消息的时候,微信会把这些消息以xml格式的形式发送到开发者服务器对应的url上面;

    开发者接收到xml息之后,可以对其进行解析,然后根据消息的内容回发对应的内容给用户,回复的消息也是要以xml格式发送

    出去的。

    <xml> <ToUserName>< ![CDATA[toUser] ]></ToUserName> <FromUserName>< ![CDATA[fromUser] ]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType>< ![CDATA[text] ]></MsgType> <Content>< ![CDATA[你好] ]></Content> </xml>
    参数是否必须描述
    ToUserName接收方帐号(收到的OpenID)
    FromUserName开发者微信号
    CreateTime消息创建时间 (整型)
    MsgTypetext
    Content回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)

       这里面有一个需要注意的就是,微信向开发者服务器发送请求的等待时间是5秒钟,如果开发者服务器5秒内不能回复,

    微信会重新发送请求(最多三次),三次之后还是不能再5秒内回复或者回复的内容微信无法进行解析,就会显示“该公众号无

    法提供服务”。如果无法保证在5秒内回复,可以回复一个空字符串,微信不会对这个消息做任何处理。

      因为微信发送到开发者服务器的消息类型比较多样化,有普通消息,关注事件,取消关注事件,按钮点击事件等。所以在设

    计自动回复功能的时候要充分考虑到程序的灵活性、可扩展性以及可维护性。

      这里我使用了“责任链设计模式”,定义一个处理接口,让每一个消息处理程序都实现这个接口;接收到请求的时候,把该请

    求传递到第一个处理程序类,每一个请求类里面都包含下一个处理程序类的引用;如果请求可以在该类里面处理则直接返回处理

    结果,否则流向下一个处理程序类,直至该请求被处理完毕。该模式的特点是,把处理请求的步骤进行分解,可以对复杂的判断

    条件进行分解,同时每一个处理程序都只有一个单一的职责,对其进行修改不会影响到其他处理程序类。另外,将每一个请求类

    xml文件的格式配置好,应用程序启动的时候,使用反射+IOC注入的方式实例化每一个处理程序类。

      首先创建一个页面,replyText.html


     我们创建两个数据表,

    rule表 :用来存储回复数据,id自增长 , mp_id是当前正在使用的公众号,keyword是用户输入的关键字,type在此为text,reply_id与reply_text表建立连接,status为当前状态(是否正在使用)。



    reply_text表 : reply_id作为主键,content为回复内容。

    (在此注意一点,在页面输入对应值后,要把数据统一添加到两个数据表中,add()方法成功返回主键值,可利用这点进行两表关联添加)



    public function replyText(){
            if(IS_GET){
                $this->display('replytext');
            }else{
                $mp = $this->mp;
                $mp_id = $mp['id'];
                $data = I('post.');
    
                $textret = M('reply_text')->add($data);
    
                if($textret){
                    $data['reply_id'] = $textret;
                    $data['mp_id'] = $mp_id;
                    $data['type'] = 'text';
                    if ($mp['is_use'] == 1){
                        $data['status'] = 1;
                    }else{
                        $data['status'] = 0;
                    }
                    $ret = M('rule')->add($data);
                    if ($ret) {
                        $this->ajaxReturn(array('msg'=>'添加成功!'));
                    }else{
                        $this->ajaxReturn(array('msg'=>$ret));
                    }
                }
            }
        }

    之前介绍过,我用的laneWeChat包,可以直接调用里边的方法,在wechatrequest.lib.php里的text方法中加入以下代码进行文本回复:

    //获取哪个公众号发过来的请求
            $mp_id = $_GET['id'];
    
            $content = $request['content'];
            $where['mp_id'] = $mp_id;
            $where['keyword'] = $content;
            $data = M('rule')->where($where)->find();
            if ($data) {
                //发送消息中有这个关键字
                $reply_id = $data['reply_id'];
                $type = $data['type'];
    
                if ($type == "text") {
                    $reply = M('reply_text')->find($reply_id);
                    $reply_text = $reply['content'];
                    return ResponsePassive::text($request['fromusername'], $request['tousername'], $reply_text);
                }
                
            }else{
                return 'success';
            }

    代码要一一写的话就有些多了,在此,只给小伙伴们分享以上代码,如果还有其他问题,欢迎留言提问哦~

    请大家多多关注,我会时刻更新的!

     
    展开全文
  • java微信公众号自动回复文字加图片开发流程详细流程,附上代码:第一步服务器(url)接口配置服务器(url)接口配置,此步骤就是微信授权接口的过程,如果域名都不改变,微信只会校验一。此请求微信文档中说明了是...

    前提:第一次发布文章,如有写的不好的地方请指正或代码上可以优化的地方也请各路大神多多指教。顺带想吐槽下微信开发文档真的太恶心了,第一次接触下来让人头晕眼花,通过查阅各种资料后理清了思路,在此写上总结。

    开发流程

    1. 服务器(url)接口配置
    2. 发送文字(根据微信公众号文档“接收事件推送”事件接口编写代码)
    3. 生成二维码(账号管理下的“生成带参数的二维码”事件接口编写代码)
    4. 根据UI的图组合微信头像、微信名字、公众号二维码并调用素材管理下的“新增临时素材”接口上传图片
    5. 发送图片(调用消息管理下的客服消息下的客服接口-发消息(图片)接口)
    6. 进行裂变(调用“扫描带参数二维码事件”)(此步骤还未开发完,需根据业务需求变动)

    详细流程,附上代码:

    第一步服务器(url)接口配置

    服务器(url)接口配置,此步骤就是微信授权接口的过程,如果域名都不改变,微信只会校验一次。此请求微信文档中说明了是get请求。
    /**
         * 微信消息接收和token验证
         * @param request
         * @param response
         */
        @RequestMapping(value = "/weChatToken", method = RequestMethod.GET)
        public void weChatToken(HttpServletRequest request, HttpServletResponse response){
            boolean isGet = request.getMethod().toLowerCase().equals("get");
            if (isGet) {
                // 微信加密签名
                String signature = request.getParameter("signature");
                // 时间戳
                String timestamp = request.getParameter("timestamp");
                // 随机数
                String nonce = request.getParameter("nonce");
                // 随机字符串
                String echostr = request.getParameter("echostr");
    
                // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
                if (signature != null && WeChatUtil.checkSignature(signature, timestamp, nonce)) {
                    try {
                        PrintWriter print = response.getWriter();
                        print.write(echostr);
                        print.flush();
                    } catch (IOException e) {
                        log.error("微信消息接收和token验证异常",e);
                    }
                }
            }
        }
    

    第二步发送文字

    我这边的需求是发送的文字中要带上微信名称,所以还需要调用微信的获取accessToken接口跟获取用户信息接口。获取用户信息接口中需要openId,在用户点击关注微信公众号时,微信会自动发送过来。微信为什么会知道咱们的接口呢,因为咱们已经在第一步授权了接口,微信会以默认post请求的方式调用相同的路由,所以我们还得写一个post请求的接口。注:finally中是发送图片的动作。调用事件中运用了抽象工厂的设计模式,此处还有待优化。

    2.1 接口详解

    /**
         * 微信公众号 -->
         *   消息管理 -->
         *     接收普通消息、接收事件推送、被动回复用户消息
         *
         * @param request
         * @return
         */
        @ResponseBody
        @RequestMapping(value = "/weChatToken", method = RequestMethod.POST)
        public String responseEvent(HttpServletRequest request){
            WeChatMsgService weChatMsgService = null;
            Map<String, String> requestMessageMap = null;
            try {
                // 获取到请求参数
                requestMessageMap = XMLUtil.xmlToMap(request);
                log.info("获取到参数为:{}",requestMessageMap);
    
                // 判断请求参数中 MsgType的值或判断Event值
                String returnMessage = "";
                if (WeChatUtil.REQ_MESSAGE_TYPE_EVENT.equals(requestMessageMap.get("MsgType"))) {
                    // 如果后期有取消订阅事件,此处方法可以去除if
                    if (WeChatUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMessageMap.get("Event"))) {
                        weChatMsgService = getType(requestMessageMap.get("Event"));
                        returnMessage = weChatMsgService.getReturnMessage(requestMessageMap);
                    }
                }
    
                return returnMessage;
            } catch (Exception e) {
                log.error("微信公众号自动回复异常",e);
            }finally {
                if (WeChatUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMessageMap.get("Event"))) {
                    weChatMsgService = weChatCustomerServiceImgMessageServiceImpl;
                    weChatMsgService.getReturnMessage(requestMessageMap);
                }
    
            }
    
            return "";
        }
    

    2.1.2 使用设计模式调用不同的业务逻辑(default有待修改)
    此处注入了业务层
    @Autowired
    private WeChatMsgService weChatTextMessageServiceImpl;

    /**
         *  根据类型选择所需的业务处理逻辑
         * @param type
         */
        private WeChatMsgService getType(String type){
            switch (type) {
                case WeChatUtil.EVENT_TYPE_SUBSCRIBE:
                    return weChatTextMessageServiceImpl;
                default:
                    return null;
            }
        }
    

    2.1.3 调用业务层组合返回的信息(微信文档要求返回的信息为xml的形式)

    @Service
    @Slf4j
    public class WeChatTextMessageServiceImpl implements WeChatMsgService {
    
        @Autowired
        private WeiXinService weiXinService;
    
        @Override
        public String getReturnMessage(Map<String,String> requestMessageMap) {
    
            // 获取access_token
            WeiXinAccessTokenDto accessToken = weiXinService.getAccessToken();
            //获取用户信息
            WeiXinUserInfoDto userInfo = null;
            if (accessToken.getAccess_token() != null) {
                userInfo = weiXinService.getUnionID(accessToken.getAccess_token(), requestMessageMap.get("FromUserName"));
            }
    
            TextMessageResponseVo textMessageResponseVo = new TextMessageResponseVo();
            textMessageResponseVo.setFromUserName(requestMessageMap.get("ToUserName"));
            textMessageResponseVo.setToUserName(requestMessageMap.get("FromUserName"));
            textMessageResponseVo.setMsgType("text");
            textMessageResponseVo.setCreateTime(System.currentTimeMillis());
            textMessageResponseVo.setContent("欢迎" + userInfo.getNickname() + "11111");
    
            String result = WeChatUtil.messageToXml(textMessageResponseVo);
            return result;
        }
    
    }
    

    2.2 调用微信获取accessToken 的接口(业务层涉及到其他逻辑,所以就不全部粘出来了)

    @Override
        public WeiXinAccessTokenDto getAccessToken() {
            Map<String,String> reqParams = new HashMap<>();
            reqParams.put("appid","appid");
            reqParams.put("secret","secret");
            reqParams.put("grant_type","client_credential");
    
            String result = HttpUtil.sendGetJoint(accessTokenUrl, reqParams);
            // 将返回的字符串转为对象
            WeiXinAccessTokenDto weiXinAccessTokenDto = JSON.parseObject(result, WeiXinAccessTokenDto.class);
            return weiXinAccessTokenDto;
        }
    

    2.3 通过微信接口获取用户信息(微信获取用户信息的接口有三种,此处只举例一种)

    @Override
        public WeiXinUserInfoDto getUnionID(String accessToken, String openId) {
            Map<String,String> reqParams = new HashMap<>();
            reqParams.put("lang","zh_CN");
            reqParams.put("access_token",accessToken);
            reqParams.put("openid",openId);
    
            String result = HttpUtil.sendGetJoint(unionIDUrl, reqParams);
            log.info("获取微信用户信息为:{}",result);
            // 将返回的字符串转为对象
            WeiXinUserInfoDto weiXinUserInfoDto = JSON.parseObject(result, WeiXinUserInfoDto.class);
            return weiXinUserInfoDto;
        }
    

    2.4 返回信息实体类(此处用了Swagger,不用的童鞋可以删除注解。还要引入lombok)

    /**
     * @author: hugh_lin
     * @date: 2019/06/26 18:53
     * @Description: 微信公众号返回消息公共实体类
     * @Modifed:
     **/
    @Getter
    @Setter
    public class BaseWeChatResponseVo {
    
        @ApiModelProperty("开发者微信号")
        private String ToUserName;
    
        @ApiModelProperty("发送方帐号(一个OpenID)")
        private String FromUserName;
    
        @ApiModelProperty("消息创建时间 (整型)")
        private Long CreateTime;
    
        @ApiModelProperty("消息类型,event或text、image、voice、video、music、news")
        private String MsgType;
    
    }
    
    /**
     * @author: hugh_lin
     * @date: 2019/06/26 18:58
     * @Description: 微信公众号返回消息实体
     * @Modifed:
     **/
    @Getter
    @Setter
    public class TextMessageResponseVo extends BaseWeChatResponseVo{
    
        @ApiModelProperty("文本消息内容")
        private String Content;
    
    }
    

    2.5 微信工具类

    import com.thoughtworks.xstream.XStream;
    import com.thoughtworks.xstream.core.util.QuickWriter;
    import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
    import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
    import com.thoughtworks.xstream.io.xml.XppDriver;
    import com.youchengjvhe.weishangketang.model.vo.wechat.TextMessageResponseVo;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.*;
    import java.net.URL;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Formatter;
    
    /**
     * @author: hugh_lin
     * @date: 2019/06/26 15:45
     * @Description: 微信消息处理工具类
     * @Modifed:
     **/
    @Slf4j
    public class WeChatUtil {
    
        //请求消息的类型------------------------------------------
        /**
         *  文本
         */
        public static final String REQ_MESSAGE_TYPE_TEXT = "text";
    
        /**
         *  图片
         */
        public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
    
        /**
         *  语音
         */
        public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
    
        /**
         *  视频
         */
        public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
    
        /**
         *  小视频
         */
        public static final String REQ_MESSAGE_TYPE_SHORTVIDEO = "shortvideo";
    
        /**
         *  地理位置
         */
        public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
    
        /**
         *  链接
         */
        public static final String REQ_MESSAGE_TYPE_LINK = "link";
    
        /**
         *  事件推送
         */
        public static final String REQ_MESSAGE_TYPE_EVENT = "event";
    
        /**
         *  事件类型--订阅
         */
        public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    
        /**
         *  取消订阅
         */
        public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    
        /**
         *  已关注用户扫描带参数二维码
         */
        public static final String EVENT_TYPE_SCAN = "SCAN";
    
        /**
         *  上报地理位置
         */
        public  static final String EVENT_TYPE_LOCATION = "LOCATION";
    
        /**
         *  自定义菜单事件
         */
        public static final String EVENT_TYPE_CLICK = "CLICK";
    
        //响应消息类型
        /**
         *  文本
         */
        public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    
        /**
         *  图片
         */
        public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
    
        /**
         *  语音
         */
        public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
    
        /**
         *  视频
         */
        public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
    
        /**
         *  音乐
         */
        public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
    
        /**
         *  图文
         */
        public static final String RESP_MESSAGE_TYPE_ARTICLE = "news";
    
        //---------------------------------------------------------------------
    
        /**
         *  与测试账号接口配置信息中的Token要一致
         *  测试token,微信公众号内可自行配置
         */
        private static String token = "******";
    
        /**
         * 验证签名
         * @param signature
         * @param timestamp
         * @param nonce
         * @return
         */
        public static boolean checkSignature(String signature, String timestamp, String nonce) {
            String[] arr = new String[] { token, timestamp, nonce };
            // 将token、timestamp、nonce三个参数进行字典序排序
            Arrays.sort(arr);
            StringBuilder content = new StringBuilder();
            for (int i = 0; i < arr.length; i++) {
                content.append(arr[i]);
            }
            MessageDigest md = null;
            String tmpStr = null;
    
            try {
                md = MessageDigest.getInstance("SHA-1");
                // 将三个参数字符串拼接成一个字符串进行sha1加密
                byte[] digest = md.digest(content.toString().getBytes());
                tmpStr = byteToHex(digest );
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
            return tmpStr != null ? tmpStr.equals(signature) : false;
        }
    
        /**
         * 十六进制字节数组转为字符串
         * @param hash
         * @return
         */
        private static String byteToHex(final byte[] hash) {
            Formatter formatter = new Formatter();
            for (byte b : hash) {
                formatter.format("%02x", b);
            }
            String result = formatter.toString();
            formatter.close();
            return result;
        }
    
        /**
         * 扩展xstream使其支持CDATA
         */
        private static XStream xstream = new XStream(new XppDriver() {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out) {
                    // 对所有xml节点的转换都增加CDATA标记
                    boolean cdata = true;
    
                    @Override
                    @SuppressWarnings("unchecked")
                    public void startNode(String name, Class clazz) {
                        super.startNode(name, clazz);
                    }
    
                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (cdata) {
                            writer.write("<![CDATA[");
                            writer.write(text);
                            writer.write("]]>");
                        } else {
                            writer.write(text);
                        }
                    }
                };
            }
        });
    
        /**
         * 文本消息转换成xml
         */
        public static String messageToXml(TextMessageResponseVo textMessageResponseVo) {
            xstream.alias("xml", textMessageResponseVo.getClass());
            return xstream.toXML(textMessageResponseVo);
        }
    
        /**
         * 链接url下载图片
         * @param urlList
         * @param path
         */
        public static void downloadPicture(String urlList,String path) {
            URL url = null;
            try {
                url = new URL(urlList);
                DataInputStream dataInputStream = new DataInputStream(url.openStream());
    
                FileOutputStream fileOutputStream = new FileOutputStream(new File(path));
                ByteArrayOutputStream output = new ByteArrayOutputStream();
    
                byte[] buffer = new byte[1024];
                int length;
    
                while ((length = dataInputStream.read(buffer)) > 0) {
                    output.write(buffer, 0, length);
                }
                fileOutputStream.write(output.toByteArray());
                dataInputStream.close();
                fileOutputStream.close();
            } catch (Exception e) {
                log.error("根据url下载图片异常",e);
            }
        }
    }
    
    

    第三步组合图片与文字信息并返回

    3、4、5步骤是在一个代码块中,所以此处进行详细分解。注:调用方式已在第二步说明

    3.1 整体方法(一些注入的路径,可以自行查阅微信公众号文档)

    /**
     * @author: hugh_lin
     * @date: 2019/06/28 16:37
     * @Description: 公众号客服消息(回复图片)
     * @Modifed:
     **/
    @Service
    public class WeChatCustomerServiceImgMessageServiceImpl implements WeChatMsgService {
    
        /**
         * 图片请求路径
         */
        @Value("${test.imgFileUrl}")
        private String imgFileUrl;
        /**
         * 图片生成路径
         */
        @Value("${test.imgFilePath}")
        private String imgFilePath;
        @Value("${weixin.getDownloadQrCodeUrl}")
        private String downloadQrCodeUrl;
    
        @Autowired
        private WeiXinService weiXinService;
    
        @Override
        public String getReturnMessage(Map<String, String> requestMessageMap) {
    
            // 获取accessToken
            WeiXinAccessTokenDto accessToken = weiXinService.getAccessToken();
            //获取用户信息
            WeiXinUserInfoDto userInfo = null;
            if (accessToken.getAccess_token() != null) {
                userInfo = weiXinService.getUnionID(accessToken.getAccess_token(), requestMessageMap.get("FromUserName"));
            }
    
            // 生成微信公众号二维码
            WeChatQrCodeDto qrCode = weiXinService.getQrCode(accessToken.getAccess_token(), userInfo.getOpenid());
    
            /* 下载二维码图片 */
            // 1.生成下载地址
            StringBuffer qrCodeUrl = new StringBuffer();
            qrCodeUrl.append(downloadQrCodeUrl);
            qrCodeUrl.append("?ticket=");
            qrCodeUrl.append(qrCode.getTicket());
            // 2.生成文件名并生成下载到本地的路径
            StringBuffer imgPath = new StringBuffer();
            imgPath.append("D:\\");
            imgPath.append(UUID.randomUUID());
            imgPath.append(".jpg");
            WeChatUtil.downloadPicture(qrCodeUrl.toString(),imgPath.toString());
    
            // 合成图片
            ImageUtil.generateCompositeImages("D:\\aaa.jpg",userInfo.getHeadimgurl(),imgPath.toString(),userInfo.getNickname());
    
            // 上传到微信公众号素材库,将数据转成json格式并取出需要的数据
            String weChatMedia = weiXinService.getWeChatMediaId(accessToken.getAccess_token(), imgPath.toString(), "image");
            JSONObject media = JSONObject.parseObject(weChatMedia);
    
            // 发送客服消息
            weiXinService.sendCustomerServiceMessage(accessToken.getAccess_token(),userInfo.getOpenid(),"image","media_id",media.getString("media_id"));
    
            return null;
        }
    }
    
    

    3.2 生成公众号二维码(此处为什么要调用微信接口生成呢,因为如果进行裂变的话,需要调用微信扫描二维码事件,所以更方便)

    @Override
        public WeChatQrCodeDto getQrCode(String accessToken, String content) {
    
            // 拼接url
            StringBuffer sb = new StringBuffer();
            sb.append(qrCodeUrl);
            sb.append("?access_token=");
            sb.append(accessToken);
    
            // 生成json数据进行请求
            JSONObject sceneStrJson = new JSONObject();
            sceneStrJson.put("scene_str",content);
            JSONObject sceneJson = new JSONObject();
            sceneJson.put("scene",sceneStrJson);
    
            JSONObject reqJson = new JSONObject();
            reqJson.put("action_name","QR_LIMIT_STR_SCENE");
            reqJson.put("action_info",sceneJson);
    
            String result = HttpUtil.sendPost(sb.toString(), reqJson.toString());
            log.info("获取微信公众号二维码信息为:{}",result);
            // 将返回的字符串转为对象
            WeChatQrCodeDto weChatQrCodeDto = JSON.parseObject(result, WeChatQrCodeDto.class);
            return weChatQrCodeDto;
        }
    

    3.3下载二维码图片到本地(因为要组合图片,需要用到。如何获取微信开发文档中有提及,此处就不多说了。此处调用的是WeChatUtil,在第二步中已写上)

    3.4合成图片(因为调用了Graphics画图工具,组合的图片大小及位置可自行根据UI图进行调整)

    /**
     * @author: hugh_lin
     * @date: 2019/06/28 09:52
     * @Description: 图片工具类
     * @Modifed:
     **/
    @Slf4j
    public class ImageUtil {
    
        /**
         * 生成组合图片(此处根据需求需要组成三张图片)
         * @param args
         *         args[0] backgroundImage 背景图路径
         *         args[1] weChatAvatar 微信头像路径
         *         args[2] qrCode 二维码路径
         *         args[3] 微信名
         */
        public static void generateCompositeImages(String... args){
            try {
                //创建初始图片
                BufferedImage img = new BufferedImage(1242, 2208, BufferedImage.TYPE_INT_RGB);
                //读取本地图片
                BufferedImage backgroundImage = ImageIO.read(new File(args[0]));
                //读取微信头像图片
                BufferedImage weChatAvatar = ImageIO.read(new URL(args[1]));
                //读取本地图片
                BufferedImage qrCode = ImageIO.read(new File(args[2]));
    
                //开启画图
                Graphics g = img.getGraphics();
    
                //将微信头像跟二维码设置为圆角(根据需求变更)
                weChatAvatar = setClip(weChatAvatar,100);
                qrCode = setClip(qrCode, 100);
    
                // 绘制缩小后的图
                g.drawImage(backgroundImage.getScaledInstance(1242,2208, Image.SCALE_DEFAULT), 0, 0, null);
                g.drawImage(weChatAvatar.getScaledInstance(85, 85, Image.SCALE_DEFAULT), 93, 35, null);
                g.drawImage(qrCode.getScaledInstance(370, 370, Image.SCALE_DEFAULT), 203, 1800, null);
    
                g.setColor(Color.black);
                g.setFont(new Font("微软雅黑", Font.PLAIN, 24));
                //绘制文字
                g.drawString(args[3], 214, 58);
    
                g.dispose();
                ImageIO.write(img, "jpg", new File(args[2]));
    
            } catch (Exception e) {
                log.error("生成组合图片异常",e);
            }
        }
    
        /**
         * 图片设置圆角
         * @param srcImage
         * @return
         * @throws IOException
         */
        private static BufferedImage setRadius(BufferedImage srcImage) throws IOException{
            int radius = (srcImage.getWidth() + srcImage.getHeight()) / 6;
            return setRadius(srcImage, radius, 2, 5);
        }
    
        /**
         * 图片设置圆角
         * @param srcImage
         * @param radius
         * @param border
         * @param padding
         * @return
         * @throws IOException
         */
        private static BufferedImage setRadius(BufferedImage srcImage, int radius, int border, int padding) throws IOException{
            int width = srcImage.getWidth();
            int height = srcImage.getHeight();
            int canvasWidth = width + padding * 2;
            int canvasHeight = height + padding * 2;
    
            BufferedImage image = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D gs = image.createGraphics();
            gs.setComposite(AlphaComposite.Src);
            gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            gs.setColor(Color.WHITE);
            gs.fill(new RoundRectangle2D.Float(0, 0, canvasWidth, canvasHeight, radius, radius));
            gs.setComposite(AlphaComposite.SrcAtop);
            gs.drawImage(setClip(srcImage, radius), padding, padding, null);
            if(border !=0){
                gs.setColor(Color.GRAY);
                gs.setStroke(new BasicStroke(border));
                gs.drawRoundRect(padding, padding, canvasWidth - 2 * padding, canvasHeight - 2 * padding, radius, radius);
            }
            gs.dispose();
            return image;
        }
    
        /**
         * 图片切圆角
         * @param srcImage
         * @param radius
         * @return
         */
        private static BufferedImage setClip(BufferedImage srcImage, int radius){
            int width = srcImage.getWidth();
            int height = srcImage.getHeight();
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D gs = image.createGraphics();
    
            gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            gs.setClip(new RoundRectangle2D.Double(0, 0, width, height, radius, radius));
            gs.drawImage(srcImage, 0, 0, null);
            gs.dispose();
            return image;
        }
    
    }
    
    

    3.5上传到微信公众号素材库(因为调用发送客服图片信息接口时需要用到参数,所以只能先上传到素材库)

     @Override
        public String getWeChatMediaId(String accessToken, String imgPath, String type) {
            try {
                File file=new File(imgPath);
                if (!file.exists() || !file.isFile()) {
                    return null;
                }
    
                // 拼接请求地址
                StringBuffer sb = new StringBuffer();
                sb.append(mediaUrl);
                sb.append("?access_token=");
                sb.append(accessToken);
                sb.append("&type=");
                sb.append(type);
    
                String result = HttpUtil.weChatFodderPost(sb.toString(), file);
    
                return result;
            } catch (Exception e) {
                log.error("素材流关闭异常",e);
            }
    
            return null;
        }
    

    3.6发送客服图片消息

    /**
         *  客服接口-发消息(此方法目前只支持:
         *                               发送文本消息、发送图片消息、发送语音消息、发送视频消息、发送音乐消息)
         * @param args
         * args[0] accessToken
         * args[1] OPENID
         * args[2] msgtype
         * args[3] content、media_id.....
         * args[4] 内容
         */
    @Override
        public void sendCustomerServiceMessage(String... args) {
            // 拼接url
            StringBuffer sb = new StringBuffer();
            sb.append(customerServiceUrl);
            sb.append("?access_token=");
            sb.append(args[0]);
    
            // 生成json数据进行请求
            JSONObject contentJson = new JSONObject();
            contentJson.put(args[3],args[4]);
    
            JSONObject reqJson = new JSONObject();
            reqJson.put("touser",args[1]);
            reqJson.put("msgtype",args[2]);
            reqJson.put(args[2],contentJson);
    
            HttpUtil.sendPost(sb.toString(), reqJson.toString());
    
        }
    

    最后,遇到个bug,因为需求是先返回文字再返回图片。测试的时候,偶然发生了先返回图片再返回文字的情况。从我的认知角度讲,会先执行finally块,再执行return,也是先返回return的消息,再返回finally中的消息。遇到此问题是否由网络延迟或等原因造成的还有待考究。

    最后附上个人微信:a373011739 ,谢谢各位大佬的观看,鞠躬。

    展开全文
  • 两个星期前,学校请了传智播客的老师给我们做实训,讲了一个微信公众号开发,感觉挺有意思,在这里做一下记录,以便以后复习. 一、了解微信公众平台 1. 什么是微信公众平台? 微信公众平台是腾讯为了让用户申请和管理...

    两个星期前,学校请了传智播客的老师给我们做实训,讲了一个微信公众号开发,感觉挺有意思,在这里做一下记录,以便以后复习.

    一、了解微信公众平台

    1. 什么是微信公众平台?

    微信公众平台是腾讯为了让用户申请和管理微信公众账号而推出的一个Web平台,而微信公众账号的操作管理在这个平台下进行.换句话说,凡是关注自己公众号的用户,在这个网站中都可以去管理他们. 平台地址: https://mp.weixin.qq.com/

    2. 微信的两种运行模式

    编辑模式与开发者模式,这两种模式是互斥的.

    编辑模式是平台内置的web系统,不需要专业的技术.

    开发者模式可以通过腾讯的API接口进行二次开发,需要专业的PHP技术.

    3. 首先要在平台地址中,创建一个订阅号.

    订阅号和服务号的区别: 订阅号是针对个人的,服务号针对有营业执照的商户.


    二、微信公众平台开发(开发者模式)

    0. 在讲下面的一些知识之前: 大家首先把这个网盘中的一些 要使用的的文件,下载下来,后面会使用到.

    网盘地址: 链接:https://pan.baidu.com/s/1qZzwLGC 密码:v9vk

    1. 开发者模式原理图


    根据上图,我们联想平时我们关注的微信公众号,当我们发消息给公众号,公众号都会自动回复我们一些东西. 

    还有一点,我们必须要有自己的服务器,在阿里云买一个服务器肯定需要钱啊,我们不想花钱怎么办?

    这里就要使用到 内网渗透 的技术,把我们自己的电脑充当服务器(拥有自己的域名).

    2、在开发之前,首先我们要下载并启动PhpStudy,做过php开发的,都应该了解什么是phpStudy.他是php的配置环境,具体参考链接中的PHP环境配置:

    http://blog.csdn.net/m0_37989980/article/details/78971478 

    3、NATAPP内网渗透技术.(将本机充当一个服务器)

    http://natapp.cn/ 使用手机号注册一个自己的账号.如下图所示


    此时我们需要下载一个natapp的客户端. 我在这里提供了网盘地址:  大家可以下载.

    1): 首先将下载好的natapp.exe 与 config.ini 同时赋值到 D盘的natapp目录下(这个natapp目录是自己创建的)

    2): 打开config.ini中的隧道密钥,改成我们自己的密钥.


    这个authtoken就是我们的密钥,把这个密钥复制到config.ini的这个位置


    4): 打开cmd窗口,将刚才D盘下natapp目录下的natapp.exe拖动到命令行中,如下图所示


    这样我们的服务器就已经创建好了,以后我们就可以通过 上面的 http:// 来访问自己的电脑了

    注意: 在运行过程中,cmd命令行不可以关闭/

    5): 下载腾讯提供的API接口, 叫api.php,也在刚才你所下载的网盘文件中.

    我们把api.php文件放到 我们安装的phpStudy的目录下的 PHPTutorial的WWW目录下;


    6): 把api.php放进去之后,打开这个文件, 查看这个文件中的 define("TOKEN","weixin") ,记住这个接口中的密钥是 weixin

    7): 打开我们注册过的微信公众平台,找到工具下 的基本配置


    提交成功后,并点击右侧的启动按钮,就启动成功了.(注意这里有一些注意点:如果提交不成功,多提交几次. 后面有写有哪些注意点)

    8): 此时做到这里就基本上完成了,然后我们可以用自己的微信关注自己所创的公众号.随便发送一条消息.会出现下面问题:


    4、手动开启自动回复(这里有个坑,一定要注意)

    我们使用sublime将www下的api.php 文件打开,做下面操作


    做了这些操作后,我们再向自己的公众号中发送消息,就可以正常回复了!

    三、微信的6大接收接口

    1. 我们想公众号发送文字消息,其就可以回复,发送图片,语音 就无法回复,这是为什么呢?

    只要设置就在api.php接口中.

    2. 微信如何接收消息的?

    在微信公众平台下,打开开发者工具-->消息管理--->接收普通消息下

    其中包括文本消息、图片,语音,图文等消息


    3. 我们来分析一下api.php文件(重点)

    首先 $msgType = "text"; 是表示接收的是什么类型的数据

    $ contentStr 用来返回给我们是什么样的数据.

    如果我们要使用 图片,图文等相关模板,需要将平台中的<XML>文件复制到api.php文件中.

    将我们的需要展示的内容填充进去,就可以了

    四、具体讲解图文消息接口

    1、文本回复接口(文本回复四步走)

    问题:微信公众平台是如何把数据发送给我们的APP的。

    文本回复一共分为这样的几个步骤:

    第一步:组装XML数据


    第二步:定义相关的变量$msgType$contentStr

    第三步:使用sprintf函数,格式化变量到%s的位置

    要用到的知识点:sprintf函数,在实际项目开发主要用于格式化字符串

    sprint(要格式化的字符串,变量1,变量2,变量3...)

    到底有多少个变量呢,就是要格式化的字符串中,有多少个%s就有多少个变量。

    第四步:使用echo输出返回XML数据到微信APP


    2、微信中的图文回复接口(重点中的重点)


    3、编写图文接口(四步走)

    第一步:组装XML数据(设置%s


    XML模板从哪里来,从微信的手册中获取:


    把以上代码复制放到$newsTpl代码中:


    放置完成后,不能立即停止,因为我们还没有放%s呢,必须放置%s


    记住:有几个%s,因为一会还要定义几个变量。

    第二步:设置相关的变量(有几个%s就要定义几个变量)

    我们需要定义6个变量,但是前3个变量,系统默认已经有了,$fromUserName$toUserName$time。所以咱们真正定义的只有3个变量。

    第三步:使用sprintf函数格式化第一步中的字符串

    第四步:使用echo把图文消息返回给微信的APP

    运行结果:

    4、如果微信开发时遇到故障,改如何调试呢?

    打开浏览器,输入http://域名/api.php,看一下语法是否有错误


    如果出现以上代码,代表微信接口没有问题,因为HTTP_RAW_POST_DATA没有经过腾讯服务器,只经过我们自己的服务器,所以报HTTP_RAW_POST_DATA.


    注意点:

    1. 在运行中我们不可以关闭命令行,否则就会出现错误

    2. 要开启phpStudy

    3. 当我们电脑重启后,将natapp.exe拖入到命令行后,会从新生成一个域名.

    3.1 首先先把vaild()方法给打开, 把responseMsg()方法关闭,然后在valid()方法中 添加ob_clean()方法,清除缓存,否则会验证token失败.

    public function valid()
        {
            $echoStr = $_GET["echostr"];
    
            //valid signature , option
            if($this->checkSignature()){
                ob_clean(); 
                echo $echoStr;
                exit;
            }
        }
    3.2 当启动成功后,一定要记住将api.php中的valid()方法注释掉,打开responseMsg()方法

    展开全文
  • 微信开发的概念

    2019-12-03 23:23:16
    微信对外开放了很多接口和能力,程序员基于这些功能进行的二开发,叫做微信开发 微信开放平台 微信开放平台是微信对外提供微信开放接口的一个平台,这些开放出来的微信接口,供第方的网站或App使用; 平台登录...

    微信开发的概念

    什么是微信开发

    微信这个软件,提供了聊天、支付、分享、收藏等各种功能,同时用户基数庞大;
    微信对外开放了很多接口和能力,程序员基于这些功能进行的二次开发,叫做微信开发

    微信开放平台

    微信开放平台是微信对外提供微信开放接口的一个平台,这些开放出来的微信接口,供第三方的网站或App使用;
    平台登录地址:https://open.weixin.qq.com

    微信开放平台的能力

    • 微信分享
    • 微信支付
    • 微信登录
    • 微信收藏
    • etc…

    谁会使用微信开放平台

    • 第三方的网站
    • 第三方的App
    • 后台开发人员

    开发者资质认证

    • 只有通过 开发者资质认证 后,才能使用开放平台提供的能力;
    • 个人名义,无法申请资质认证;
    • 只有企业类型的主体,才能申请资质认证

    微信公众平台

    • 微信公众平台:是运营者通过公众号这个媒介,为微信用户提供资讯和服务的平台;
    • 微信公众平台开发:是指基于微信公众号进行的业务开发;
    • 平台登录地址:https://mp.weixin.qq.com

    微信公众平台

    微信公众平台的账号分类

    在这里插入图片描述

    如何选择适合自己的公众号类型

    • 如果想简单的发送消息,达到宣传效果,建议选择订阅号;
    • 如果想用公众号获得更多的功能、提供更多的服务,例如开通微信支付,建议选择服务号;
    • 如果想用来管理内部企业员工、团队,对内使用,可申请企业微信(原企业号);
    • 如果想提供类似于手机App的服务体验,建议选择小程序;

    开放平台与公众平台的区别

    开放平台:

    • 是微信对外开放API接口的平台
    • 开放的API接口,供第三方网站和App调用
    • 后端程序员是开放平台开发的主力军

    公众平台:

    • 是基于微信公众号,为微信用户提供服务的平台
    • 所有公众号,都属于微信内开发
    • 前端程序员是公众平台开发的主力军

    小程序

    相关概念

    什么是小程序

    官方定义: 微信小程序是一种全新的连接用户与服务的媒介,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。

    小程序的特点:体积小、方便获取与传播;

    小程序的理念:用完即走;

    小程序出现的目的

    拦截用户流量入口,今后,用户每天的大多数应用需求,都可从微信小程序中得到满足

    小程序和订阅号、服务号的区别

    发布时间不同

    • 2012-07 公众平台发布
    • 2017-01 小程序发布

    入口方式不同

    • 订阅号、服务号是作为微信联系人存在的
    • 小程序有自己的独立入口

    体验效果不同

    • 订阅号、服务号体验差、无法提供类似于App的体验
    • 小程序模拟了手机App的体验,更人性化

    小程序适合的业务

    适合做用完即走的应用:

    • 例如:点外卖、打车、代驾、共享单车等;

    不适合做重度依赖的应用:

    • 例如:音乐视频播放类、大型手机游戏类等;

    小程序和传统手机App的区别

    开发原理不同

    • App:基于手机操作系统提供的API进行开发;
    • 小程序:基于微信提供的API进行开发;

    运行方式不同

    • App:直接安装并运行在手机操作系统之上;
    • 小程序:必须基于手机微信才能安装和运行;
      在这里插入图片描述

    小程序使用

    小程序组件

    组件是视图层的基本组成单元,它自带一些功能与微信风格一致的样式;
    一个组件通常包括 开始标签 和 结束标签,属性 用来修饰这个组件,内容 在两个标签之内
    注意:所有 组件名称 与 属性名称 都是小写

    API

    API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数

    目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力。通过API,开发人员无需访问程序的源码,或理解其内部工作机制的细节。

    微信同样提供了对外访问的 API ,这些 API 是微信开放给小程序开发者使用的能力。例如:微信扫码、获取用户的地理位置、微信支付等等。丰富的微信 API 和庞大的用户群体,是小程序开发的魅力所在

    API 的三种分类

    事件监听 API

    • 特点:这类 API 以 on 开头,用来监听某个事件是否触发
    • 举例:wx.onNetworkStatusChange(function callback) // 当网络状态更改时触发此事件

    同步 API

    • 特点1:以 Sync 结尾的 API 都是同步 API
    • 特点2:同步 API 的执行结果,可以通过函数返回值直接获取
    • 举例:var batteryInfo = wx.getBatteryInfoSync() // 获取电池信息

    异步 API

    • 特点:通常需要指定回调函数接收调用的结果;小程序中,大多数 API 都是异步 API
    • 举例:wx.request(Object object) // 发起 HTTPS 网络请求

    官方示例中展示的API接口

    可以扫码体验小程序中的API接口,例如:发起支付、获取手机网络状态、发起网络请求等
    在这里插入图片描述

    组件和API的异同点

    相同点:

    • 组件 和 API 都是微信官方提供的
    • 组件 和 API 的目的,都是为了方便小程序的快速开发

    不同点:

    • 组件以UI结构布局为主,一般不需要处理业务逻辑
    • API 以纯业务逻辑为主,一般没有对应的UI结构

    小程序开发账号

    注册小程序开发账号

    小程序账号设置

    刚注册的小程序账号,基本信息尚未被初始化,推荐进行账号设置:

    基本信息设置

    • 登录小程序后台 -> 设置 -> 基本设置
    • 在基本设置中,可以设置小程序头像、小程序名称、介绍、服务类目等基本信息
      在这里插入图片描述

    开发者设置

    • 登录小程序后台 -> 开发 -> 开发设置
    • 开发者ID 是唯一的
    • 服务器域名 可以去阿里云,腾讯云申请相应域名
      在这里插入图片描述

    成员管理

    一个团队进行小程序的开发,那么团队成员的身份管理是很有必要的
    管理员可在小程序管理后台统一管理项目成员、设置项目成员的权限
    管理员可以为项目成员分配的权限如下:

    • 开发者权限:开发模块权限,可使用体验版小程序、开发者工具(IDE)
    • 运营者权限:管理、推广、设置等模块权限,可使用体验版小程序
    • 数据分析者(基础分析):统计模块权限,可使用体验版小程序

    添加成员和分配权限

    小程序开发者工具的使用

    下载并安装微信开发者工具

    根据自己的操作系统下载对应的安装包进行安装。

    下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
    在这里插入图片描述

    创建一个小程序项目

    • 打开小程序开发者工具,用微信扫码登录开发者工具
    • 点击左侧菜单中的小程序选项
    • 点击+号新建小程序项目
    • 填写项目名称
    • 选择项目存放路径(必须选择空目录)
    • 填写AppID
    • 点击新建按钮
      在这里插入图片描述

    预览小程序

    作用:通过预览功能,可以在真机环境下,快速查看小程序的实际效果

    • 点击 工具栏 -> 预览 即可弹出预览窗口
    • 预览方式:扫描二维码预览、自动预览
      在这里插入图片描述

    开发者工具主界面功能介绍

    开发者工具主界面,从上到下,从左到右,分别为五大部分:

    • 菜单栏
    • 工具栏
    • 模拟器
    • 编辑器
    • 调试器
      在这里插入图片描述
    展开全文
  • 上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。 一、微信公众平台的基本原理  在开始做之前,先简单介绍了微信公众平台的基本...

    上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。

    一、微信公众平台的基本原理

      在开始做之前,先简单介绍了微信公众平台的基本原理。

      微信服务器就相当于一个转发服务器,终端(手机、Pad等)发起请求至微信服务器,微信服务器然后将请求转发给我们的应用服务器。应用服务器处理完毕后,将响应数据回发给微信服务器,微信服务器再将具体响应信息回复到微信App终端。

      通信协议为:HTTP

      数据传输格式为:XML

      具体的流程如下图所示:

      

      来一张更加直观的图吧:

      

      我们需要做的事情,就是对微信服务器转发的HTTP请求做出响应。具体的请求内容,我们按照特定的XML格式去解析,处理完毕后,也要按照特定的XML格式返回。

    二、微信公众号接入

      在微信公众平台开发者文档上,关于公众号接入这一节内容在接入指南上写的比较详细的,文档中说接入公众号需要3个步骤,分别是:

      1、填写服务器配置
      2、验证服务器地址的有效性
      3、依据接口文档实现业务逻辑

      其实,第3步已经不能算做公众号接入的步骤,而是接入之后,开发人员可以根据微信公众号提供的接口所能做的一些开发。

      第1步中服务器配置包含服务器地址(URL)、Token和EncodingAESKey。

      服务器地址即公众号后台提供业务逻辑的入口地址,目前只支持80端口,之后包括接入验证以及任何其它的操作的请求(例如消息的发送、菜单管理、素材管理等)都要从这个地址进入。接入验证和其它请求的区别就是,接入验证时是get请求,其它时候是post请求;

      Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性);

      EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。本例中全部以未加密的明文消息方式,不涉及此配置项。

      第2步,验证服务器地址的有效性,当点击“提交”按钮后,微信服务器将发送一个http的get请求到刚刚填写的服务器地址,并且携带四个参数:

      

      接到请求后,我们需要做如下三步,若确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,否则接入失败。

      1. 将token、timestamp、nonce三个参数进行字典序排序
      2. 将三个参数字符串拼接成一个字符串进行sha1加密
      3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

      下面我们用Java代码来演示一下这个验证过程

      使用IDE(Eclipse或者IntelliJ IDEA)创建一个JavaWeb项目,这里我使用的是IntelliJ IDEA,项目目录结构如下图所示:

      

      编写一个servlevt,在其中的doGet方法中定义校验方法,具体代码如下:

    复制代码
      1 package me.gacl.wx.web.servlet;
      2 
      3 import javax.servlet.ServletException;
      4 import javax.servlet.annotation.WebServlet;
      5 import javax.servlet.http.HttpServlet;
      6 import javax.servlet.http.HttpServletRequest;
      7 import javax.servlet.http.HttpServletResponse;
      8 import java.io.IOException;
      9 import java.security.MessageDigest;
     10 import java.security.NoSuchAlgorithmException;
     11 import java.util.Arrays;
     12 
     13 /**
     14  * Created by xdp on 2016/1/25.
     15  * 使用@WebServlet注解配置WxServlet,urlPatterns属性指明了WxServlet的访问路径
     16  */
     17 @WebServlet(urlPatterns="/WxServlet")
     18 public class WxServlet extends HttpServlet {
     19 
     20     /**
     21      * Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
     22      * 比如这里我将Token设置为gacl
     23      */
     24     private final String TOKEN = "gacl";
     25 
     26     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     27 
     28     }
     29 
     30     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     31         System.out.println("开始校验签名");
     32         /**
     33          * 接收微信服务器发送请求时传递过来的4个参数
     34          */
     35         String signature = request.getParameter("signature");//微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
     36         String timestamp = request.getParameter("timestamp");//时间戳
     37         String nonce = request.getParameter("nonce");//随机数
     38         String echostr = request.getParameter("echostr");//随机字符串
     39         //排序
     40         String sortString = sort(TOKEN, timestamp, nonce);
     41         //加密
     42         String mySignature = sha1(sortString);
     43         //校验签名
     44         if (mySignature != null && mySignature != "" && mySignature.equals(signature)) {
     45             System.out.println("签名校验通过。");
     46             //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
     47             //response.getWriter().println(echostr);
     48             response.getWriter().write(echostr);
     49         } else {
     50             System.out.println("签名校验失败.");
     51         }
     52 
     53     }
     54 
     55     /**
     56      * 排序方法
     57      *
     58      * @param token
     59      * @param timestamp
     60      * @param nonce
     61      * @return
     62      */
     63     public String sort(String token, String timestamp, String nonce) {
     64         String[] strArray = {token, timestamp, nonce};
     65         Arrays.sort(strArray);
     66         StringBuilder sb = new StringBuilder();
     67         for (String str : strArray) {
     68             sb.append(str);
     69         }
     70 
     71         return sb.toString();
     72     }
     73 
     74     /**
     75      * 将字符串进行sha1加密
     76      *
     77      * @param str 需要加密的字符串
     78      * @return 加密后的内容
     79      */
     80     public String sha1(String str) {
     81         try {
     82             MessageDigest digest = MessageDigest.getInstance("SHA-1");
     83             digest.update(str.getBytes());
     84             byte messageDigest[] = digest.digest();
     85             // Create Hex String
     86             StringBuffer hexString = new StringBuffer();
     87             // 字节数组转换为 十六进制 数
     88             for (int i = 0; i < messageDigest.length; i++) {
     89                 String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
     90                 if (shaHex.length() < 2) {
     91                     hexString.append(0);
     92                 }
     93                 hexString.append(shaHex);
     94             }
     95             return hexString.toString();
     96 
     97         } catch (NoSuchAlgorithmException e) {
     98             e.printStackTrace();
     99         }
    100         return "";
    101     }
    102 }
    复制代码

      我这里用的Servlet3.0,使用Servlet3.0的好处就是可以直接使用@WebServlet注解映射Servlet的访问路径,不再需要在web.xml文件中进行配置.

      将WxStudy项目部署到Tomcat服务器中运行,直接启动项目,然后用ngrok将本地8080端口映射到外网(如何使用ngrok请参考博客《微信开发学习总结(一)——微信开发环境搭建》)。如下图所示:

      

      测试是否可以通过http://xdp.ngrok.natapp.cn地址正常访问,测试结果如下:

      

      可以看到,我们的项目已经可以被外网正常访问到了。

      进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和token,如下图所示:

     

      点击提交按钮,页面会提示配置成功,

      

      IDE的控制台中输出了校验通过的信息,如下图所示:

      

      到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。

    三、access_token管理

    3.1、access_token介绍

      我们的公众号和微信服务器对接成功之后,接下来要做的就是根据我们的业务需求调用微信公众号提供的接口来实现相应的逻辑了。在使用微信公众号接口中都需要一个access_token。

      关于access_token,在微信公众平台开发者文档上的获取接口调用凭据有比较详细的介绍:access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token,开发者需要妥善保存access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。并且每天调用获取access_token接口的上限是2000次。

      总结以上说明,access_token需要做到以下两点:

      1.因为access_token有2个小时的时效性,要有一个机制保证最长2个小时重新获取一次。

      2.因为接口调用上限每天2000次,所以不能调用太频繁。

    3.2、微信公众平台提供的获取access_token的接口

      关于access_token的获取方式,在微信公众平台开发者文档上有说明,公众号可以调用一个叫"获取access token"的接口来获取access_token。

      获取access token接口调用请求说明

        http请求方式: GET

        请求的URL地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
        
      

      我们可以看到,调用过程中需要传递appID和AppSecret,appID和AppSecret是在申请公众号的时候自动分配给公众号的,相当于公众号的身份标示,使用微信公众号的注册帐号登录到腾讯提供的微信公众号管理后台就可以看到自己申请的公众号的AppID和AppSecret,如下图所示:

      

      这是我申请公众号测试帐号时分配到的AppID和AppSecret。

    3.3、获取access_token方案以及具体实现

      这里采用的方案是这样的,定义一个默认启动的servlet,在init方法中启动一个Thread,这个进程中定义一个无限循环的方法,用来获取access_token,当获取成功后,此进程休眠7000秒(7000秒=1.944444444444444小时),否则休眠3秒钟继续获取。流程图如下:

      

      下面正式开始在工程中实现以上思路,因为返回的数据都是json格式,这里会用到阿里的fastjson库,为构造请求和处理请求后的数据序列化和反序列化提供支持。

      1.定义一个AccessToken实体类

    复制代码
     1 package me.gacl.wx.entry;
     2 
     3 /**
     4  * AccessToken的数据模型
     5  * Created by xdp on 2016/1/25.
     6  */
     7 public class AccessToken {
     8 
     9     //获取到的凭证
    10     private String accessToken;
    11     //凭证有效时间,单位:秒
    12     private int expiresin;
    13 
    14     public String getAccessToken() {
    15         return accessToken;
    16     }
    17 
    18     public void setAccessToken(String accessToken) {
    19         this.accessToken = accessToken;
    20     }
    21 
    22     public int getExpiresin() {
    23         return expiresin;
    24     }
    25 
    26     public void setExpiresin(int expiresin) {
    27         this.expiresin = expiresin;
    28     }
    29 }
    复制代码

     2.定义一个AccessTokenInfo类,用于存放获取到的AccessToken,代码如下:

    复制代码
     1 package me.gacl.wx.Common;
     2 
     3 import me.gacl.wx.entry.AccessToken;
     4 
     5 /**
     6  * Created by xdp on 2016/1/25.
     7  */
     8 public class AccessTokenInfo {
     9 
    10     //注意是静态的
    11     public static AccessToken accessToken = null;
    12 }
    复制代码

      3.编写一个用于发起https请求的工具类NetWorkHelper,代码如下:

    复制代码
     1 package me.gacl.wx.util;
     2 
     3 import javax.net.ssl.*;
     4 import java.io.BufferedReader;
     5 import java.io.InputStream;
     6 import java.io.InputStreamReader;
     7 import java.net.URL;
     8 import java.security.cert.CertificateException;
     9 import java.security.cert.X509Certificate;
    10 
    11 /**
    12  * 访问网络用到的工具类
    13  */
    14 public class NetWorkHelper {
    15 
    16     /**
    17      * 发起Https请求
    18      * @param reqUrl 请求的URL地址
    19      * @param requestMethod
    20      * @return 响应后的字符串
    21      */
    22     public String getHttpsResponse(String reqUrl, String requestMethod) {
    23         URL url;
    24         InputStream is;
    25         String resultData = "";
    26         try {
    27             url = new URL(reqUrl);
    28             HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
    29             TrustManager[] tm = {xtm};
    30 
    31             SSLContext ctx = SSLContext.getInstance("TLS");
    32             ctx.init(null, tm, null);
    33 
    34             con.setSSLSocketFactory(ctx.getSocketFactory());
    35             con.setHostnameVerifier(new HostnameVerifier() {
    36                 @Override
    37                 public boolean verify(String arg0, SSLSession arg1) {
    38                     return true;
    39                 }
    40             });
    41 
    42 
    43             con.setDoInput(true); //允许输入流,即允许下载
    44 
    45             //在android中必须将此项设置为false
    46             con.setDoOutput(false); //允许输出流,即允许上传
    47             con.setUseCaches(false); //不使用缓冲
    48             if (null != requestMethod && !requestMethod.equals("")) {
    49                 con.setRequestMethod(requestMethod); //使用指定的方式
    50             } else {
    51                 con.setRequestMethod("GET"); //使用get请求
    52             }
    53             is = con.getInputStream();   //获取输入流,此时才真正建立链接
    54             InputStreamReader isr = new InputStreamReader(is);
    55             BufferedReader bufferReader = new BufferedReader(isr);
    56             String inputLine;
    57             while ((inputLine = bufferReader.readLine()) != null) {
    58                 resultData += inputLine + "\n";
    59             }
    60             System.out.println(resultData);
    61 
    62         } catch (Exception e) {
    63             e.printStackTrace();
    64         }
    65         return resultData;
    66     }
    67 
    68     X509TrustManager xtm = new X509TrustManager() {
    69         @Override
    70         public X509Certificate[] getAcceptedIssuers() {
    71             return null;
    72         }
    73 
    74         @Override
    75         public void checkServerTrusted(X509Certificate[] arg0, String arg1)
    76                 throws CertificateException {
    77 
    78         }
    79 
    80         @Override
    81         public void checkClientTrusted(X509Certificate[] arg0, String arg1)
    82                 throws CertificateException {
    83 
    84         }
    85     };
    86 }
    复制代码

      getHttpsResponse方法是请求一个https地址,参数requestMethod为字符串“GET”或者“POST”,传null或者“”默认为get方式。

      4.定义一个默认启动的servlet,在init方法中启动一个新的线程去获取accessToken

    复制代码
     1 package me.gacl.wx.web.servlet;
     2 
     3 import com.alibaba.fastjson.JSON;
     4 import com.alibaba.fastjson.JSONObject;
     5 import me.gacl.wx.Common.AccessTokenInfo;
     6 import me.gacl.wx.entry.AccessToken;
     7 import me.gacl.wx.util.NetWorkHelper;
     8 
     9 import javax.servlet.ServletException;
    10 import javax.servlet.annotation.WebInitParam;
    11 import javax.servlet.annotation.WebServlet;
    12 import javax.servlet.http.HttpServlet;
    13 
    14 /**
    15  * 用于获取accessToken的Servlet
    16  * Created by xdp on 2016/1/25.
    17  */
    18 @WebServlet(
    19         name = "AccessTokenServlet",
    20         urlPatterns = {"/AccessTokenServlet"},
    21         loadOnStartup = 1,
    22         initParams = {
    23                 @WebInitParam(name = "appId", value = "wxbe4d433e857e8bb1"),
    24                 @WebInitParam(name = "appSecret", value = "ccbc82d560876711027b3d43a6f2ebda")
    25         })
    26 public class AccessTokenServlet extends HttpServlet {
    27 
    28     @Override
    29     public void init() throws ServletException {
    30         System.out.println("启动WebServlet");
    31         super.init();
    32 
    33         final String appId = getInitParameter("appId");
    34         final String appSecret = getInitParameter("appSecret");
    35 
    36         //开启一个新的线程
    37         new Thread(new Runnable() {
    38             @Override
    39             public void run() {
    40                 while (true) {
    41                     try {
    42                         //获取accessToken
    43                         AccessTokenInfo.accessToken = getAccessToken(appId, appSecret);
    44                         //获取成功
    45                         if (AccessTokenInfo.accessToken != null) {
    46                             //获取到access_token 休眠7000秒,大约2个小时左右
    47                             Thread.sleep(7000 * 1000);
    48                             //Thread.sleep(10 * 1000);//10秒钟获取一次
    49                         } else {
    50                             //获取失败
    51                             Thread.sleep(1000 * 3); //获取的access_token为空 休眠3秒
    52                         }
    53                     } catch (Exception e) {
    54                         System.out.println("发生异常:" + e.getMessage());
    55                         e.printStackTrace();
    56                         try {
    57                             Thread.sleep(1000 * 10); //发生异常休眠1秒
    58                         } catch (Exception e1) {
    59 
    60                         }
    61                     }
    62                 }
    63 
    64             }
    65         }).start();
    66     }
    67 
    68     /**
    69      * 获取access_token
    70      *
    71      * @return AccessToken
    72      */
    73     private AccessToken getAccessToken(String appId, String appSecret) {
    74         NetWorkHelper netHelper = new NetWorkHelper();
    75         /**
    76          * 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
    77          */
    78         String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
    79         //此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
    80         String result = netHelper.getHttpsResponse(Url, "");
    81         System.out.println("获取到的access_token="+result);
    82         //使用FastJson将Json字符串解析成Json对象
    83         JSONObject json = JSON.parseObject(result);
    84         AccessToken token = new AccessToken();
    85         token.setAccessToken(json.getString("access_token"));
    86         token.setExpiresin(json.getInteger("expires_in"));
    87         return token;
    88     }
    89 }
    复制代码

      AccessTokenServlet采用注解的方式进行配置
      至此代码实现完毕,将项目部署,看到控制台输出如下:

      

      为了方便看效果,可以把休眠时间设置短一点,比如10秒获取一次,然后将access_token输出。

      下面做一个测试jsp页面,并把休眠时间设置为10秒,这样过10秒刷新页面,就可以看到变化

    复制代码
     1 <%-- Created by IntelliJ IDEA. --%>
     2 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     3 <%@ page import="me.gacl.wx.Common.AccessTokenInfo"%>
     4 <html>
     5   <head>
     6     <title></title>
     7   </head>
     8   <body>
     9     微信学习
    10     <hr/>
    11     access_token为:<%=AccessTokenInfo.accessToken.getAccessToken()%>
    12   </body>
    13 </html>
    复制代码

      

      10秒钟后刷新页面,access_token变了,如下图所示:

      

    四、接收微信服务器发送的消息并做出响应

      经过上述的三步,我们开发前的准备工作已经完成了,接下来要做的就是接收微信服务器发送的消息并做出响应

      从微信公众平台接口消息指南中可以了解到,当用户向公众帐号发消息时,微信服务器会将消息通过POST方式提交给我们在接口配置信息中填写的URL,而我们就需要在URL所指向的请求处理类WxServlet的doPost方法中接收消息、处理消息和响应消息。

    4.1.编写一个用于处理消息的工具类

      编写处理消息的工具栏,工具类代码如下:

    复制代码
      1 package me.gacl.wx.util;
      2 
      3 import org.dom4j.Document;
      4 import org.dom4j.Element;
      5 import org.dom4j.io.SAXReader;
      6 
      7 import javax.servlet.http.HttpServletRequest;
      8 import java.io.InputStream;
      9 import java.text.DateFormat;
     10 import java.text.SimpleDateFormat;
     11 import java.util.Date;
     12 import java.util.HashMap;
     13 import java.util.List;
     14 import java.util.Map;
     15 
     16 /**
     17  * 消息处理工具类
     18  * Created by xdp on 2016/1/26.
     19  */
     20 public class MessageHandlerUtil {
     21 
     22     /**
     23      * 解析微信发来的请求(XML)
     24      * @param request
     25      * @return map
     26      * @throws Exception
     27      */
     28     public static Map<String,String> parseXml(HttpServletRequest request) throws Exception {
     29         // 将解析结果存储在HashMap中
     30         Map<String,String> map = new HashMap();
     31         // 从request中取得输入流
     32         InputStream inputStream = request.getInputStream();
     33         System.out.println("获取输入流");
     34         // 读取输入流
     35         SAXReader reader = new SAXReader();
     36         Document document = reader.read(inputStream);
     37         // 得到xml根元素
     38         Element root = document.getRootElement();
     39         // 得到根元素的所有子节点
     40         List<Element> elementList = root.elements();
     41 
     42         // 遍历所有子节点
     43         for (Element e : elementList) {
     44             System.out.println(e.getName() + "|" + e.getText());
     45             map.put(e.getName(), e.getText());
     46         }
     47 
     48         // 释放资源
     49         inputStream.close();
     50         inputStream = null;
     51         return map;
     52     }
     53 
     54     // 根据消息类型 构造返回消息
     55     public static String buildXml(Map<String,String> map) {
     56         String result;
     57         String msgType = map.get("MsgType").toString();
     58         System.out.println("MsgType:" + msgType);
     59         if(msgType.toUpperCase().equals("TEXT")){
     60             result = buildTextMessage(map, "孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!");
     61         }else{
     62             String fromUserName = map.get("FromUserName");
     63             // 开发者微信号
     64             String toUserName = map.get("ToUserName");
     65             result = String
     66                     .format(
     67                             "<xml>" +
     68                                     "<ToUserName><![CDATA[%s]]></ToUserName>" +
     69                                     "<FromUserName><![CDATA[%s]]></FromUserName>" +
     70                                     "<CreateTime>%s</CreateTime>" +
     71                                     "<MsgType><![CDATA[text]]></MsgType>" +
     72                                     "<Content><![CDATA[%s]]></Content>" +
     73                                     "</xml>",
     74                             fromUserName, toUserName, getUtcTime(),
     75                             "请回复如下关键词:\n文本\n图片\n语音\n视频\n音乐\n图文");
     76         }
     77 
     78         return result;
     79     }
     80 
     81     /**
     82      * 构造文本消息
     83      *
     84      * @param map
     85      * @param content
     86      * @return
     87      */
     88     private static String buildTextMessage(Map<String,String> map, String content) {
     89         //发送方帐号
     90         String fromUserName = map.get("FromUserName");
     91         // 开发者微信号
     92         String toUserName = map.get("ToUserName");
     93         /**
     94          * 文本消息XML数据格式
     95          * <xml>
     96              <ToUserName><![CDATA[toUser]]></ToUserName>
     97              <FromUserName><![CDATA[fromUser]]></FromUserName>
     98              <CreateTime>1348831860</CreateTime>
     99              <MsgType><![CDATA[text]]></MsgType>
    100              <Content><![CDATA[this is a test]]></Content>
    101              <MsgId>1234567890123456</MsgId>
    102          </xml>
    103          */
    104         return String.format(
    105                 "<xml>" +
    106                         "<ToUserName><![CDATA[%s]]></ToUserName>" +
    107                         "<FromUserName><![CDATA[%s]]></FromUserName>" +
    108                         "<CreateTime>%s</CreateTime>" +
    109                         "<MsgType><![CDATA[text]]></MsgType>" +
    110                         "<Content><![CDATA[%s]]></Content>" + "</xml>",
    111                 fromUserName, toUserName, getUtcTime(), content);
    112     }
    113 
    114     private static String getUtcTime() {
    115         Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是当前系统时间
    116         DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 设置显示格式
    117         String nowTime = df.format(dt);
    118         long dd = (long) 0;
    119         try {
    120             dd = df.parse(nowTime).getTime();
    121         } catch (Exception e) {
    122 
    123         }
    124         return String.valueOf(dd);
    125     }
    126 }
    复制代码

      为了方便解析微信服务器发送给我们的xml格式的数据,这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-2.0.0-RC1.jar)

      

    4.2.在WxServlet的doPost方法中处理请求

      WxServlet的doPost方法的代码如下:

    复制代码
     1  /**
     2      * 处理微信服务器发来的消息
     3      */
     4     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     5         // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
     6         // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
     7         request.setCharacterEncoding("UTF-8");
     8         response.setCharacterEncoding("UTF-8");
     9         System.out.println("请求进入");
    10         String result = "";
    11         try {
    12             Map<String,String> map = MessageHandlerUtil.parseXml(request);
    13             System.out.println("开始构造消息");
    14             result = MessageHandlerUtil.buildXml(map);
    15             System.out.println(result);
    16             if(result.equals("")){
    17                 result = "未正确响应";
    18             }
    19         } catch (Exception e) {
    20             e.printStackTrace();
    21             System.out.println("发生异常:"+ e.getMessage());
    22         }
    23         response.getWriter().println(result);
    24     }
    复制代码

      到此,我们的WxServlet已经可以正常处理用户的请求并做出响应了.接下来我们测试一下我们开发好的公众号应用是否可以正常和微信用户交互

      将WxStudy部署到Tomcat服务器,启动服务器,记得使用ngrok将本地Tomcat服务器的8080端口映射到外网,保证接口配置信息的URL地址:http://xdp.ngrok.natapp.cn/WxServlet可以正常与微信服务器通信

      登录到我们的测试公众号的管理后台,然后用微信扫描一下测试号的二维码,如下图所示:

      

     

      

      

      关注成功后,我们开发好的公众号应用会先给用户发一条提示用户操作的文本消息,微信用户根据提示操作输入"文本",我们的公众号应用接收到用户请求后就给用户回复了一条我们自己构建好的文本消息,如下图所示:

      

      我们的公众号应用响应给微信用户的文本消息的XML数据如下:

    复制代码
    1 <xml>
    2   <ToUserName><![CDATA[ojADgs0eDaqh7XkTM9GvDmdYPoDw]]></ToUserName>
    3   <FromUserName><![CDATA[gh_43df3882c452]]></FromUserName>
    4   <CreateTime>1453755900000</CreateTime>
    5   <MsgType><![CDATA[text]]></MsgType>
    6   <Content><![CDATA[孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!]]></Content>
    7 </xml>
    复制代码

      测试公众号的管理后台也可以看到关注测试号的用户列表,如下图所示:

      

      通过这个简单的入门程序,我们揭开了微信开发的神秘面纱了.

      本篇的内容就这么多了。本文的内容和代码参考了用java开发微信公众号:公众号接入和access_token管理(二)这篇博客,在此对作者"风的姿态"表示感谢。

      下载测试项目部署运行时,由于项目中使用的是Servlet3.0,所以要求部署的Tomcat必须是7.x,以前也写过几篇关于Servlet3.0的博客,大家有兴趣的话可以去看看,本篇博文对应的测试项目代码下载http://pan.baidu.com/s/1hrfcGks

    展开全文
  • 微信开发与代码的编写(一) 微信开发环境的搭建 目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型    我在平时的工作中接触得比较多的就是基于Android的Native...
  • 需求在公众号内的输入任意文字,文字相关的第方网站链接实现简单后台设置我们在微信公众平台上,是可以进行简单的自定义消息回复的。这里的显然不能满足我们的需求。配置服务器配置域名略配置测试服务器略。和一般...
  • 程序很简单仅仅十行代码左右,实现了机器人自动与你的微信好友聊天,下面是我的机器人小笼包跟自己微信好友聊天的截图。 二、程序介绍 本程序通过itchat获取微信的相关消息,将微信消息传输到机器人接口(本程序...
  • 过年前后做了个微信公众号项目,已经过去一段时间了,抽空回忆总结下基本流程吧,不然很快估计自己就忘了。。 微信公众平台官网:https://mp.weixin.qq.com 文章目录一、注册公众号二、了解公众号管理页面、必备...
  • 本文档基于腾讯公司微信次开发的相关规范,对微信次开发的流程、步骤做了相关的说明,方便程序设计和开发人员快速掌握微信公众平台开发技术,便于提高代码的编写效率以及减少出现错误概率。 使用范围 本...
  • 微信公众号开发现在在企业应用中,比较火爆的一款应用,微信开放了第方公众平台,作为开发者只需要按照公众平台的开发文档就能快速开发应用,废话不多说,下面我来介绍下个人公众号申请,第一个api调用,自动消息回复...
  • 微信开发sdk_二开发微信API更新日志 微信开发sdk_二开发微信API更新日志 2.6.35 清粉优化 小于1k的图片发送失败的问题 联系人资料新增电话号码和描述 发布2.6.31->2.6.34 清粉发消息改成发...
  • 对于刚开始接触第方平台开发的人员来说,微信开发平台的文档相对比较复杂,现阶段网上博客文章较为杂乱零散,无法提供有效的指导。 通过本达人课的学习,读者将熟悉第方平台中的授权流程、Token管理、相关功能的...
  • 微信开发时候在做消息接口交互的时候需要使用带微信加密ID(OpenId),下面讲讲述2中类型方式获取微信OpenID,接收事件推送方式和网页授权获取用户基本信息方式获取。
  • 说明:企业微信\color{red}{企业微信}企业微信,和微信企业\color{red}{微信企业}微信企业 2. 注册账号 https://work.weixin.qq.com/ 并且登录 手机上最好也同时下载“企业微信”app 进入应用管理可以看见创建...
  • 所有微信开发的相关内容,都需要参考官方文档。 [微信公众平台|开发文档] http://mp.weixin.qq.com/wiki/home/。 一、通过网页授权,可以获取用户微信的基本信息。 二、总共有5个步骤: 1 :用户同意授权,...
  •  微信支付开发系统开发流程及完整Demo展示3. 微信公共服务平台开发Net实现获取Access Token4. 微信公众平台开发文档获取用户地理位置5. 微信公共服务平台开发Net实现解决Access Token过期的问题6. 微信...
1 2 3 4 5 ... 20
收藏数 18,065
精华内容 7,226