微信公众号自动回复及创建菜单开发

小弟我是做android开发的,新版本H5 需求要微信公众号开发,后台人员不够,无奈 老大嫌弃 移动端事情太少,就分配给我了.

前期准备

 1.外网
因为微信公众号开发在后台配置的url 只支持外网 并且是端口80或者443 的,所以准备下载一个花生壳,进行内网穿透(如果你有自己的服务器,当我没说)。在花生壳 进行内网穿透,新增映射,选择映射类型HTTP80  固定端口   ,完成:

微信公众号自动回复及创建菜单开发_第1张图片

2.微信公众号

微信公众号开发文档

微信公众平台账号 申请

 

正式开发

1.开发配置

在微信公众平台 基本配置  或者测试号管理 里面 接口配置 ,微信服务器将发送GET请求到填写的服务器地址URL上,通过检验signature对请求进行校验,确认此次GET请求来自微信服务器,返回echostr参数内容,则接入生效。

 @RequestMapping(value = "/connectValidate.do", method = RequestMethod.GET)
    @ResponseBody
    public void connectValidate(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        logger.info("" + signature + "@" + timestamp + "$" + nonce + "^" + echostr);
         PrintWriter out = response.getWriter();
        if (CheckConnectUtils.checkConncetWithWeChat(signature, timestamp, nonce)) {
            out.print(echostr);
        }
    }

校验代码:

public class CheckConnectUtils {

    private static final String token = "token";

    /**
     * 判断是否链接匹配
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkConncetWithWeChat(String signature,String timestamp,String nonce){
        String[] arr = new String[]{token,timestamp,nonce};
        //排序
        Arrays.sort(arr);
        //生成字符串
        StringBuilder stringBuilder = new StringBuilder();
        for (String str:arr) {
            stringBuilder.append(str);
        }
        //进行SHA1加密
        String encodeString = passSha1Encode(stringBuilder.toString());
        if(signature.equals(encodeString)){
            return true;
        }else{
            return false;
        }
    }

    /**
     * 字符串进行SHA1加密
     * @param str
     * @return
     */
    public static String passSha1Encode(String str){
        if(str == null || str.length() == 0){
            return null;
        }
        char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9'
                ,'a','b','c','d','e','f'};
        try{
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes());
            byte[] md = mdTemp.digest();
            int j = md.length;
            char[] buf = new char[j*2];
            int k = 0;
            for(int i=0 ; i >>4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }
}

2.消息的接收和回复

首先我们创建消息实体类:

BaseMessage.java:

public class BaseMessage {

    private String ToUserName;
    private String FromUserName;
    private String CreateTime;
    private String MsgType;

    public String getToUserName() {
        return ToUserName;
    }

    public void setToUserName(String toUserName) {
        ToUserName = toUserName;
    }

    public String getFromUserName() {
        return FromUserName;
    }

    public void setFromUserName(String fromUserName) {
        FromUserName = fromUserName;
    }

    public String getCreateTime() {
        return CreateTime;
    }

    public void setCreateTime(String createTime) {
        CreateTime = createTime;
    }

    public String getMsgType() {
        return MsgType;
    }

    public void setMsgType(String msgType) {
        MsgType = msgType;
    }
}

文本消息TextMessage.java:

public class TextMessage {
    private String Content;
    private String  MsgId;
    private String ToUserName;
    private String FromUserName;
    private String CreateTime;
    private String MsgType;

    public String getToUserName() {
        return ToUserName;
    }

    public void setToUserName(String toUserName) {
        ToUserName = toUserName;
    }

    public String getFromUserName() {
        return FromUserName;
    }

    public void setFromUserName(String fromUserName) {
        FromUserName = fromUserName;
    }

    public String getCreateTime() {
        return CreateTime;
    }

    public void setCreateTime(String createTime) {
        CreateTime = createTime;
    }

    public String getMsgType() {
        return MsgType;
    }

    public void setMsgType(String msgType) {
        MsgType = msgType;
    }
    public String getContent() {
        return Content;
    }

    public void setContent(String content) {
        Content = content;
    }

    public String getMsgId() {
        return MsgId;
    }

    public void setMsgId(String msgId) {
        MsgId = msgId;
    }
}

前期都准备好了,此时当我们向公众账号发消息时,微信服务器将POST消息的XML数据包到我们配置URL上。我们接收到消息xml包,解析根据msgType 回复我们想要回复的消息。

@RequestMapping(value = "/connectValidate.do", method = RequestMethod.POST)
    @ResponseBody
    public void backTextMessage(HttpServletRequest request, HttpServletResponse response) {
        logger.info("公众号消息:"+ JsonUtil.toJson(request.getParameterMap()));
        try {
            PrintWriter writer = response.getWriter();
            Map map = MessageUtil.xmlToMap(request);
            logger.debug("公众号消息xmlToMap:"+ JsonUtil.toJson(map));
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            String msgType = map.get("MsgType");
            String content = map.get("Content");
            String message = null;

            logger.debug("content:"+content+",msgType:"+msgType);
            if (MessageUtil.MESSAGE_TEXT.equals(msgType)) {
	//普通文本消息回复
                    textMsg = “你输出的内容:”+content;
                
            }else if(MessageUtil.MESSAGE_EVENT.equals(msgType)){
                String event = map.get("Event");
               //事件类型
                if (MessageUtil.MESSAGE_SUBSCRIBE.equals(event)){
                  //关注公众号 回复
                    textMsg =  MessageUtil.subscriBackMessage();
                }else if(MessageUtil.MESSAGE_CLICK.equals(event)){
	//click 类型的 菜单 回复
                    textMsg="Hi~亲,有什么需要帮助的吗~\n";
                }
            }
            message = MessageUtil.initText(fromUserName, toUserName, textMsg);
            logger.debug("返回内容:"+message);
            writer.print(message);
        } catch (IOException e) {
            logger.error("公众号消息回复异常:",e);
        }
    }

MessageUtil 工具类:

public class MessageUtil {
    /**
     * 定义多种消息类型
     */
    public static final String MESSAGE_TEXT = "text";
    public static final String MESSAGE_IMAGE = "image";
    public static final String MESSAGE_VOICE = "voice";
    public static final String MESSAGE_MUSIC = "music";
    public static final String MESSAGE_VIDEO = "video";
    public static final String MESSAGE_LINK = "link";
    public static final String MESSAGE_LOCATION = "location";
    public static final String MESSAGE_EVENT = "event";
    public static final String MESSAGE_SUBSCRIBE = "subscribe";
    public static final String MESSAGE_UNSUBSCRIBE = "unsubscribe";
    public static final String MESSAGE_CLICK = "CLICK";
    public static final String MESSAGE_VIEW = "VIEW";
    //扫码事件
    public static final String MESSAGE_SCANCODE = "scancode_push";

    public static  final String CREATE_MENU_URL="https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

    public static  final String GET_ACCESS_TOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    /**
     * XML格式转为map格式
     * @param request
     * @return
     */
    public static Map xmlToMap(HttpServletRequest request){
        Map map = new HashMap();
        try {
            InputStream inputStream =null;
            inputStream = request.getInputStream();
            SAXReader reader = new SAXReader();
            Document doc = reader.read(inputStream);
            Element rootElement = doc.getRootElement();
            List elements = rootElement.elements();
            for (Element el:elements) {
                map.put(el.getName() , el.getText());
            }
            inputStream.close();
            return map ;
        } catch (Exception e) {
            e.printStackTrace();
            return null ;
        }
    }
    /**
     * 文本消息对象转为xml格式
     * @param textMessage
     * @return
     */
    public static String textMessage2Xml(TextMessage textMessage){
        XStream xStream = new XStream();
        xStream.alias("xml" , textMessage.getClass());
        return xStream.toXML(textMessage);
    }

    /**
     * 设置需要返回的文本信息
     * @param fromUserName
     * @param toUserName
     * @param content
     * @return
     */
    public static String initText(String fromUserName , String toUserName , String content){
        TextMessage text = new TextMessage();
        //注意接受消息和发送消息的顺序要烦过来
        text.setFromUserName(toUserName);
        text.setToUserName(fromUserName);
        text.setMsgType(MESSAGE_TEXT);
        long time = System.currentTimeMillis();
        text.setCreateTime(String.valueOf(time));
        text.setContent(content);
        return textMessage2Xml(text);
    }

    public static String subscriBackMessage(){
        StringBuffer sb = new StringBuffer();
        sb.append("请多支持,谢谢");
        return sb.toString();
    }

    public static  Menu initMenu(){
        Menu menu = new Menu();
        ViewButton viewButton = new ViewButton();
        viewButton.setName("开发文档");
        viewButton.setType("view");
        viewButton.setUrl("https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453");

        ViewButton viewButton2 = new ViewButton();
        viewButton2.setName("我的博客");
        viewButton2.setType("view");
        viewButton2.setUrl("https://mp.csdn.net/");

        ClickButton clickButton = new ClickButton();
        clickButton.setType("click");
        clickButton.setName("在线客服");
        clickButton.setKey("1");

        Button button = new Button();
        button.setName("在线客服");
        button.setSub_button(new Button[]{clickButton});

        menu.setButton(new Button[]{viewButton,viewButton2,button});

        return menu;
    }

    public static void main(String[] args) {
        String menu = JSONObject.fromObject(initMenu()).toString();
        System.out.println("菜单:"+menu);
        EzzHttpClient ezzHttpClient = new EzzHttpClient();
        String result = ezzHttpClient.postData(CREATE_MENU_URL.replace("ACCESS_TOKEN", getToken()), menu, "utf-8");
        System.out.println("创建菜单接口:"+result);
        if (result!=null){
            JSONObject object = JSONObject.fromObject(result);
            if (object.getInt("errcode") == 0){
                System.out.print("创建菜单成功");
            }
        }
    }

    public static String getToken(){
        String token ="";
        EzzHttpClient ezzHttpClient = new EzzHttpClient();
        String url = GET_ACCESS_TOKEN_URL.replace("APPID", "你的appid").replace("APPSECRET", "你的appsecret");
        String data = ezzHttpClient.getData(url, "utf-8");
        System.out.println("获取token接口:"+data);
        JSONObject object = JSONObject.fromObject(data);
        if (object!=null){
             token = object.getString("access_token");
        }

        return  token;
    }
}

此时如果 出现" 该公众号提供的服务出现故障,请稍后再试" ,请检查你的回复消息 体格式,或者转成XML 出现异常

3.创建菜单

首先从开发文档中知道,菜单分为:

1.click类型(点击事件)

用户点击click类型按钮后,微信服务器会通过消息接口(event类型)推送点击事件给开发者,并且带上你设置的key值,我们可以在msgType 为event类型 里通过key进行一系列回复。

2.view类型(网页)

用户点击view类型按钮后,会直接跳转到我们指定的url中。

注意:创建自定义菜单成功后,由于微信客户端缓存,不会立刻显示出来。建议测试时取消关注公众账号后,再次关注,则可以看到创建后的效果。

菜单实体类:Button.java

public class Button {
    //菜单类型
    private String type;
    //菜单名称
    private String name;
    //二级菜单
    private Button[] sub_button;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Button[] getSub_button() {
        return sub_button;
    }

    public void setSub_button(Button[] sub_button) {
        this.sub_button = sub_button;
    }
}

ClickButton.java:

public class ClickButton extends Button {
    //Click类型菜单key
    private String key;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

ViewButton.java:

public class ViewButton extends Button {
    //view类型菜单url
    private String url;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

Menu.java:

public class Menu {
    //一级菜单
    private Button[] button;

    public Button[] getButton() {
        return button;
    }

    public void setButton(Button[] button) {
        this.button = button;
    }
}
服务请求:EzzHttpClient.java
public String getData(String url,String charset){
        String result="";
        String id = Thread.currentThread().getId() + "";
        if(StringTool.isBlank(charset)){
            charset = "UTF-8";
        }
        try{
            HttpGet httpget = new HttpGet(url);
            logger.debug(id + " - about to get something from " + url);
            try(CloseableHttpResponse response = EzzHttpClient.getHttpClient().execute(httpget)){
                // get the response body as an array of bytes
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    logger.debug(id + " - " + entity.getContentLength() + " bytes read ");
                    result = EntityUtils.toString(entity, charset);
                }else{
                    logger.debug(id + " -  read nothing");
                }
                EntityUtils.consume(entity);
            }
        }catch(IOException e){
            logger.error("启动远程调用服务异常,url:"+url, e);
        }catch(Exception e){
            logger.error("调用远程服务服务失败,url:"+url, e);
        }
        return result;
    }

    public String postData(String url,String data,String charset){
        String result="";
        String id = Thread.currentThread().getId() + "";
        if(StringTool.isBlank(charset)){
            charset = "UTF-8";
        }
        try{
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(new StringEntity(data, Charset.forName(charset)));
            logger.debug(id + " - about to get something from " + url);
            try(CloseableHttpResponse response = EzzHttpClient.getHttpClient().execute(httpPost)){
                // get the response body as an array of bytes
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    logger.debug(id + " - " + entity.getContentLength() + " bytes read");
                    result = EntityUtils.toString(entity, charset);
                }else{
                    logger.debug(id + " -  read nothing ");
                }
                EntityUtils.consume(entity);
            }
        }catch(IOException e){
            logger.error("启动远程调用服务异常,url:"+url, e);
        }catch(Exception e){
            logger.error("调用远程服务服务失败,url:"+url, e);
        }
        return result;
    }

菜单创建代码实现在MessageUtil 中,手动往上翻即可(嘿嘿)。

最后分享下 创建菜单 当时遇到的坑:

1.{"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest hint: [XXXXXXXXXXXXXX]"}

  40001代表获取 access_token 时 AppSecret 错误,或者 access_token 无效

2.40018: invalid button name size

40018 不合法的按钮名字长度

3.40019: invalid button key size

 40019 代表不合法的按钮KEY长度,检查你的clickButton是否设置了key 值

4.{"errcode":48001,"errmsg":"api unauthorized, hints: [ req_id: 1QoCla0699ns81 ]"}

如果此时你用的appId和appSecret是你申请的订阅号的,那么建议你更换为测试公众号的appid和appsecret

5.获取access_token 错误码 40164  

此时在你微信公众号基本配置里配置ip白名单

 

最后附上错误码:

返回码 说明
-1 系统繁忙
0 请求成功
40001 验证失败
40002 不合法的凭证类型
40003 不合法的OpenID
40004 不合法的媒体文件类型
40005 不合法的文件类型
40006 不合法的文件大小
40007 不合法的媒体文件id
40008 不合法的消息类型
40009 不合法的图片文件大小
40010 不合法的语音文件大小
40011 不合法的视频文件大小
40012 不合法的缩略图文件大小
40013 不合法的APPID
40014 不合法的access_token
40014 不合法的access_token
40015 不合法的菜单类型
40016 不合法的按钮个数
40017 不合法的按钮个数
40018 不合法的按钮名字长度
40019 不合法的按钮KEY长度
40020 不合法的按钮URL长度
40021 不合法的菜单版本号
40022 不合法的子菜单级数
40023 不合法的子菜单按钮个数
40024 不合法的子菜单按钮类型
40025 不合法的子菜单按钮名字长度
40026 不合法的子菜单按钮KEY长度
40027 不合法的子菜单按钮URL长度
40028 不合法的自定义菜单使用用户
41001 缺少access_token参数
41002 缺少appid参数
41003 缺少refresh_token参数
41004 缺少secret参数
41005 缺少多媒体文件数据
41006 缺少media_id参数
41007 缺少子菜单数据
42001 access_token超时
43001 需要GET请求
43002 需要POST请求
43003 需要HTTPS请求
44001 多媒体文件为空
44002 POST的数据包为空
44003 图文消息内容为空
45001 多媒体文件大小超过限制
45002 消息内容超过限制
45003 标题字段超过限制
45004 描述字段超过限制
45005 链接字段超过限制
45006 图片链接字段超过限制
45007 语音播放时间超过限制
45008 图文消息超过限制
45009 接口调用超过限制
45010 创建菜单个数超过限制
46001 不存在媒体数据
46002 不存在的菜单版本
46003 不存在的菜单数据
47001 解析JSON/XML内容错误

 

 

你可能感兴趣的:(微信开发)