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

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

前期准备

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

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 <j ; i++){
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>>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<String, String> 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<String , String> xmlToMap(HttpServletRequest request){
        Map<String ,String> map = new HashMap<String , String>();
        try {
            InputStream inputStream =null;
            inputStream = request.getInputStream();
            SAXReader reader = new SAXReader();
            Document doc = reader.read(inputStream);
            Element rootElement = doc.getRootElement();
            List<Element> 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缺少子菜单数据
42001access_token超时
43001需要GET请求
43002需要POST请求
43003需要HTTPS请求
44001多媒体文件为空
44002POST的数据包为空
44003图文消息内容为空
45001多媒体文件大小超过限制
45002消息内容超过限制
45003标题字段超过限制
45004描述字段超过限制
45005链接字段超过限制
45006图片链接字段超过限制
45007语音播放时间超过限制
45008图文消息超过限制
45009接口调用超过限制
45010创建菜单个数超过限制
46001不存在媒体数据
46002不存在的菜单版本
46003不存在的菜单数据
47001解析JSON/XML内容错误

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值