1. 微信公众号是一个非常轻便的项目平台,我主要是做web应用,在我看来,公众号就是一个web项目存放平台,通过view类型的菜单,可以存放多个web应用。
  2. 公众号开发入门:

配置服务器地址————即为你的公众号token验证的controller(我用的SpringMVC),这个controller比较简单,但是极为重要,这是整个项目的入口,关于微信公众号的最基本操作都放在里面,比如token验证,消息回复,其中也有一定的代码规范,比如异常处理,又比如在五秒内必须做出对微信消息的响应,如果代码不规范就会造成你的控制太明明打印的是验证成功,微信公众平台却显示配置失败的情况。

公众号开发 Java源码 微信公众号开发源码_json

令牌(Token)————这个和你检验程序中的token一致就可以。
消息加解秘钥————我选择随机生成,(43位太麻烦,建议大家还是随机生成吧)。
消息加解密方式————如果要追求安全系数可以选择安全模式,但要自己写加密程序。
wechatcontroller:
@Autowired
private WechatPostService wechatPostService;

@RequestMapping(value = "/wechat/connect", method = { RequestMethod.GET, RequestMethod.POST })
@ResponseBody
public void ConnectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException, DocumentException {
    // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
    request.setCharacterEncoding("UTF-8"); // 微信服务器POST消息时用的是UTF-8编码,在接收时也要用同样的编码,否则中文会乱码;
    response.setCharacterEncoding("UTF-8"); // 在响应消息(回复消息给用户)时,也将编码方式设置为UTF-8,原理同上;
    boolean isGet = request.getMethod().toLowerCase().equals("get");

    PrintWriter out = response.getWriter();
    try {
        if (isGet) {
            String signature = request.getParameter("signature");
            String timestamp = request.getParameter("timestamp");
            String nonce = request.getParameter("nonce");
            String echostr = request.getParameter("echostr");
            if (CheckUtils.checkSignature(signature, timestamp, nonce)) {
                System.out.println("验证成功!");
                out.write(echostr);
            } else {
                System.out.println("验证失败!");
            }
        } else {
            String respMessage = "异常消息!";
            respMessage = wechatPostService.weixinPost(request);
            out.write(respMessage);
            System.out.println(respMessage);
        }
    } catch (Exception e) {
        System.out.println("链接失败!");
    }finally {
        out.close();
    }
}

chickUtil:
/**
* 开发者模式-开发者自己填写的 token (令牌)
*/
private static final String token = "lm03";

/**
 * 功能:验证消息的确来自微信服务器
 */
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 shaString = getSha1(content.toString());
    return shaString.equals(signature);
}
    /**
    * sha1加密
    * @param str
    * @return
    */
    public static String getSha1(String str){
    if (null == str || 0 == str.length()){
        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) {
        return null;
    }
}

WechatPostService:
@SuppressWarnings("null")
public String weixinPost(HttpServletRequest request) {

String reqMessage = null;

    try {
        Map<String, String> map = MessageUtils.xmlToMap(request);
        String fromUserName = map.get("FromUserName");
        String toUserName = map.get("ToUserName");
        String msgType = map.get("MsgType");
        String content = map.get("Content");
        if (MessageUtils.MESSAGE_TEXT.equals(msgType)) {
            if ("1".equals(content)) {
                System.out.println(fromUserName);
                reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.firstMenu());
            } else if ("2".equals(content)) {
                reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.secondMenu());
            } else {
                reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.menuText());
            }
        } else {
            String eventType = map.get("Event");
            if (MessageUtils.MESSAGE_SUBSCRIBE.equals(eventType)) {
                // 关注时创建菜单
                AccessToken accessToken = TokenUtils.getAccessToken();
                /*System.out.println("票据:" + accessToken.getToken());
                System.out.println("时效:" + accessToken.getExpiresIn());*/

                String menu = JSONObject.fromObject(TokenUtils.InitMenu()).toString();
                System.out.println(menu);
                int result = TokenUtils.CreateMenu(accessToken.getToken(), menu);
                if (result == 0) {
                    System.out.println("创建菜单成功");
                } else {
                    System.out.println("错误码:" + result);
                }
                reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.menuText());
            } 
            
            else if (MessageUtils.MESSAGE_CLICK.equals(eventType)) {
                reqMessage = MessageUtils.initText(toUserName, fromUserName, MessageUtils.menuText());
            } else if (MessageUtils.MESSAGE_VIEW.equals(eventType)) {
                String url = map.get("EventKey");
                reqMessage = MessageUtils.initText(toUserName, fromUserName, url);
            } else if (MessageUtils.MESSAGE_UNSUBSCRIBE.equals(eventType)) {
                reqMessage = MessageUtils.initText(toUserName, fromUserName, "取消关注");
            } else {
                String key = map.get("EventKey");
                reqMessage = MessageUtils.initText(toUserName, fromUserName, key);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return reqMessage;
}
  1. 消息回复:
    微信公众号的消息都是以XML的格式发送的,不管是客户发送给微信公众号还是微信公众号发给客户,都是如此,除文本消息外,其他的都要获取相应的数据进行封装。
1.  MessageUtils:
 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_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 String initText(String toUserName,String fromUserName,String content){
 TextMessage text = new TextMessage();
 text.setFromUserName(toUserName);
 text.setToUserName(fromUserName);
 text.setMsgType(MessageUtils.MESSAGE_TEXT);
 text.setCreateTime(new Date().getTime());
 text.setContent(content);
 return textMessageToxml(text);
 }•  /** 
   
• 主菜单
/
 public static String menuText(){
 StringBuffer sb = new StringBuffer();
 sb.append("欢迎您的关注,请按照菜单提示进行操作:\n\n");
 sb.append("1、家学通介绍\n");
 sb.append("2、学校风采\n\n");
 sb.append("回复任意调出此菜单。");
 return sb.toString();
 }
 /• 关键字1
/
 public static String firstMenu(){
 StringBuffer sb = new StringBuffer();
 sb.append("此公众号主要用于家长能够及时了解学生在校学习情况");
 return sb.toString();
 }
 /• 关键字2
/
 public static String secondMenu(){
 StringBuffer sb = new StringBuffer();
 sb.append("学校在近期的活动,如运动会,演讲,竞赛等");
 return sb.toString();
 }
 /• 消息请求为XML格式,将XML转为map集合
 */
 public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException{
 Map<String, String> map = new HashMap<String ,String>();
 SAXReader Reader = new SAXReader();
 InputStream inputStream = request.getInputStream();
 Document document = Reader.read(inputStream);
 Element root = document.getRootElement();
 List list = root.elements();
 for (Element element : list) {
 map.put(element.getName(), element.getText());
 }
 inputStream.close();
 return map;•  }
 /* 
   • 响应为XML格式,将响应转为XML格式
 */
 public static String textMessageToxml(TextMessage textmessage) {
 XStream stream = new XStream();
 stream.alias("xml", textmessage.getClass());
 return stream.toXML(textmessage);
 }
  1. 菜单创建:
    我这里就不说菜单有什么类型了,对于一个web应用来说,一个view类型的菜单就是一个web项目的入口。在入口中又很多事情可以做,比如网页开发权限的获取,用户信息的获取。还有就是菜单创建的位置,当时我学的时候为这个东西苦恼了很久,最后觉得在用户关注时进行菜单创建比较好,当然这里只是提供我的建议而已,还有很多其他途径。
    代码中替换的都是微信提供的接口
2.  /** 
   
• 发起GET请求
 */
 public static JSONObject doGetStr(String url) {
 HttpClientBuilder builder = HttpClientBuilder.create();
 HttpGet httpGet = new HttpGet(url);
 JSONObject object = null;
try {
 HttpResponse response = builder.build().execute(httpGet);
 HttpEntity entity = response.getEntity();
 if (null != entity) {
 String result = EntityUtils.toString(entity, CHARSET_FORMAT);
 object = JSONObject.fromObject(result);
 }
 } catch (ClientProtocolException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 return object;
 }•  /* 
   
• 发起post请求
 */
 public static JSONObject doPostStr(String url, String outstr) {
 HttpClientBuilder builder = HttpClientBuilder.create();
 HttpPost httpPost = new HttpPost(url);
 JSONObject object = null;
 httpPost.setEntity(new StringEntity(outstr, CHARSET_FORMAT));
 try {
 HttpResponse response = builder.build().execute(httpPost);
 HttpEntity entity = response.getEntity();
 if (null != entity) {
 String result = EntityUtils.toString(entity, CHARSET_FORMAT);
 object = JSONObject.fromObject(result);
 }
 } catch (IOException e) {e.printStackTrace();}
 return object;
 }•  /* 
   
• 获取 access_token
 */
 public static AccessToken getAccessToken() {
 AccessToken token = new AccessToken();
 String url = ACCESS_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
 JSONObject json = doGetStr(url);if (null != json && json.containsKey("access_token")) {
 token.setToken(json.getString("access_token"));
 token.setExpiresIn(json.getInt("expires_in"));
 }
 return token;
 }•  /* 
   
• 初始化菜单
 */
 public static Menu InitMenu() throws UnsupportedEncodingException {
 Menu menu = new Menu();ViewButton button1 = new ViewButton();
 button1.setName("家学通");
 button1.setType("view");
 button1.setUrl(getCodeUrl());ClickButton button2 = new ClickButton();
 button2.setName("更多");
 button2.setType("click");
 button2.setKey("11");ClickButton button31 = new ClickButton();
 button31.setName("扫码事件");
 button31.setType("scancode_push");
 button31.setKey("31");ClickButton button32 = new ClickButton();
 button32.setName("地理位置");
 button32.setType("location_select");
 button32.setKey("32");Button button3 = new Button();
 button3.setName("菜单");
 button3.setSub_button(new Button[] { button31, button32 });menu.setButton(new Button[] { button1, button2, button3 });
 return menu;
 }•  /* 
   
• 创建菜单
 */
 public static int CreateMenu(String token, String menu) {
 int result = 0;
 String url = CREATE_MENU_URL.replace("ACCESS_TOKEN", token);
 JSONObject jsonObject = TokenUtils.doPostStr(url, menu);
 if (jsonObject != null) {
 result = jsonObject.getInt("errcode");
 }
 return result;
 }
  1. 网页开发:
    开始我不得不说一句,微信的网页开发真的很坑,我学的时候真是痛不欲生。
    首先就是微信网页开发的配置,配置的是域名,前面不带http或者HTTPS
    其次就是微信客户信息的获取,首先获取code,由code获取acces_token,再由access_token获取用户信息,这里面有很多坑,第一个:code必须由请求重定向获取,至于为什么请求转发不行,我也不知道,你可以问下马化腾;第二:code的时效性,五分钟内你获取的只能用一次,有木有很坑;第三,这是最坑的一点,明明你只发送了一次请求,但是他却会发送多次,这就造成了我说的第二个问题,重复使用code造成获取不到用户信息,其实第一次是获取到的,但是被后面的覆盖了,最好的办法是只获取一次就存入session,后面想用的时候全部在session中获得,而且,如果你没有设置session时效,除非你退出web应用,否则session一直有效,至于微信发送多次请求的问题,我觉得除了我用映射工具的原因,一定还有其他因素,希望知道的同学可以告诉我。
  2. 最后,有关其他功能比如群发消息,支付接口,等等,虽然我没做过,但是我觉得无非是调用接口,另外,希望大家多看微信的开发文档,虽然微信的开发文档真的很烂。