Java微信开发入门

前言:
1、先简单介绍一下微信公众号的开发者模式和编辑模式,玩过微信公众号的同志都知道,在微信公众号后台功能栏里面可以编辑消息自动回复,关键字回复,自定义菜单等功能,这些都是在视图页面下编辑的,叫编辑模式。

这里写图片描述

在开发栏目里面,有基本配置和开发工具,在基本配置里面点击启用就能进入开发者模式。我想强调的是:编辑模式和开发者模式是互斥的,一旦开启了开发者模式,编辑模式下设置的逻辑全部都会失效,微信服务器会把管理权交给开发者,由代码来完成用户交互逻辑。

这里写图片描述

2、然后简单介绍一款外网映射工具Sunny-Ngrok。在这里强调,这个映射工具在开发过程起到非常重要的作用,因为微信服务器是在外网的,我们本地开发的代码是部署在本地tomcat服务器的,本地tomcat是不能访问外网的微信服务器的,所以要借助Ngrok这个工具把我们本地ip映射到外网,这样本地代码就可以跟微信服务器交互了,这样调试代码就非常方便了。

Ngrok是外国的一款软件,服务器在美国,正常情况下,国内网络也是不能访问的(被墙了,大家都懂的)。在这里真的非常感谢大神Sunny把服务器部署到了香港,防止被墙让我们能够访问美国Ngrok服务器。更重要的是免费使用啊!免费使用啊!免费使用啊!

准备工作:
1.首先你要有一个微信公众号,申请流程很简单,不在赘述。
2.外网映射工具:Sunny-Ngrok
3.Eclipse,tomcat,jdk(这些都是最基本的开发工具,不在强调了)

一、部署好外网映射工具。
1、下载Sunny-Ngrok客户端:http://www.ngrok.cc/
2、注册Sunny-Ngrok后台管理账号:http://www.ngrok.cc/login/register
3、登录之后开通隧道:隧道名字随便填;前置域名就是你要映射的外网地址;本地端口就是本地ip:127.0.0.1+tomcat端口号:8080(本地的都可以修改,但是微信只能访问80和443端口,所以最好用默认的,不要修改)http验证用户名和密码不要填,因为到时这个地址要部署到微信后台的,设置之后微信服务器就不能访问这个映射地址了,微信服务器不知道你的用户名和密码。

这里写图片描述

这里写图片描述

4、本地运行Sunny-Ngrok启动工具.bat,将隧道id复制到启动工具,这样本地ip就映射到外网了。

这里写图片描述

这里写图片描述

这里写图片描述

二、编写接入代码。
1、启动Eclipse,新建web工程Weixin,需要导入3个jar包,目录结构如下:

这里写图片描述

2、编写WeixinServlet,这个类负责与微信服务器交互数据。参看微信开发文档,可知,开发者接入需要通过GET请求验证。

这里写图片描述

所以doGet()方法代码如下:

/**
     * 开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //GET请求携带参数如下四个
        String signature = req.getParameter("signature");
        String timestamp = req.getParameter("timestamp");
        String nonce = req.getParameter("nonce");
        String echostr = req.getParameter("echostr");

        //检验微信服务器传来的参数
        boolean check = CheckUtil.checkSignature(signature, timestamp, nonce);

        PrintWriter out = resp.getWriter();
        //若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
        if (check) {    
            //通过print()方法将参数传送到微信服务器
            out.print(echostr);
        }
    }

上面用到了一个CheckUtil工具类,里面有一个checkSignature()方法,传入三个参数,类中还要用到sha1加密算法,算法是网上找的,自己写还有可能写错,代码如下:

public class CheckUtil {

    private static final String token = "jiaxiang";

    /**
     * 开发者通过检验signature对请求进行校验(下面有校验方式)。
     * 若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,
     * 否则接入失败。加密/校验流程如下:
     * 1)将token、timestamp、nonce三个参数进行字典序排序
     * 2)将三个参数字符串拼接成一个字符串进行sha1加密
     * 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature,String timestamp,String nonce){
        String[] arr = new String[]{token,timestamp,nonce};

        //排序
        Arrays.sort(arr);

        //生成字符串
        StringBuffer content = new StringBuffer();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }

        //sha1加密
        String temp = getSha1(content.toString());

        return temp.equals(signature);
    }


    //sha1加密算法实现
    public static String getSha1(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("UTF-8"));
            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 (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}   

web.xml加上以下代码:

 <servlet>
    <servlet-name>WeixinServlet</servlet-name>
    <servlet-class>com.laijiaxiang.servlet.WeixinServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>WeixinServlet</servlet-name>
    <url-pattern>/wx.do</url-pattern>
  </servlet-mapping>

到这里开发者接入校验的代码就写完了,把代码部署到本地tomcat服务器,Sunny-Ngrok会自动把我们的项目映射到外网上。

下面到微信公众号后台校验。在基本配置栏修改服务器配置,把我们外网路径和项目WeixinServlet填写到服务器URl栏;Token与代码里的Token常量一致,这个可以随便写没有要求;EncodingAESKey点击随机生成就好。然后点击提交,提示提交成功,开发者接入成功,下面就可以写业务代码了。

这里写图片描述

三、代码实现被添加自动回复,消息自动回复,关键词自动回复。

1、由微信开发文档可知,当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
文本消息xml和参数如下:

这里写图片描述

所以在WeixinServlet类上添加doPost()方法,代码如下:

/**
     * 当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        try {
            Map<String, String> map = MessageUtil.xmlToMap(req);
            String toUserName = map.get("ToUserName");
            String fromUserName = map.get("FromUserName");
//          String createTime = map.get("CreateTime");
            String msgType = map.get("MsgType");
            String content = map.get("Content");
//          String msgId = map.get("MsgId");
            System.out.println("用户输入:"+content);
            System.out.println("系统回复:");
            String message = null;
            //判断如果为文本类型消息
            if (MessageUtil.MESSAGE_TEXT.equals(msgType)) {
                //判断输入的关键字,回复指定内容
                if ("1".equals(content)) {
                    message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.firstMenu());
                    System.out.println(message);
                }else if ("2".equals(content)) {
                    message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.secondMenu());
                    System.out.println(message);
                }else if ("?".equals(content) || "?".equals(content)) {
                    //回复主菜单文本
                    message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText());
                    System.out.println(message);
                }else { //非关键字,回复原文
                    TextMessage text = new TextMessage();
                    text.setFromUserName(toUserName);
                    text.setToUserName(fromUserName);
                    text.setMsgType("text");
                    text.setCreateTime(new Date().getTime()+"");
                    text.setContent("您发送的消息是:"+content);
                    message = MessageUtil.textMessageToXml(text);
                    System.out.println(message);
                }


            }
            //判断如果为事件消息
            else if (MessageUtil.MESSAGE_EVENT.equals(msgType)) {
                String eventType = map.get("Event");
                //判断如果为关注事件
                if (MessageUtil.MESSAGE_SUBSCRIBE.equals(eventType)) {
                    //回复主菜单文本
                    message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText());
                    System.out.println(message);
                }
            }
//          System.out.println(message);
            //将消息返回给微信后台
            out.print(message);
        } catch (DocumentException e) {
            e.printStackTrace();
        }finally {
            out.close();
        }
    }

上面方法调用了MessageUtil类,这个类的主要作用是实现xml与集合对象的相互转换。代码如下:

/**
 * 实现xml与集合对象的相互转换
 * @author Administrator
 *
 */
public class MessageUtil {

    //文本消息类型
    public static final String MESSAGE_TEXT = "text";
    //事件消息类型
    public static final String MESSAGE_EVENT = "event";
    //关注事件类型
    public static final String MESSAGE_SUBSCRIBE = "subscribe";

    /**
     * xml转换成Map集合
     * @param request
     * @return
     * @throws IOException 
     * @throws DocumentException 
     */
    public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException{
        Map<String, String> map = new HashMap<String,String>();
        SAXReader reader = new SAXReader();

        //获取输入流
        InputStream ins = request.getInputStream();
        //将输入流转换为xml对象
        Document doc = reader.read(ins);
        //获取xml文档的根节点
        Element root = doc.getRootElement();
        //获取子节点
        List<Element> list = root.elements();
        //遍历子节点
        for (Element e : list) {
            map.put(e.getName(), e.getText());
//          System.out.println(e.getName()+"=="+e.getText());
        }

        ins.close();
        return map;
    }

    /**
     * 将文本消息对象转换为xml
     * @param textMessage
     * @return
     */
    public static String textMessageToXml(TextMessage textMessage){
        XStream xstream = new XStream();
        //将根节点改为"xml",默认是用实体类路径com.laijiaxiang.po.TextMessage
        xstream.alias("xml", textMessage.getClass());
        return xstream.toXML(textMessage);
    }

    /**
     * 初始化主菜单
     * @param toUserName
     * @param fromUserName
     * @param content
     * @return
     */
    public static String initText(String toUserName,String fromUserName,String content){
        TextMessage text = new TextMessage();
        text.setFromUserName(toUserName);
        text.setToUserName(fromUserName);
        text.setMsgType(MESSAGE_TEXT);
        text.setCreateTime(new Date().getTime()+"");
        text.setContent(content);
        return textMessageToXml(text);
    }

    /**
     * 主菜单
     * @return
     */
    public static String menuText(){
        StringBuffer sb = new StringBuffer();
        sb.append("欢迎您的关注,请按照菜单提示进行操作:\n");
        sb.append("1、《你的名字》高清资源\n");
        sb.append("2、《声之形》高清资源\n");
        sb.append("回复?调出此菜单");
        return sb.toString();
    }

    /**
     * 输入"1",回复的文本内容
     * @return
     */
    public static String firstMenu(){
        StringBuffer sb = new StringBuffer();
        sb.append("百度网盘:www.panbaidu.com/你的名字/xxxxx \n");
        sb.append("密码:xxxxx");
        return sb.toString();
    }

    /**
     * 输入"2",回复的文本内容
     * @return
     */
    public static String secondMenu(){
        StringBuffer sb = new StringBuffer();
        sb.append("百度网盘:www.panbaidu.com/声之形/xxxxx \n");
        sb.append("密码:xxxxx");
        return sb.toString();
    }

}   

还有就是TextMessage实体类,主要是记录微信服务器传过来的参数,代码如下:

public class TextMessage {
    private String ToUserName;
    private String FromUserName;
    private String CreateTime;
    private String MsgType;
    private String MsgId;
    private String Content;
    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 getMsgId() {
        return MsgId;
    }
    public void setMsgId(String msgId) {
        MsgId = msgId;
    }
    public String getContent() {
        return Content;
    }
    public void setContent(String content) {
        Content = content;
    }
}

到这里整个文本消息回复的逻辑代码就写完了。下面部署到服务器测试一下。

四、文本消息回复功能测试。

1、作为开发者我建议使用官方提供的测试账号进行测试。因为个人订阅号很多接口不能调用,但是测试号就可以,用来调试代码很方便。

这里写图片描述

进入公众平台测试账号,填写接口配置信息,点击提交。

这里写图片描述

2、提交成功之后,用手机微信扫描测试号二维码就可以在手机上测试功能了。

这里写图片描述

这里写图片描述

Eclipse后台也能看到交互的数据。

这里写图片描述

到此,Java微信开发算是入门了。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值