• 经常有同学过来询问“小白想学习微信开发需要会哪些技术?”,今天我就系统的回答这个问题。想弄清楚这个问题,你必须知道一些微信开发的相关知识。微信公众号主要有:订阅号、服务号、企业号(已经升级到企业微信...

    经常有同学过来询问“小白想学习微信开发,需要会哪些技术?”,今天我就系统的回答这个问题。

    想弄清楚这个问题,你必须知道一些微信开发的相关知识。

    微信公众号主要有:订阅号、服务号、企业号(已经升级到企业微信)、小程序、微信支付。这些公众号,订阅号和服务号从技术角度讲是一样的,你可以只学习服务号就OK。企业号,已经升级到企业微信,我们就直接说企业微信。企业微信是相对很有难度的。企业微信基本上包含了服务号的全部知识点,除此之外,还有企业微信特别的知识。因此,如果你是“小白”,先别妄想一步都学会,还是先学服务号比较现实。

    小程序,这个领域是火锅一段时间的。小程序的想法是指的肯定的,就像是任何一个技术,其实都没有高低贵贱之分,只是看你对这项技术的学习程度。小程序号称是使用JavaScript语言,简单。但是,九宝老师觉得,JavaScript其实很不简单。但是,对于小白级别的学员,知道简单的语法,就OK。

    微信支付,和支付有关的编程就没有简单的。如果你没有几年实际的软件开发基础作保障,不建议写微信支付的程序。当然,项目经理地主婆似的逼上梁山另当别论。

    说了这么多,如果你还是一抖雾水,OK。这样说。

    微信开发需要以下技术储备:
    1.网络编程
    2.xml/json相关知识和技术
    3.JavaScript相关技术。没错,不是只有小程序需要JavaScript基础,微信订阅号,服务号,企业微信开发,都需要你会JavaScript。
    4.基本的网络知识。

    基础的网络知识,这点特重要。微信开发是三方开发。你需要和微信服务器打交道。如果你没有一定的网络知识,连ip、域名都不知道,基本上就不要学习微信了。

    当然,以上所列是最基本的。每个技术都是一门课程。网络编程,要求你熟练实现io读写。xml/json要求你能够实现复杂数据到bean。JavaScript其实是应用程序员的必备基础。基本的网络知识,要求你能够在没有服务器的前提,实现外网的访问。

    先简单说这些,有关技术问题,后续再讲。

    展开全文
  • 同时它也取代了我们传统的一些交流方式,为我们节省了很多的信息费用,因此很多人在开发微信的时候,都将微信当成了是一个商业的平台,有更多的商家开始驻足于微信的开发,那么,微信开发需要掌握哪些系统和技术?...

        微信在近几年的时间内,迅速的成为了我们大家生活中离不开的一种社交软件,同时它也取代了我们传统的一些交流方式,为我们节省了很多的信息费用,因此很多人在开发微信的时候,都将微信当成了是一个商业的平台,有更多的商家开始驻足于微信的开发,那么,微信开发需要掌握哪些系统和技术?

    完成微信开发需要什么技术

        互联网模式的第三方微信商城

        这种模式主要是在第三方平台下完成的,一般情况下商家和企业都能在第三方平台中获得一个账户,里面包含了一个新微信商城所需的页面、数据、程序等资源。但与直接入驻腾讯旗下不同的是,这类的搭建商城方法会对商家和企业的投入要求降低,也是微信商城的制作费用较低的一种。

        微信商城直接入驻腾讯旗下

        腾讯在很久以前就有考虑给餐饮行业做移动服务的念头,所以将以前的“微生活”平台改造成今天的微购物和微商户两个平台,所以在这两个平台上,商家们可以选择直接入驻。但这要考验入驻商家的实力,因为这是跟腾讯的内部产品有着或多或少的挂钩,因此这样的微信商城要有足够的时间和金钱才能成功地搭建。

        独立第三方微信商城系统

        这一类的商城制作已经不再是对微信开发平台的简单利用,相比上述的两种商城制作方法,这一类的商城有更高的自主性和可拓展性,而且这类的微信商城多半都是由一些具有成熟经验的开发公司来提供技术支持,想麦多网上商城系统这类有成熟开发经验的公司现在也有很多成功的案例了。

        自主搭建微信商城

        如果商家对微信商城的制作有足够的经验,这时就可以选用这种方法。具体的开发团队有30人左右的规模,至少要花上3~5个月的时间也应该能完成,这只是一个保守的估计。如果不是专注于这一方面的开发商家,又要在微信上搭建自己的商城,恐怕消耗的人力、时间和金钱等资源会更难估计。

        以上给大家介绍的就是微信开发需要掌握的系统和技术了,在微信取代我们其他的一些社交软件时,我们不仅要懂得微信开发的系统和技术,而且也应该要跟消费者进行随时随地的交流,让消费者能够体会到自己的品牌形象,同时,我们也应该要正确的对待微信这一平台,让微信能够更加的为我们服务。


    展开全文
  • 微信开发的公司现在已是铺天盖地,但是真正能够进行定制开发的去很少,大部分都是在微信自定义菜单上面跳转链接,可以说一点技术含量都没有。笔者建议如果你是一位有开发能力的程序员,还是一字一句的敲代码把功能...

    最近一段时间换了工作,虽说时间不长但也算收获颇丰,不仅学到了新的知识而且眼界也比之前开阔了。这次就分享下这段时间做微信开发的心得和体会。


    一   java微信开发应具备的前提条件

    1  掌握xml解析工具Dom4j、Jdom中的任意一种

    微信所有的消息处理都是xml,因此xml的解析就显得尤为重要,这集中体现在文本消息、图文消息这两个部分

    2 掌握JSON开发工具类如json-lib

    json数据的处理在微信开发集中体现在自定义菜单接口、获取Access_Token、Oauth2.0网页授权等常用接口,此外第三方接口也会使用到如百度翻译、百度词典等。

    3 掌握xstream

    xstream的用途集中体现在java对象转xml字符串这个方面,使用xstream主要是为了最大程度地发挥java面向对象的特点。

    4 熟悉MD5和SHA-1加密算法

    加密算法 主要用于微信验证签名和生成签名(微信支付)两个部分

    5 掌握HTTPConnection和HTTPSConnecion

    这个部分一帮的第二点配合使用以达到最佳效果

    6 掌握常用数据库

    7 能熟练使用linux操作系统


    这7点是2每个java微信开发者都必须具备的,如果这几点都没办法掌握,微信定制开发你就高攀不起。


    二   下面是几个常用工具类

    1 微信消息处理工具类

    package com.debug.weixin.util;
    
    import java.io.InputStream;
    import java.io.Writer;
    import java.util.*;
    
    import javax.servlet.http.HttpServletRequest;
    
    
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    
    import com.debug.weixin.message.resp.Article;
    import com.debug.weixin.message.resp.MusicMessage;
    import com.debug.weixin.message.resp.NewsMessage;
    import com.debug.weixin.message.resp.TextMessage;
    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;
    
    public class MessageUtil {
    	/** 
         * 返回消息类型:文本 
         */  
        public static final String PICTUREHOST = "http://xxxx";  
    	/** 
         * 返回消息类型:文本 
         */  
        public static final String RESP_MESSAGE_TYPE_TEXT = "text";  
      
        /** 
         * 返回消息类型:音乐 
         */  
        public static final String RESP_MESSAGE_TYPE_MUSIC = "music";  
      
        /** 
         * 返回消息类型:图文 
         */  
        public static final String RESP_MESSAGE_TYPE_NEWS = "news";  
      
        /** 
         * 请求消息类型:文本 
         */  
        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_LINK = "link";  
      
        /** 
         * 请求消息类型:地理位置 
         */  
        public static final String REQ_MESSAGE_TYPE_LOCATION = "location";  
      
        /** 
         * 请求消息类型:音频 
         */  
        public static final String REQ_MESSAGE_TYPE_VOICE = "voice";  
      
        /** 
         * 请求消息类型:推送 
         */  
        public static final String REQ_MESSAGE_TYPE_EVENT = "event";  
      
        /** 
         * 事件类型:subscribe(订阅) 
         */  
        public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";  
      
        /** 
         * 事件类型:unsubscribe(取消订阅) 
         */  
        public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";  
      
        /** 
         * 事件类型:CLICK(自定义菜单点击事件) 
         */  
        public static final String EVENT_TYPE_CLICK = "CLICK";  
      
        /** 
         * 解析微信发来的请求(XML) 
         *  
         * @param request 
         * @return 
         * @throws Exception 
         */  
        @SuppressWarnings("unchecked")  
        public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {  
            // 将解析结果存储在HashMap中  
            Map<String, String> map = new HashMap<String, String>();  
      
            // 从request中取得输入流  
            InputStream inputStream = request.getInputStream();  
            // 读取输入流  
            SAXReader reader = new SAXReader();  
            Document document = reader.read(inputStream);  
            // 得到xml根元素  
            Element root = document.getRootElement();  
            // 得到根元素的所有子节点  
            List<Element> elementList = root.elements();  
      
            // 遍历所有子节点  
            for (Element e : elementList)  
                map.put(e.getName(), e.getText());  
      
            // 释放资源  
            inputStream.close();  
            inputStream = null;  
      
            return map;  
        }  
      
        /** 
         * 文本消息对象转换成xml 
         *  
         * @param textMessage 文本消息对象 
         * @return xml 
         */  
        public static String textMessageToXml(TextMessage textMessage) {  
            xstream.alias("xml", textMessage.getClass());  
            return xstream.toXML(textMessage);  
        }  
      
        /** 
         * 音乐消息对象转换成xml 
         *  
         * @param musicMessage 音乐消息对象 
         * @return xml 
         */  
        public static String musicMessageToXml(MusicMessage musicMessage) {  
            xstream.alias("xml", musicMessage.getClass());  
            return xstream.toXML(musicMessage);  
        }  
      
        /** 
         * 图文消息对象转换成xml 
         *  
         * @param newsMessage 图文消息对象 
         * @return xml 
         */  
        public static String newsMessageToXml(NewsMessage newsMessage) {  
            xstream.alias("xml", newsMessage.getClass());  
            xstream.alias("item", new Article().getClass());  
            return xstream.toXML(newsMessage);  
        }  
      
        /** 
         * 扩展xstream,使其支持CDATA块 
         *  
         * @date 2013-05-19 
         */  
        private static XStream xstream = new XStream(new XppDriver() {  
            public HierarchicalStreamWriter createWriter(Writer out) {  
                return new PrettyPrintWriter(out) {  
                    // 对所有xml节点的转换都增加CDATA标记  
                    boolean cdata = true;  
      
                    @SuppressWarnings("unchecked")  
                    public void startNode(String name, Class clazz) {  
                        super.startNode(name, clazz);  
                    }  
      
                    protected void writeText(QuickWriter writer, String text) {  
                        if (cdata) {  
                            writer.write("<![CDATA[");  
                            writer.write(text);  
                            writer.write("]]>");  
                        } else {  
                            writer.write(text);  
                        }  
                    }  
                };  
            }  
        });  
    }
    

    2  签名验证工具类

    package com.debug.weixin.util;
    
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    
    public class SignUtil {
    	
    	// 与接口配置信息中的Token要一致
    	private static String token = "xxxxxx";
    
    	/**
    	 * 验证签名
    	 * 
    	 * @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 = byteToStr(digest);
    		} catch (NoSuchAlgorithmException e) {
    			e.printStackTrace();
    		}
    
    		content = null;
    		// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
    		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    	}
    
    	/**
    	 * 将字节数组转换为十六进制字符串
    	 * 
    	 * @param byteArray
    	 * @return
    	 */
    	private static String byteToStr(byte[] byteArray) {
    		String strDigest = "";
    		for (int i = 0; i < byteArray.length; i++) {
    			strDigest += byteToHexStr(byteArray[i]);
    		}
    		return strDigest;
    	}
    
    	/**
    	 * 将字节转换为十六进制字符串
    	 * 
    	 * @param mByte
    	 * @return
    	 */
    	private static String byteToHexStr(byte mByte) {
    		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    		char[] tempArr = new char[2];
    		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
    		tempArr[1] = Digit[mByte & 0X0F];
    
    		String s = new String(tempArr);
    		return s;
    	}
    }
    


    3 xml解析工具类

    package com.debug.weixin.util;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    
    import org.jdom.Document;
    import org.jdom.Element;
    import org.jdom.JDOMException;
    import org.jdom.input.SAXBuilder;
    
    
    public class XMLUtil {
    	/**
    	 * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
    	 * @param strxml
    	 * @return
    	 * @throws JDOMException
    	 * @throws IOException
    	 */
    	public static Map doXMLParse(String strxml) throws JDOMException, IOException {
    		strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
    
    		if(null == strxml || "".equals(strxml)) {
    			return null;
    		}
    		
    		Map m = new HashMap();
    		
    		InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
    		SAXBuilder builder = new SAXBuilder();
    		Document doc = builder.build(in);
    		Element root = doc.getRootElement();
    		List list = root.getChildren();
    		Iterator it = list.iterator();
    		while(it.hasNext()) {
    			Element e = (Element) it.next();
    			String k = e.getName();
    			String v = "";
    			List children = e.getChildren();
    			if(children.isEmpty()) {
    				v = e.getTextNormalize();
    			} else {
    				v = XMLUtil.getChildrenText(children);
    			}
    			
    			m.put(k, v);
    		}
    		
    		//关闭流
    		in.close();
    		
    		return m;
    	}
    	
    	/**
    	 * 获取子结点的xml
    	 * @param children
    	 * @return String
    	 */
    	public static String getChildrenText(List children) {
    		StringBuffer sb = new StringBuffer();
    		if(!children.isEmpty()) {
    			Iterator it = children.iterator();
    			while(it.hasNext()) {
    				Element e = (Element) it.next();
    				String name = e.getName();
    				String value = e.getTextNormalize();
    				List list = e.getChildren();
    				sb.append("<" + name + ">");
    				if(!list.isEmpty()) {
    					sb.append(XMLUtil.getChildrenText(list));
    				}
    				sb.append(value);
    				sb.append("</" + name + ">");
    			}
    		}
    		
    		return sb.toString();
    	}
    	
    }
    

    4  微信自定义菜单工具类

    package com.debug.weixin.util;
    
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ConnectException;
    import java.net.URL;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    
    import com.debug.weixin.pojo.AccessToken;
    import com.debug.weixin.pojo.Menu;
    
    import net.sf.json.JSONException;
    import net.sf.json.JSONObject;
    
    public class WeixinUtil {
    	
    	
    	
    	public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    	
    	// 菜单创建(POST) 限100(次/天)  
    	public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; 
    	// 菜单删除
    	public static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN"; 
    	  
        /** 
         * 发起https请求并获取结果 
         *  
         * @param requestUrl 请求地址 
         * @param requestMethod 请求方式(GET、POST) 
         * @param outputStr 提交的数据 
         * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) 
         */  
        public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {  
            JSONObject jsonObject = null;  
            StringBuffer buffer = new StringBuffer();  
            try {  
                // 创建SSLContext对象,并使用我们指定的信任管理器初始化  
                TrustManager[] tm = { new MyX509TrustManager() };  
                SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");  
                sslContext.init(null, tm, new java.security.SecureRandom());  
                // 从上述SSLContext对象中得到SSLSocketFactory对象  
                SSLSocketFactory ssf = sslContext.getSocketFactory();  
      
                URL url = new URL(requestUrl);  
                HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();  
                httpUrlConn.setSSLSocketFactory(ssf);  
      
                httpUrlConn.setDoOutput(true);  
                httpUrlConn.setDoInput(true);  
                httpUrlConn.setUseCaches(false);  
                // 设置请求方式(GET/POST)  
                httpUrlConn.setRequestMethod(requestMethod);  
      
                if ("GET".equalsIgnoreCase(requestMethod))  {
                	
                	httpUrlConn.connect();  
                }
      
                // 当有数据需要提交时  
                if (null != outputStr) {  
                    OutputStream outputStream = httpUrlConn.getOutputStream();  
                    // 注意编码格式,防止中文乱码  
                    outputStream.write(outputStr.getBytes("UTF-8"));  
                    outputStream.close();  
                }  
      
                // 将返回的输入流转换成字符串  
                InputStream inputStream = httpUrlConn.getInputStream();  
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
      
                String str = null;  
                while ((str = bufferedReader.readLine()) != null) {  
                    buffer.append(str);  
                }  
                bufferedReader.close();  
                inputStreamReader.close();  
                // 释放资源  
                inputStream.close();  
                inputStream = null;  
                httpUrlConn.disconnect();  
                jsonObject = JSONObject.fromObject(buffer.toString());  
            } catch (ConnectException ce) {  
            	ce.printStackTrace();
               // log.error("Weixin server connection timed out.");  
            } catch (Exception e) {  
                //log.error("https request error:{}", e); 
            	e.printStackTrace();
            }  
            return jsonObject;  
        }  
        
        /** 
         * 获取access_token 
         *  
         * @param appid 凭证 
         * @param appsecret 密钥 
         * @return 
         */  
        public static AccessToken getAccessToken(String appid, String appsecret) {  
            AccessToken accessToken = null;  
          
            String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);  
            JSONObject jsonObject = httpRequest(requestUrl, "GET", null);  
            // 如果请求成功  
            if (null != jsonObject) {  
                try {  
                    accessToken = new AccessToken();  
                    accessToken.setToken(jsonObject.getString("access_token"));  
                    accessToken.setExpiresIn(jsonObject.getInt("expires_in"));  
                } catch (JSONException e) {  
                    accessToken = null;  
                    // 获取token失败  
                    //log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); 
                    System.out.println("获取token失败"+jsonObject.getInt("errcode")+"," +jsonObject.getString("errmsg"));
                }  
            }  
            return accessToken;  
        }
        
        /** 
         * 创建菜单 
         *  
         * @param menu 菜单实例 
         * @param accessToken 有效的access_token 
         * @return 0表示成功,其他值表示失败 
         */  
        public static int createMenu(Menu menu, String accessToken) {  
            int result = 0;  
          
            // 拼装创建菜单的url  
            String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);  
            // 将菜单对象转换成json字符串  
            String jsonMenu = JSONObject.fromObject(menu).toString();  
            // 调用接口创建菜单  
            JSONObject jsonObject = httpRequest(url, "POST", jsonMenu);  
          
            if (null != jsonObject) {  
                if (0 != jsonObject.getInt("errcode")) {  
                    result = jsonObject.getInt("errcode");  
                    //log.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));  
                    System.out.println("创建菜单失败"+jsonObject.getInt("errcode")+","+ jsonObject.getString("errmsg"));
                }  
            }  
          
            return result;  
        }  
        
        public static void removeAllMenu(String accessToken){
        	  // 拼装创建菜单的url  
            String url = menu_delete_url.replace("ACCESS_TOKEN", accessToken);  
            
            // 调用接口创建菜单  
            JSONObject jsonObject = httpRequest(url, "POST", null);  
            if (null != jsonObject) {  
                //if (0 != jsonObject.getInt("errcode")) {  
                	System.out.println(jsonObject.getInt("errcode")+","+ jsonObject.getString("errmsg"));
                //}
            }
            
            
        }
    }
    

    httpRequest这个类比较常用,单独提取到一个类里也是非常OK的。


    5 MD5工具类(主要用于微信支付)

    package com.debug.weixin.util;
    import java.security.MessageDigest;
    public class MD5Util {
    
    	private static String byteArrayToHexString(byte b[]) {
            StringBuffer resultSb = new StringBuffer();
            for (int i = 0; i < b.length; i++)
                resultSb.append(byteToHexString(b[i]));
    
            return resultSb.toString();
        }
    
        private static String byteToHexString(byte b) {
            int n = b;
            if (n < 0)
                n += 256;
            int d1 = n / 16;
            int d2 = n % 16;
            return hexDigits[d1] + hexDigits[d2];
        }
    
        public static String MD5Encode(String origin, String charsetname) {
            String resultString = null;
            try {
                resultString = new String(origin);
                MessageDigest md = MessageDigest.getInstance("MD5");
                if (charsetname == null || "".equals(charsetname))
                    resultString = byteArrayToHexString(md.digest(resultString
                            .getBytes()));
                else
                    resultString = byteArrayToHexString(md.digest(resultString
                            .getBytes(charsetname)));
            } catch (Exception exception) {
            }
            return resultString;
        }
        
        private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
    }
    

    6  微信统一下单工具类(主要用于微信支付)

    package com.debug.weixin.util;
    
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Random;
    import java.util.Set;
    import java.util.SortedMap;
    
    
    
    
    public class PayCommonUtil {
    	
    	public static String CreateNoncestr(int length) {
    		String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    		String res = "";
    		for (int i = 0; i < length; i++) {
    			Random rd = new Random();
    			res += chars.indexOf(rd.nextInt(chars.length() - 1));
    		}
    		return res;
    	}
    
    	public static String CreateNoncestr() {
    		String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    		String res = "";
    		for (int i = 0; i < 16; i++) {
    			Random rd = new Random();
    			res += chars.charAt(rd.nextInt(chars.length() - 1));
    		}
    		return res;
    	}
    	/**
    	 * @author 李欣桦
    	 * @date 2014-12-5下午2:29:34
    	 * @Description:sign签名
    	 * @param characterEncoding 编码格式
    	 * @param parameters 请求参数
    	 * @return
    	 */
    	public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
    		StringBuffer sb = new StringBuffer();  
            Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)  
            Iterator it = es.iterator();  
            while(it.hasNext()) {  
                Map.Entry entry = (Map.Entry)it.next();  
                String k = (String)entry.getKey();  
                Object v = entry.getValue();  
                if(null != v && !"".equals(v)   
                        && !"sign".equals(k) && !"key".equals(k)) {  
                    sb.append(k + "=" + v + "&");  
                }  
            }  
            sb.append("key=" + ConfigUtil.API_KEY);  
            String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();  
            return sign;  
    	}
    	/**
    	 * @author 李欣桦
    	 * @date 2014-12-5下午2:32:05
    	 * @Description:将请求参数转换为xml格式的string
    	 * @param parameters  请求参数
    	 * @return
    	 */
    	public static String getRequestXml(SortedMap<Object,Object> parameters){
    		StringBuffer sb = new StringBuffer();
    		sb.append("<xml>");
    		Set es = parameters.entrySet();
    		Iterator it = es.iterator();
    		while(it.hasNext()) {
    			Map.Entry entry = (Map.Entry)it.next();
    			String k = (String)entry.getKey();
    			String v = (String)entry.getValue();
    			if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
    				sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
    			}else {
    				sb.append("<"+k+">"+v+"</"+k+">");
    			}
    		}
    		sb.append("</xml>");
    		return sb.toString();
    	}
    	/**
    	 * @author 李欣桦
    	 * @date 2014-12-3上午10:17:43
    	 * @Description:返回给微信的参数
    	 * @param return_code 返回编码
    	 * @param return_msg  返回信息
    	 * @return
    	 */
    	public static String setXML(String return_code, String return_msg) {
    		return "<xml><return_code><![CDATA[" + return_code
    				+ "]]></return_code><return_msg><![CDATA[" + return_msg
    				+ "]]></return_msg></xml>";
    	}
    }
    

    这个工具类是参考csdn上一位大神的博客,与其说参考不如说是复制粘贴过来的,没修改注释主要是为了尊重原创作者,在此也真诚感谢这位作者的无私奉献。


    7 证书信任管理器(主要用于HttpsConnection)

    关于这个部分我也专门写过一篇博客的,这里就直接上代码了

    package com.debug.weixin.util;
    
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.X509TrustManager;
    
    /** 
     * 证书信任管理器(用于https请求) 
     * @date 2013-08-08 
     */
    public class MyX509TrustManager implements X509TrustManager{
    
    	
    	public void checkClientTrusted(X509Certificate[] arg0, String arg1)
    			throws CertificateException {
    		
    		
    	}
    
    	
    	public void checkServerTrusted(X509Certificate[] arg0, String arg1)
    			throws CertificateException {
    		
    		
    	}
    
    	
    	public X509Certificate[] getAcceptedIssuers() {
    		
    		return null;
    	}
    
    }
    

    三  微信支付部分容易出错几个细节点

    这个部分主要是对csdn上“情本寂寞”这位大神(就上面的那位李欣桦)的博客做补充说明。

    1  对Oauth2.0网页授权获取openid的补充

    这个地方是支付环节最容易出错也是最困难的部分,我给出了几个方法以供参考


    Oauth2.0网页授权第一步:

    @RequestMapping("/wxIndex")
    public void wxIndex(HttpServletRequest request, HttpServletResponse response)
    			throws Exception {
          String orderNo=request.getParameter("orderNo");
    		String re = URLEncoder.encode(ServerConfig.SERVERDOMAIN+"/waphulai/order/wxOAuth.do?orderNo="+orderNo, "UTF-8");
    		String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect";
    		url = url.replace("APPID", "xxxxxx");
    		url = url.replace("REDIRECT_URI", re);
    
    		response.sendRedirect(url);
    
    }

    Oauth2.0网页授权第二步(直接获取openid):

     @RequestMapping("/wxOAuth")
       public String wxOAuth(HttpServletRequest request,
    			HttpServletResponse response) throws Exception {
    
    		request.setCharacterEncoding("UTF-8");
    		response.setCharacterEncoding("UTF-8");
    		String code = request.getParameter("code");
                    String orderNo=request.getParameter("orderNo");
    		if (!"authdeny".equals("code")) {
    			WeixinOauth2Token w = AdvancedUtil.getOauth2AccessToken("xxxxx", "xxxxxx",code);
    			// 网页授权接口访问凭证
    			String accessToken = w.getAccessToken();
    			// 用户标识
    			String openId = w.getOpenId();
    			// 获取用户信息
    			/*SNSUserInfo snsUser = AdvancedUtil.getSNSUserInfo(accessToken,
    					openId);
    
    			request.setAttribute("snsUserInfo", snsUser);*/
    			
    			Order orderInfo=orderService.getOrderByNo(orderNo);
    			
    			SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
    			parameters.put("appid", ConfigUtil.APPID);
    
    			parameters.put("mch_id", ConfigUtil.MCH_ID);
    			parameters.put("device_info", "1000");  
    			parameters.put("body", "洗车订单");
    			parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());
    			
    			
    			 
    			parameters.put("out_trade_no", orderInfo.getOrder_no());
    			parameters.put("total_fee", String.valueOf(orderInfo.getOrder_money()*100));
    			parameters.put("spbill_create_ip", request.getRemoteAddr());
    			parameters.put("notify_url", ConfigUtil.NOTIFY_URL);
    			parameters.put("trade_type", "JSAPI");
    			parameters.put("openid", openId);
    
    			String sign = PayCommonUtil.createSign("UTF-8", parameters);
    			//System.out.println("我 的签名是:"+sign);  
    			parameters.put("sign", sign);
    
    			String requestXML = PayCommonUtil.getRequestXml(parameters);
    
    			String result = CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL,"POST", requestXML);
    			System.out.println("----------------------------------");
    			System.out.println(result);
    			System.out.println("----------------------------------");
    			System.out.println(String.valueOf(orderInfo.getOrder_money()*100)+"----------------------------------");
    			
    			
    			request.setAttribute("orderNo", orderInfo.getOrder_no());
    			request.setAttribute("totalPrice", orderInfo.getOrder_money());
    			request.setAttribute("unifiedOrder",getH5PayStr(result,request));
    		}
    
    		return "/weixin/wxPay.ftl";
    
    	}

    这里我编写了一个工具类来获取,工具类如下:

    package com.debug.weixin.util;
    
    import net.sf.json.JSONArray;
    import net.sf.json.JSONObject;
    
    import com.debug.weixin.pojo.SNSUserInfo;
    import com.debug.weixin.pojo.WeixinOauth2Token;
    
    public class AdvancedUtil {
    	
    	
       public static WeixinOauth2Token getOauth2AccessToken(String appId,String appSecret,String code){
    	   
    	   WeixinOauth2Token wat=null;
    	   //拼接请求地址
    	   String requestUrl="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
    	   requestUrl=requestUrl.replace("APPID", appId);
    	   requestUrl=requestUrl.replace("SECRET", appSecret);
    	   requestUrl=requestUrl.replace("CODE", code);
    	   
    	   //获取网页授权凭证
    	   JSONObject jsonObject=WeixinUtil.httpRequest(requestUrl, "GET", null);
    	   if(jsonObject!=null){
    		   try{
    			   wat=new WeixinOauth2Token();
    			   wat.setAccessToken(jsonObject.getString("access_token"));
    			   wat.setExpiresIn(jsonObject.getInt("expires_in"));
    			   wat.setRefreshToken(jsonObject.getString("refresh_token"));
    			   wat.setOpenId(jsonObject.getString("openid"));
    			   wat.setScope(jsonObject.getString("scope"));
    		   }catch(Exception e){
    			   wat=null;
    			   int errorCode=jsonObject.getInt("errcode");
    			   String errorMsg=jsonObject.getString("errmsg");
    			   System.out.println("获取网页授权凭证失败:"+errorCode+","+errorMsg);
    		   }
    	   }
    	   return wat;
       }
       
    public static WeixinOauth2Token refreshOauth2AccessToken(String appId,String refreshToken){
    	   
    	   WeixinOauth2Token wat=null;
    	   //拼接请求地址
    	   String requestUrl="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
    	   requestUrl=requestUrl.replace("APPID", appId);
    	   requestUrl=requestUrl.replace("REFRESH_TOKEN", refreshToken);
    	  
    	   
    	   //获取网页授权凭证
    	   JSONObject jsonObject=WeixinUtil.httpRequest(requestUrl, "GET", null);
    	   if(jsonObject!=null){
    		   try{
    			   wat=new WeixinOauth2Token();
    			   wat.setAccessToken(jsonObject.getString("access_token"));
    			   wat.setExpiresIn(jsonObject.getInt("expires_in"));
    			   wat.setRefreshToken(jsonObject.getString("refresh_token"));
    			   wat.setOpenId(jsonObject.getString("openid"));
    			   wat.setScope(jsonObject.getString("scope"));
    		   }catch(Exception e){
    			   wat=null;
    			   int errorCode=jsonObject.getInt("errcode");
    			   String errorMsg=jsonObject.getString("errmsg");
    			   System.out.println("刷新网页授权凭证失败:"+errorCode+","+errorMsg);
    		   }
    	   }
    	   return wat;
       }
    
      public static SNSUserInfo getSNSUserInfo(String accessToken,String openId){
    	  SNSUserInfo snsUserInfo=null;
    	  
    	  String requestUrl="https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
    	  
    	  requestUrl=requestUrl.replace("ACCESS_TOKEN", accessToken);
    	  requestUrl=requestUrl.replace("OPENID", openId);
    	  
    	  //通过网页获取用户信息
    	  JSONObject jsonObject=WeixinUtil.httpRequest(requestUrl, "GET", null);
    	  
    	  if(jsonObject!=null){
    		   try{
    			   snsUserInfo=new SNSUserInfo();
    			   snsUserInfo.setOpenId(jsonObject.getString("openid"));
    			   snsUserInfo.setNickname(jsonObject.getString("nickname"));
    			   //性别(1男  2女 0未知)
    			   snsUserInfo.setSex(jsonObject.getInt("sex"));
    			   snsUserInfo.setCountry(jsonObject.getString("country"));
    			   snsUserInfo.setProvince(jsonObject.getString("province"));
    			   snsUserInfo.setCity(jsonObject.getString("city"));
    			   snsUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
    			   snsUserInfo.setPrivilegeList(JSONArray.toList(jsonObject.getJSONArray("privilege")));
    		   }catch(Exception e){
    			   snsUserInfo=null;
    			   int errorCode=jsonObject.getInt("errcode");
    			   String errorMsg=jsonObject.getString("errmsg");
    			   System.out.println("获取用户信息失败:"+errorCode+","+errorMsg);
    		   }
    	   }
    	   return snsUserInfo;
      }
    }
    

    2 拼接H5微信支付相关参数的方法

    public String getH5PayStr(String result,HttpServletRequest request) throws Exception{
      	
         Map<String, String> map = XMLUtil.doXMLParse(result);
    		
    		
          SortedMap<Object,Object> params = new TreeMap<Object,Object>();
          params.put("appId", ConfigUtil.APPID);
          params.put("timeStamp", Long.toString(new Date().getTime()));
          params.put("nonceStr", PayCommonUtil.CreateNoncestr());
          params.put("package", "prepay_id="+map.get("prepay_id"));
          params.put("signType", ConfigUtil.SIGN_TYPE);
          String paySign =  PayCommonUtil.createSign("UTF-8", params);
          params.put("packageValue", "prepay_id="+map.get("prepay_id"));    //这里用packageValue是预防package是关键字在js获取值出错
          params.put("paySign", paySign);                                                          //paySign的生成规则和Sign的生成规则一致
          params.put("sendUrl", ConfigUtil.SUCCESS_URL);                               //付款成功后跳转的页面
          String userAgent = request.getHeader("user-agent");
          char agent = userAgent.charAt(userAgent.indexOf("MicroMessenger")+15);
          params.put("agent", new String(new char[]{agent}));//微信版本号,用于前面提到的判断用户手机微信的版本是否是5.0以上版本。
          String json = JSONObject.fromObject(params).toString();
          
          return json;
      }


    这个方法也是参考大神的博客改出来的,相比之前大神的博客,只是把流程理得更清晰明了


    3 对支付成功微信通知的请求方法的修改

            @RequestMapping("/paySuccess")
    	public void paySuccess(HttpServletRequest request,HttpServletResponse response) throws Exception {
    	
    	        InputStream inStream = request.getInputStream();
    	        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
    	        byte[] buffer = new byte[1024];
    	        int len = 0;
    	        while ((len = inStream.read(buffer)) != -1) {
    	            outSteam.write(buffer, 0, len);
    	        }
    	        System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
    	        outSteam.close();
    	        inStream.close();
    	        String result  = new String(outSteam.toByteArray(),"utf-8");//获取微信调用我们notify_url的返回信息
    	        Map<Object, Object> map = XMLUtil.doXMLParse(result);
    	        for(Object keyValue : map.keySet()){
    	            System.out.println(keyValue+"="+map.get(keyValue));
    	        }
    	        if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
    	            //TODO 对数据库的操作
    	        	String orderNo=map.get("out_trade_no").toString();
    			boolean ressult=orderService.updateOrderStatus(orderNo, 2);
    			if(ressult){
    			    System.out.println("数据库订单状态修改成功");
    			}else{
    			   System.out.println("数据库订单状态修改失败");
    			}
    	            response.getWriter().write(PayCommonUtil.setXML("SUCCESS", ""));   //告诉微信服务器,我收到信息了,不要在调用回调action了
    	            System.out.println("-------------"+PayCommonUtil.setXML("SUCCESS", ""));
    	        }
    	}

    这个方法是微信支付的终点或者说是最后一步,我对大神对微信支付文档的阅读用四个字概括就是“相当到位”。为了解释这段代码为什么这样写,请参考下面的链接


    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7

    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1


    三  使用微信支付容易被坑到的几个细节点

    1 有关微信支付开发配置部分





    2 Oauth2.0网页版授权请记得配置回调页面域名



    3 支付参数的问题点

    这一步的问题主要在total_fee这个参数上面,这里应该是一个整型的字符串如“3500”、“1000”;但不能是“3500.0”或“3500.00”这种,且单位是分而不是元;其他参数应该不会出问题。


    如果这些步骤都OK那么微信支付就不会有问题了,其他部分请参考如下链接


    微信支付参考博客:http://blog.csdn.net/u011160656/article/details/41759195

    消息处理参考博客:http://blog.csdn.net/lyq8479


    看了这两位作者的博客之后个人觉得微信的常规开发应该是不成问题了。



    展开全文
  • 那么微信小程序开发制件需要掌握哪些语言?  第一、微信小程序wxml  有过编程基础的工程师,在接触到wxml之后你才会发现,其实这个语言的编程理念和html网页的编程技术是类似的,当你研究一点时间后就...

      微信成为一种生活方式的目标已经实现,可以说微信已是移动互联网中的独立生态王国,现在微信要借小程序收割线下的流量!无论是线上流量还是线下场景,微信小程序对于线下零售店都有充分的吸引力。那么微信小程序开发制件需要掌握哪些语言?

      第一、微信小程序wxml

      有过编程基础的工程师,在接触到wxml之后你才会发现,其实这个语言的编程理念和html网页的编程技术是类似的,当你研究一点时间后就知晓,开发一款微信小程序所需要的技术含量并不高,只是对一些标签的更换,比如

      换成了等。即使你对前端不不是非常拿手,转战微信小程序的开发事业,也会是非常好的方向。  

      第二、微信小程序之wxss

      wxss就是微信的css。微信把网页编程里运用的css,换成了自己的开发语言,wxss;,其实主要的实现思想理念也和网页的开发技术基本没什么不同,也是一些标签的简单替换,大部分和原先的css、基本不误,都是通过同页面调用的方式实现的,但是可以说微信小程序比网页开发还要简单一些,更方便一些,比如是在两个文件内,只要index.wxml和index.wxss着两个文件同时都在一个目录内就能想再网页上直接写css一样,简单快捷。

      第三、微信小程序之js

      如果你想开发一款微信小程序学会微信小程序的js是必须要精通的,只要你html+css+js的基础打的好在来全力的学习微信小程序js,之后在前端开发上就没有什么问题了,但是微信js是需要花精力去学习,可以买一本参考书或者了解下微信小程序的api都是可以快速的帮助你介入开发的队列。

      第四、微信小程序之json

      以上几点精通后,需要熟练掌握的就是json,简单来说,json就是微信小程序的一门主次的界面,工程师们可以通过json控制上下菜单栏、主次的页面展示顺序。不过用的频率不高,只是在基本的小程序的框架构架才会应用到,但是这个也需要学习,因为除了展示类型的每个前端的操作都需要和后端对起来,因为如果想做大就要做到简易化,因为在修改代码之后在小程序内搜索到的是上线后的版本,也就是我们提交过后微信审核之后才被展示的,在源代码上修改之后需要提交审核到微信公众平台-小程序管理平台,审核通过后用户才能看到你修改后的,所以为了避免这个麻烦一定要学会后端技术开发,和前端链接起来互通有无。

      其实,小程序类似于H5面,提供了视图层描述语言,需要掌握WXML 和 WXSS 以及基于JavaScript的逻辑层框架,这里wxml相当于html,wxss相当于css。

    展开全文
  • 微信公众号开发技术要点 微信公众号开发技术要点 微信公众号及其接口功能介绍 基本概念 公众号开发者模式 代码验证及图示 Open ID与Union ID 基本概念 使用说明 Access_token 基本介绍 注意事项 获取流程 ...

    微信公众号开发技术要点

    前言

    本文将介绍微信公众号开发中涉及的一些技术概念,以便读者可以快速掌握公众号开发过程中的基本操作和流程。文档不涉及具体开发技术和流程的介绍,该文档作用相当于官方文档中关键点的详细注解。

    在公众号开发流程中,涉及到三种系统:

    1. 微信方面的系统,负责监听用户的操作并将相关消息和事件推送到响应系统,下称微信系统;
    2. 接收微信系统所推送的事件和消息的系统,下称响应系统;
    3. 业务逻辑系统,实现业务逻辑处理,下称业务系统。

    响应系统和业务系统需要自己开发;其中业务系统中的页面即为下文中提到的第三方页面;

    微信公众号及其接口功能介绍

    基本概念

    公众号是为微信用户提供资讯和服务的平台;公众平台开发接口则是通过后台业务系统向用户提供服务的基础;

    不同的公众号类型有不同数量的接口权限,服务号要多于订阅号;

    公众号开发者模式

    开启公众号开发模式后,微信系统将以事件推送的形式告知响应系统用户在公众号里的相关行为,包括:关注/取关事件、二维码扫描事件、自定义菜单事件、跳转链接事件时的事件、点击菜单拉取消息时的事件,从而开发者可以获取到该消息并做出响应;

    1. 开启公众号开发者模式
    2. 填写服务器配置
    3. 验证服务器地址的有效性

    这些步骤都是在微信官方公众号后台里开发->基本配置里完成的

    代码验证及图示

    配置图示:

    这里写图片描述

    服务器代码:

    public class WeChatServer extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req,resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            PrintWriter writer=resp.getWriter();
            System.out.println(req.getParameter("echostr"));
            writer.println(req.getParameter("echostr"));
        }
    }
    //微信系统对响应系统地址的有效性验证是通过返回值(echostr)完成的,这里没有添加对微信系统身份的检查,直接输出
    

    完整代码见WeChatWebSystem

    Open ID与Union ID

    基本概念

    为识别用户,微信为每一个用户提供了针对特定公众号平台的Open ID;同一个用户在不同公众号里的有不同的Open ID;同一个公众号里不同用户有不同的Open ID;获取用户的Open ID是无需用户同意的,但是获取用户的基本信息(所在地址、昵称等)是需要用户同意的;

    为了实现在多个公众平台、移动应用之间做到用户统一管理,提供了Union ID机制。同一用户在同一开放平台账号下的所有公众号和应用里Union ID是相同的;

    使用说明

    1. Open ID的获取和使用

      获取

      1. Open ID会在微信系统向响应系统推送普通消息以及事件推送时附带于消息体内;
      2. 从公众号进入第三方页面时,业务系统需要获得该用户的Open ID;

      使用:

      1. 发送被动消息时(包括对事件的响应和消息的响应),使用Open ID来标记发送对象;
      2. 发送主动消息时(模板消息),使用Open ID来标记发送对象;
    2. Union ID的获取和使用

      从公众号进入第三方页面时,业务系统可以选择获取Union ID来实现业务逻辑,其具体流程为:

      准备阶段:

      1. 获得微信网页授权:微信公众号后台->开发->接口权限;
      2. 设置授权回调域名:一旦设置成功,该域名下的所有页面均可以进行OAuth2.0鉴权;
      3. 以snsapi_userInfo为scope发起授权以获得该用户的基本信息;

      获取阶段:

      1. 引导用户进入授权页面,用户同意后,获得code;

        https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
        
        其中APPID/REDIRECT_URI/SCOPE/STATE为变量,参数顺序不可改变;跳转回调的redirect_uri,应当使用https链接来确保授权code的安全性。以上链接需要自微信客户端里打开,如果用户同意授权,则页面跳转到:redirect_uri/?code=CODE&state=STATE
        
        参数	是否必须	说明
        appid	是	公众号的唯一标识
        redirect_uri	是	授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
        response_type	是	返回类型,请填写code
        scope	是	应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
        state	否	重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
        #wechat_redirect	是	无论直接打开还是做页面302重定向时候,必须带此参数
        
      2. 使用code换取网页授权access_token(用于获取用户基本信息的token,不是微信基础服务里的access_token,在这一步里,获取access_token的同时也会获得用户的open ID);

        获取code后,请求以下链接获取access_token:  https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
        
        其中APPID/SECRET/CODE为变量
        
        正确返回的JSON数据如下:
        { "access_token":"ACCESS_TOKEN",
        "expires_in":7200,
        "refresh_token":"REFRESH_TOKEN",
        "openid":"OPENID",
        "scope":"SCOPE" }
        
      3. 如有必要,开发者刷新该access_token,避免过期;

        获取第二步的refresh_token后,请求以下链接获取access_token:
        https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
        
        access_token拥有较短的有效期,refresh_token则有30天的有效期;
        
        返回结果同上
        { "access_token":"ACCESS_TOKEN",
        "expires_in":7200,
        "refresh_token":"REFRESH_TOKEN",
        "openid":"OPENID",
        "scope":"SCOPE" }
        
      4. 通过access_token和openID获得用户基本信息;

         https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
         
         access_token为网页授权时使用的access_token,不是调用微信接口时使用的access_token;
         返回数据:
         {    "openid":" OPENID",
        " nickname": NICKNAME,
        "sex":"1",
        "province":"PROVINCE"
        "city":"CITY",
        "country":"COUNTRY",
        "headimgurl":    "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
        "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
        "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
        }
        
        

      从响应系统内获得Union ID是通过其他接口实现的:

       请求方式: GET
       请求地址:https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
       参数说明:access_token即为接口访问令牌,openid即为消息的发送者
    
    1. 注意事项

      使用snaapi_base为scope的网页授权对用户是静默的,用户感觉直接进入了公众号网页;

      对于已关注公众号的用户,如果用户从公众号会话或者自定义菜单进入网页授权页,即使scope为snaapi_userInfo,也是静默的;

    Access_token

    基本介绍

    access_token是公众号内全局唯一接口调用凭据;其有效期为2小时,需要定时刷新,重新获取将导致上次access_token失效;

    注意事项

    1. 建议公众号使用统一的中控服务器来获取和刷新access_token;
    2. access_token的有效期通过expire_in标志;在刷新过程中中控服务器可以继续对外输出旧的access_token,刷新完毕5分钟内新旧access_token都有效;
    3. 中控服务器应当提供被动刷新access_token的机制;
    4. 公众号和小程序均可以使用AppID和AppSecret调用本接口来获取access_token;

    获取流程

    https请求方式: GET
    https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
    
    正确返回结果
    {"access_token":"ACCESS_TOKEN","expires_in":7200}
    出粗返回结果
    {"errcode":40013,"errmsg":"invalid appid"}
    

    代码验证及图示

    下图为访问失败时的截图,因为本地开发中使用的IP地址不在公众号后台的IP访问白名单所致,正式部署后将不存在该问题:

    这里写图片描述

    下图为服务器端浏览器接口调试页面

    这里写图片描述

    自定义菜单

    开启服务模式后,将无法在微信公众号后台内实现对菜单的自定义,但是微信公众号后台内可以自定义的菜单点击类型十分有限(2/10),可以通过使用微信提供的菜单管理接口来对菜单进行管理。

    基本介绍

    1. 微信公众号内允许3个一级菜单,每个一级菜单允许5个二级子菜单;一级菜单最多4个汉字,二级菜单最多7个汉字;
    2. 菜单项共有10种类型:
      1. click:当用户点击该类子菜单时,微信系统将向响应系统推送类型为event的消息并附带该菜单项的key值,开发者可以据此对该事件做出响应;
      2. view:当用户点击该类子菜单时,微信客户端将打开开发者在该菜单项中设置的网页URL,在该URL内开发者可以配合网页授权接口获得用户的基本信息以开展业务服务;
      3. scancode_push:当用户点击此类型子菜单时,微信客户端将调起扫一扫工具,并将结果展示给用户,如果识别结果为URL,将进入该URL,同时响应系统将接收到该消息;
      4. scancode_waitmsg:当用户点击该类子菜单时,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
      5. pic_sysphoto:当用户点击该类子菜单时,微信客户端将调起系统相机,完成拍照操作后,将拍摄的图片发送给响应系统,同时收起系统相机,等待开发者下发消息;
      6. pic_photo_or_album:基本功能同上,但是会给用户两种选择:拍照或者系统相册上传;
      7. pic_weixin:基本功能同上,但是会使用微信相册;
      8. location_select:弹出地理位置选择器,完成操作后,将选择地理信息发送给响应系统,收起地理位置选择器后,等待开发者下发消息;
      9. media_id:当用户点击该类子菜单项后,微信系统将对应永久素材id的素材下发给用户;
      10. view_limited:当用户点击该类型子菜单时,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL;

    创建菜单

    接口说明:

     接口地址:https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
     访问方式:POST
     参数说明:
     ACCESS_TOKEN即为获取的access_token;
     POST的数据为JSON字符串,其中button定义了菜单,为一个JSON数组;数组中每一个元素都是一个一级菜单,其中sub_button属性为该一级菜单的二级菜单,同样也是JSON数组;每一个菜单项包含type(上面提到的10种)、name、key等信息。
     {
         "button":[
         {    
              "type":"click",
              "name":"今日歌曲",
              "key":"V1001_TODAY_MUSIC"
          },
          {
               "name":"菜单",
               "sub_button":[
               {    
                   "type":"view",
                   "name":"搜索",
                   "url":"http://www.soso.com/"
                },
                {//跳转小程序
                     "type":"miniprogram",
                     "name":"wxa",
                     "url":"http://mp.weixin.qq.com",
                     "appid":"wx286b93c14bbf93aa",
                     "pagepath":"pages/lunar/index"
                 },
                {
                   "type":"click",
                   "name":"赞一下我们",
                   "key":"V1001_GOOD"
                }]
           }]
     }
     正确返回消息:
     {"errcode":0,"errmsg":"ok"}
     出错时返回消息:
     {"errcode":40018,"errmsg":"invalid button name size"}
     
    

    这里需要注意的是,POST的内容类型(content-type)需要设置为application/json;

    查询菜单

    创建自定义菜单后,可使用该接口查询自定义菜单的结构。如果使用了个性化菜单,那么该接口将返回默认菜单和全部个性化菜单的信息;

    请求方式:GET
    https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
    
    返回数据(无个性化菜单时):
    {
        "menu": {
            "button": [
                {
                    "type": "click", 
                    "name": "今日歌曲", 
                    "key": "V1001_TODAY_MUSIC", 
                    "sub_button": [ ]
                }, 
                {
                    "type": "click", 
                    "name": "歌手简介", 
                    "key": "V1001_TODAY_SINGER", 
                    "sub_button": [ ]
                }, 
                {
                    "name": "菜单", 
                    "sub_button": [
                        {
                            "type": "view", 
                            "name": "搜索", 
                            "url": "http://www.soso.com/", 
                            "sub_button": [ ]
                        }, 
                        {
                            "type": "view", 
                            "name": "视频", 
                            "url": "http://v.qq.com/", 
                            "sub_button": [ ]
                        }, 
                        {
                            "type": "click", 
                            "name": "赞一下我们", 
                            "key": "V1001_GOOD", 
                            "sub_button": [ ]
                        }
                    ]
                }
            ]
        }
    }
    
    返回结果(有个性化菜单时):
    {
        "menu": {
            "button": [
                {
                    "type": "click", 
                    "name": "今日歌曲", 
                    "key": "V1001_TODAY_MUSIC", 
                    "sub_button": [ ]
                }
            ], 
            "menuid": 208396938
        }, 
        "conditionalmenu": [
            {
                "button": [
                    {
                        "type": "click", 
                        "name": "今日歌曲", 
                        "key": "V1001_TODAY_MUSIC", 
                        "sub_button": [ ]
                    }, 
                    {
                        "name": "菜单", 
                        "sub_button": [
                            {
                                "type": "view", 
                                "name": "搜索", 
                                "url": "http://www.soso.com/", 
                                "sub_button": [ ]
                            }, 
                            {
                                "type": "view", 
                                "name": "视频", 
                                "url": "http://v.qq.com/", 
                                "sub_button": [ ]
                            }, 
                            {
                                "type": "click", 
                                "name": "赞一下我们", 
                                "key": "V1001_GOOD", 
                                "sub_button": [ ]
                            }
                        ]
                    }
                ], 
                "matchrule": {
                    "group_id": 2, 
                    "sex": 1, 
                    "country": "中国", 
                    "province": "广东", 
                    "city": "广州", 
                    "client_platform_type": 2
                }, 
                "menuid": 208396993
            }
        ]
    }
    

    删除菜单

    接口说明:

    http请求方式:GET
    https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
    正确返回消息:
    {"errcode":0,"errmsg":"ok"}
    错误返回消息同上
    

    这里需要注意的是,该方式将删除整个菜单。

    个性化菜单

    为了帮助公众号实现灵活的业务运营,微信公众平台新增了个性化菜单接口,开发者可以通过该接口,让公众号的不同用户群体看到不一样的自定义菜单。该接口开放给已认证订阅号和已认证服务号。

    开发者可以使用如下方式标志用户:

    1. 用户标签(开发者的业务需求可以借助用户标签来完成)
    2. 性别
    3. 手机操作系统地区(用户在微信客户端设置的地区)
    4. 语言(用户在微信客户端设置的语言)

    使用个性化菜单需要有以下几点注意:

    1. 个性化菜单要求用户的微信客户端版本在iPhone6.2.2,Android 6.2.4以上,暂时不支持其他版本微信;
    2. 菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果;
    3. 普通公众号的个性化菜单的新增接口每日限制次数为2000次,删除接口也是2000次,测试个性化菜单匹配结果接口为20000次;
    4. 出于安全考虑,一个公众号的所有个性化菜单,最多只能设置为跳转到3个域名下的链接;
    5. 创建个性化菜单之前必须先创建默认菜单(默认菜单是指使用普通自定义菜单创建接口创建的菜单)。如果删除默认菜单,个性化菜单也会全部删除;
    6. 个性化菜单接口支持用户标签,请开发者注意,当用户身上的标签超过1个时,以最后打上的标签为匹配;

    创建个性化菜单:

    请求方式:POST(请使用https协议)
    https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token=ACCESS_TOKEN
    POST数据为JSON对象;
    {
         "button":[
         {    
            "type":"click",
            "name":"今日歌曲",
             "key":"V1001_TODAY_MUSIC" },
        {     "name":"菜单",
            "sub_button":[
            {            
                "type":"view",
                "name":"搜索",
                "url":"http://www.soso.com/"},
                {
                             "type":"miniprogram",
                             "name":"wxa",
                             "url":"http://mp.weixin.qq.com",
                             "appid":"wx286b93c14bbf93aa",
                             "pagepath":"pages/lunar/index"
                },
                 {
            "type":"click",
            "name":"赞一下我们",
            "key":"V1001_GOOD"
               }]
     }],
    "matchrule":{
      "tag_id":"2",
      "sex":"1",
      "country":"中国",
      "province":"广东",
      "city":"广州",
      "client_platform_type":"2",
      "language":"zh_CN"
      }
    }
    正确返回消息:
    {"menuid":"208379533"}——menuid即为该菜单的标记;可用于以后删除使用;
    

    删除个性化菜单:

    请求方式:POST(请使用https协议)
    https://api.weixin.qq.com/cgi-bin/menu/delconditional?access_token=ACCESS_TOKEN
    参数说明:
    POST数据为JSON字符串
    {"menuid":"208379533"}
    正确返回:
    {"errcode":0,"errmsg":"ok"}
    错误返回:
    通用
    

    自定义菜单事件推送

    注意,第3个到第8个的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。

    click类型的消息推送:
    <xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[FromUser]]></FromUserName>
    <CreateTime>123456789</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[CLICK]]></Event>
    <EventKey><![CDATA[EVENTKEY]]></EventKey>
    </xml>
    
    view类型的消息推送:
    <xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[FromUser]]></FromUserName>
    <CreateTime>123456789</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[VIEW]]></Event>
    <EventKey><![CDATA[www.qq.com]]></EventKey>
    <MenuId>MENUID</MenuId>//指菜单ID,如果是个性化菜单,则可以通过这个字段,知道是哪个规则的菜单被点击了。
    </xml>
    
    scancode_push类型的消息推送:
    <xml><ToUserName><![CDATA[gh_e136c6e50636]]></ToUserName>
    <FromUserName><![CDATA[oMgHVjngRipVsoxg6TuX3vz6glDg]]></FromUserName>
    <CreateTime>1408090502</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[scancode_push]]></Event>
    <EventKey><![CDATA[6]]></EventKey>
    <ScanCodeInfo><ScanType><![CDATA[qrcode]]></ScanType>
    <ScanResult><![CDATA[1]]></ScanResult>
    </ScanCodeInfo>
    </xml>
    
    scancode_waitmsg类型的消息推送:
    <xml><ToUserName><![CDATA[gh_e136c6e50636]]></ToUserName>
    <FromUserName><![CDATA[oMgHVjngRipVsoxg6TuX3vz6glDg]]></FromUserName>
    <CreateTime>1408090606</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[scancode_waitmsg]]></Event>
    <EventKey><![CDATA[6]]></EventKey>
    <ScanCodeInfo><ScanType><![CDATA[qrcode]]></ScanType>
    <ScanResult><![CDATA[2]]></ScanResult>
    </ScanCodeInfo>
    </xml>
    
    pic_sysphoto类型的消息推送:
    <xml><ToUserName><![CDATA[gh_e136c6e50636]]></ToUserName>
    <FromUserName><![CDATA[oMgHVjngRipVsoxg6TuX3vz6glDg]]></FromUserName>
    <CreateTime>1408090651</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[pic_sysphoto]]></Event>
    <EventKey><![CDATA[6]]></EventKey>
    <SendPicsInfo><Count>1</Count>
    <PicList><item><PicMd5Sum><![CDATA[1b5f7c23b5bf75682a53e7b6d163e185]]></PicMd5Sum>
    </item>
    </PicList>
    </SendPicsInfo>
    </xml>
    
    pic_photo_or_album类型的消息推送:
    <xml><ToUserName><![CDATA[gh_e136c6e50636]]></ToUserName>
    <FromUserName><![CDATA[oMgHVjngRipVsoxg6TuX3vz6glDg]]></FromUserName>
    <CreateTime>1408090816</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[pic_photo_or_album]]></Event>
    <EventKey><![CDATA[6]]></EventKey>
    <SendPicsInfo><Count>1</Count>
    <PicList><item><PicMd5Sum><![CDATA[5a75aaca956d97be686719218f275c6b]]></PicMd5Sum>
    </item>
    </PicList>
    </SendPicsInfo>
    </xml>
    
    pic_weixin类型的消息推送:
    <xml><ToUserName><![CDATA[gh_e136c6e50636]]></ToUserName>
    <FromUserName><![CDATA[oMgHVjngRipVsoxg6TuX3vz6glDg]]></FromUserName>
    <CreateTime>1408090816</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[pic_weixin]]></Event>
    <EventKey><![CDATA[6]]></EventKey>
    <SendPicsInfo><Count>1</Count>
    <PicList><item><PicMd5Sum><![CDATA[5a75aaca956d97be686719218f275c6b]]></PicMd5Sum>
    </item>
    </PicList>
    </SendPicsInfo>
    </xml>
    
    location_select类型的消息推送:
    <xml><ToUserName><![CDATA[gh_e136c6e50636]]></ToUserName>
    <FromUserName><![CDATA[oMgHVjngRipVsoxg6TuX3vz6glDg]]></FromUserName>
    <CreateTime>1408091189</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[location_select]]></Event>
    <EventKey><![CDATA[6]]></EventKey>
    <SendLocationInfo><Location_X><![CDATA[23]]></Location_X>
    <Location_Y><![CDATA[113]]></Location_Y>
    <Scale><![CDATA[15]]></Scale>
    <Label><![CDATA[ 广州市海珠区客村艺苑路 106号]]></Label>
    <Poiname><![CDATA[]]></Poiname>
    </SendLocationInfo>
    </xml>
    
    
    

    用户管理

    标签管理

    开发者可以使用用户标签管理的相关接口,实现对公众号标签的管理;标签可以用于对用户的分类管理;

    每个公众号可以创建100个标签;创建方式如下:

    请求方式:POST(使用https协议);
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/create?access_token=ACCESS_TOKEN
    POST数据格式:
    {   "tag":{ "name" : "广东"//标签名长度不能超过30字节}}
    返回结果:
    {   "tag":{ "id":134,id "name":"广东"   } }//包含tag-id
    

    获取公众号已创建的标签:

    请求方式:GET(使用https协议) 
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/get?access_token=ACCESS_TOKEN
    返回说明:
    {   "tags":[{       "id":1,       "name":"每天一罐可乐星人",       "count":0 //此标签下粉丝数 },{   "id":2,   "name":"星标组",   "count":0 },{   "id":127,   "name":"广东",   "count":5 }   ] }
    

    编辑标签:

    请求方式:POST(请使用https协议)
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/update?access_token=ACCESS_TOKEN
    POST数据:
    {   "tag" : {     "id" : 134,     "name" : "广东人"   } }
    返回说明:
    正确返回:{   "errcode":0,   "errmsg":"ok" }
    错误返回:详见错误返回码
    

    删除标签:

    请注意,当某个标签下的粉丝超过10w时,后台不可直接删除标签。此时,开发者可以对该标签下的openid列表,先进行取消标签的操作,直到粉丝数不超过10w后,才可直接删除该标签。

    请求方式:POST(使用https协议) 
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=ACCESS_TOKEN
    POST数据:
    {   "tag":{        "id" : 134   } }
    返回说明:
    正确返回:{   "errcode":0,   "errmsg":"ok" }
    错误返回:详见错误返回码
    

    获取标签下的粉丝列表:

    请求方式:POST(请使用https协议) 
    请求地址:https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=ACCESS_TOKEN
    POST数据格式:JSON
    {   "tagid" : 134,   "next_openid":""//第一个拉取的OPENID,不填默认从头开始拉取 }
    返回说明:
    { 
    	"count":2,//这次获取的粉丝数量   
    	"data":{//粉丝列表
    		"openid":[  
    			"ocYxcuAEy30bX0NXmGn4ypqx3tI0",    
    			"ocYxcuBt0mRugKZ7tGAHPnUaOW7Y"
    		]  
    	},  
    	"next_openid":"ocYxcuBt0mRugKZ7tGAHPnUaOW7Y"//拉取列表最后一个用户的openid 
    }
    

    用户标签管理

    标签功能支持为公众号用户进行打标签、取消标签等操作;该功能可以实现个性化菜单定制

    批量为用户打标签

    请求方式:POST(请使用https协议)
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=ACCESS_TOKEN
    POST数据:
    {   
    	"openid_list" : [//粉丝列表    
    		"ocYxcuAEy30bX0NXmGn4ypqx3tI0",    
    		"ocYxcuBt0mRugKZ7tGAHPnUaOW7Y"],   
    	"tagid" : 134 
    }
    返回说明:
    {   
    	"errcode":0,   
    	"errmsg":"ok"
    }
    

    批量为用户取消标签

    请求方式:POST(请使用https协议) 
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=ACCESS_TOKEN
    POST数据格式:JSON
    {
    	"openid_list" : [//粉丝列表     
    		"ocYxcuAEy30bX0NXmGn4ypqx3tI0",     
    		"ocYxcuBt0mRugKZ7tGAHPnUaOW7Y"],   
    	"tagid" : 134
    }
    返回说明:{"errcode":0, "errmsg":"ok"}
    

    获取用户的标签列表

    请求方式:POST(请使用https协议)
    请求地址:https://api.weixin.qq.com/cgi-bin/tags/getidlist?access_token=ACCESS_TOKEN
    POST数据格式:JSON
    {   "openid" : "ocYxcuBt0mRugKZ7tGAHPnUaOW7Y" }
    返回说明:{   "tagid_list":[//被置上的标签列表 134, 2   ] }
    

    设置用户备注名

    请求方式: POST(请使用https协议)
    请求地址:https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN
    POST数据格式:JSON
    POST数据例子:
    {
        "openid":"oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
        "remark":"pangzi"//标签名
    }
    返回说明:{"errcode":0,"errmsg":"ok"}
    

    获取用户基本信息

    注意,这里获的用户基本信息是在关注者和公众号产生消息交互后,公众号可获得该用户的Open ID,之后在响应系统中,通过Open ID获的Union ID以及基本信息

    请求方式: GET
    请求地址:https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
    返回说明:正常情况下,微信会返回下述JSON数据包给公众号
    {
        "subscribe": 1, 
        "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", 
        "nickname": "Band", 
        "sex": 1, 
        "language": "zh_CN", 
        "city": "广州", 
        "province": "广东", 
        "country": "中国", 
        "headimgurl":"http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
        "subscribe_time": 1382694957,
        "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
        "remark": "",
        "groupid": 0,
        "tagid_list":[128,2],
        "subscribe_scene": "ADD_SCENE_QR_CODE",
        "qr_scene": 98765,
        "qr_scene_str": ""
    }
    

    批量获得用户基本信息

    请求方式: POST
    请求地址:https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN
    POST:JSON
    {
        "user_list": [
            {
                "openid": "otvxTs4dckWG7imySrJd6jSi0CWE", 
                "lang": "zh_CN"
            }, 
            {
                "openid": "otvxTs_JZ6SEiP0imdhpi50fuSZg", 
                "lang": "zh_CN"
            }
        ]
    }
    返回说明:
    {
       "user_info_list": [
           {
               "subscribe": 1, 
               "openid": "otvxTs4dckWG7imySrJd6jSi0CWE", 
               "nickname": "iWithery", 
               "sex": 1, 
               "language": "zh_CN", 
               "city": "揭阳", 
               "province": "广东", 
               "country": "中国", 
    
               "headimgurl": "http://thirdwx.qlogo.cn/mmopen/xbIQx1GRqdvyqkMMhEaGOX802l1CyqMJNgUzKP8MeAeHFicRDSnZH7FY4XB7p8XHXIf6uJA2SCunTPicGKezDC4saKISzRj3nz/0",
    
              "subscribe_time": 1434093047, 
               "unionid": "oR5GjjgEhCMJFyzaVZdrxZ2zRRF4", 
               "remark": "", 
    
               "groupid": 0,
               "tagid_list":[128,2],
               "subscribe_scene": "ADD_SCENE_QR_CODE",
               "qr_scene": 98765,
               "qr_scene_str": ""
    
          }, 
           {
               "subscribe": 0, 
               "openid": "otvxTs_JZ6SEiP0imdhpi50fuSZg"
           }
       ]
    }
    

    获取用户列表

    公众号可通过本接口来获取帐号的关注者列表,关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的)组成。一次拉取调用最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。

    请求方式: GET(请使用https协议)
    请求地址:https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
    返回说明:
    {
    	"total":2,
    	"count":2,
    	"data":{
    		"openid":["OPENID1","OPENID2"]
    	},
    	"next_openid":"NEXT_OPENID"
    }
    

    当公众号关注者数量超过10000时,可通过填写next_openid的值,从而多次拉取列表的方式来满足需求。

    具体而言,就是在调用接口时,将上一次调用得到的返回中的next_openid值,作为下一次调用中的next_openid值。

    消息管理

    接收普通消息

    当普通用户向公众号发送消息时,微信系统将POST消息的XML数据包到开发者填写的URL上;

    注意事项:

    1. 微信服务器在5s内收不到响应并不会断掉连接,并且重新发起请求,总共重试3次;如果服务器无法保证在5s内做出响应,应当回复空串。
    2. 如果开发者需要在5s内对用户发送的消息做出回应,即使用发送消息->被回复消息接口向用户被动回复消息,那么需要选择对消息的加密方式,详情见 消息加密;

    各种消息体结构(XML格式组织,可使用输入流的方式读取):

    注意,首次出现的XML标签将给出注释,第二次出现则不再注释,该规则同样适用于后面的接收事件消息

    1. 文本消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>	//开发者微信号
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>		//用户的openid
      	<CreateTime>1348831860</CreateTime>
      	<MsgType>< ![CDATA[text] ]></MsgType>	//文本为text
      	<Content>< ![CDATA[this is a test] ]></Content>
      	<MsgId>1234567890123456</MsgId>		//64位整型
      </xml>
      
    2. 图片消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>1348831860</CreateTime>
      	<MsgType>< ![CDATA[image] ]></MsgType>	//图片为image
      	<PicUrl>< ![CDATA[this is a url] ]></PicUrl>
      	<MediaId>< ![CDATA[media_id] ]></MediaId>	//可使用图片消息媒体id,可以调用多媒体文件下载接口拉取数据。
      	<MsgId>1234567890123456</MsgId>
      </xml>
      
    3. 语音消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>1357290913</CreateTime>
      	<MsgType>< ![CDATA[voice] ]></MsgType>	//语音为voice
      	<MediaId>< ![CDATA[media_id] ]></MediaId>
      	<Format>< ![CDATA[Format] ]></Format>	//语音格式,如amr,speex等
      	<MsgId>1234567890123456</MsgId>
      	<Recognition>< ![CDATA[腾讯微信团队] ]></Recognition>
      </xml>
      

      这里需要注意的是,如果开启了语音识别,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recongnition字段标志识别结果;

    4. 视频消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>1357290913</CreateTime>
      	<MsgType>< ![CDATA[video] ]></MsgType>	//视频为video
      	<MediaId>< ![CDATA[media_id] ]></MediaId>	//可使用图片消息媒体id,可以调用多媒体文件下载接口拉取数据。
      	<ThumbMediaId>< ![CDATA[thumb_media_id] ]></ThumbMediaId>	//视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
      	<MsgId>1234567890123456</MsgId></xml>
      
    5. 小视频消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>1357290913</CreateTime>
      	<MsgType>< ![CDATA[shortvideo] ]></MsgType>		//小视频为shortvideo
      	<MediaId>< ![CDATA[media_id] ]></MediaId>
      	<ThumbMediaId>< ![CDATA[thumb_media_id] ]></ThumbMediaId>
      	<MsgId>1234567890123456</MsgId>
      </xml>
      
    6. 地理位置消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>1351776360</CreateTime>
      	<MsgType>< ![CDATA[location] ]></MsgType>	//地理位置为	location
      	<Location_X>23.134521</Location_X>	// 地理纬度
      	<Location_Y>113.358803</Location_Y>	// 地理经度
      	<Scale>20</Scale>	//地图缩放大小
      	<Label>< ![CDATA[位置信息] ]></Label>	//地理位置信息
      	<MsgId>1234567890123456</MsgId>
      </xml>
      
      
    7. 链接消息

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>1351776360</CreateTime>
      	<MsgType>< ![CDATA[link] ]></MsgType>	//链接消息为 link
      	<Title>< ![CDATA[公众平台官网链接] ]></Title>	//标题
      	<Description>< ![CDATA[公众平台官网链接] ]></Description>	//描述
      	<Url>< ![CDATA[url] ]></Url>	//消息链接
      	<MsgId>1234567890123456</MsgId>
      </xml>
      
      

    接收事件消息

    在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信系统通过事件推送的形式通知响应系统,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许,详细内容如下:

    1. 关注/取消关注事件
    2. 扫描带参数二维码事件
    3. 上报地理位置事件
    4. 自定义菜单事件
    5. 点击菜单拉取消息的事件
    6. 点击菜单跳转链接时的事件

    各种事件介绍:

    1. 关注/取消关注事件

      用户在关注与取消关注公众号时,微信系统会把这个事件推送到响应系统,方便开发者给用户下发欢迎消息或者做帐号的解绑。为保护用户数据隐私,开发者收到用户取消关注事件时需要删除该用户的所有信息。

      推送XML数据包示例:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>	//开发者微信号
      	<FromUserName>< ![CDATA[FromUser] ]></FromUserName>		//用户的OennID
      	<CreateTime>123456789</CreateTime>	//创建时间,int整型
      	<MsgType>< ![CDATA[event] ]></MsgType>
      	<Event>	//消息类型为event< ![CDATA[subscribe] ]></Event>	//事件类型,subscribe(订阅)、unsubscribe(取消订阅)
      </xml>
      
      
    2. 扫描带参数二维码事件:

      用户扫描带场景值二维码时,可能推送以下两种事件:

      1. 如果用户还未关注公众号,则用户可以关注公众号,关注后微信系统会将带场景值关注事件推送给响应系统。
      2. 如果用户已经关注公众号,则微信系统会将带场景值的扫描事件推送响应系统。

      推送XML数据包示例:

      用户未关注时,进行关注后的事件推送
      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[FromUser] ]></FromUserName>
      	<CreateTime>123456789</CreateTime>
      	<MsgType>< ![CDATA[event] ]></MsgType>		//消息类型为event
      	<Event>< ![CDATA[subscribe] ]></Event>	//事件类型,subscribe
      	<EventKey>< ![CDATA[qrscene_123123] ]></EventKey>	//事件KEY值,qrscene_为前缀,后面为二维码的参数值
      	<Ticket>< ![CDATA[TICKET] ]></Ticket>	//二维码的ticket,可用来换取二维码图片
      </xml>
      
      用户已关注时的事件推送
      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName> 
      	<FromUserName>< ![CDATA[FromUser] ]></FromUserName> 
      	<CreateTime>123456789</CreateTime> 
      	<MsgType>< ![CDATA[event] ]></MsgType>
          <Event>< ![CDATA[SCAN] ]></Event>
          <EventKey>< ![CDATA[SCENE_VALUE] ]></EventKey>	//创建二维码是的scene_id
          <Ticket>< ![CDATA[TICKET] ]></Ticket>	//二维码的ticket,可用来换取图片
      </xml>
      
      
    3. 上报地理位置事件

      用户同意上报地理位置后,每次进入公众号会话时,都会进入上报地理位置,或在进入会话后每5秒报一次地理位置。上报地理位置时,微信系统将上报地理位置事件推送到响应系统;

      推送XML数据包示例:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>123456789</CreateTime>
      	<MsgType>< ![CDATA[event] ]></MsgType>
      	<Event>< ![CDATA[LOCATION] ]></Event>
      	<Latitude>23.137466</Latitude>	//维度
      	<Longitude>113.352425</Longitude>	//经度
      	<Precision>119.385040</Precision>	//地理位置精度
      </xml>
      
      
    4. 自定义菜单事件

      用户点击自定义菜单后,微信系统会把点击事件推送给响应系统,点击菜单弹出子菜单,不会产生上报。

      推送XML数据包示例:

      点击菜单拉取消息时的事件推送
      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[FromUser] ]></FromUserName>
      	<CreateTime>123456789</CreateTime>
      	<MsgType>< ![CDATA[event] ]></MsgType>
      	<Event>< ![CDATA[CLICK] ]></Event>	//事件类型为CLICK
      	<EventKey>< ![CDATA[EVENTKEY] ]></EventKey>	//事件KEY值,与自定义菜单接口中KEY值对应
      </xml>
      
      点击菜单跳转链接时的事件推送
      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[FromUser] ]></FromUserName>
      	<CreateTime>123456789</CreateTime>
      	<MsgType>< ![CDATA[event] ]></MsgType>	
      	<Event>< ![CDATA[VIEW] ]></Event>	//事件类型为 VIEW
      	<EventKey>< ![CDATA[www.qq.com] ]></EventKey>	//事件的KEY值,设置的跳转URL
      </xml>
      
      

    被动回复用户消息

    当用户发送消息给公众号时,或某些特定的用户操作引发的事件推送时,微信系统会将一个POST请求发送到响应系统,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应,这种方式称为被动回复用户消息。

    现支持回复文本、图片、图文、语音、视频、音乐。

    发送被动响应消息其实并不是一种接口,而是对微信系统发过来消息的一次回复,只是这次回复将发送到用户。

    被动回复消息的数据格式:

    注意:

    1. 回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

    2. 一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:

      1. 开发者在5秒内未回复任何内容;
      2. 开发者回复了异常数据,比如JSON数据等;
    3. 回复文本消息

      XML格式数据:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>	//用户Open id
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>	//开发者 微信号;这两者都可以从响应系统收到的消息里获得
          <CreateTime>12345678</CreateTime>
          <MsgType>< ![CDATA[text] ]></MsgType>	//消息类型为:text
          <Content>< ![CDATA[你好] ]></Content>
      </xml>
      
      
    4. 回复图片消息

      XML格式数据:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>12345678</CreateTime>
      	<MsgType>< ![CDATA[image] ]></MsgType>	//消息类型为:image
      	<Image>
      		<MediaId>< ![CDATA[media_id] ]></MediaId>//通过素材管理中的接口上传多媒体文件,得到的id。
      	</Image>
      </xml>
      
      
    5. 回复语音消息

      XML格式数据:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>12345678</CreateTime>
      	<MsgType>< ![CDATA[voice] ]></MsgType>	//消息类型为:voice
      	<Voice>
      		<MediaId>< ![CDATA[media_id] ]></MediaId>
      	</Voice>
      </xml>
      
      
    6. 回复视频消息

      XML格式数据:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>12345678</CreateTime>
      	<MsgType>< ![CDATA[video] ]></MsgType>	//消息类型为:video
      	<Video>
      		<MediaId>< ![CDATA[media_id] ]></MediaId>
      		<Title>< ![CDATA[title] ]></Title>	//视频的标题
      		<Description>< ![CDATA[description] ]></Description>	//视频的描述
      	</Video>
      </xml>
      
      
    7. 回复音乐消息

      XML格式数据:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>12345678</CreateTime>
      	<MsgType>< ![CDATA[music] ]></MsgType>
      	<Music>
      		<Title>< ![CDATA[TITLE] ]></Title>
      		<Description>< ![CDATA[DESCRIPTION] ]></Description>
      		<MusicUrl>< ![CDATA[MUSIC_Url] ]></MusicUrl>
      		<HQMusicUrl>< ![CDATA[HQ_MUSIC_Url] ]></HQMusicUrl>
      		<ThumbMediaId>< ![CDATA[media_id] ]></ThumbMediaId>
      	</Music>
      </xml>
      
      
    8. 回复图文消息

      XML格式数据:

      <xml>
      	<ToUserName>< ![CDATA[toUser] ]></ToUserName>
      	<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
      	<CreateTime>12345678</CreateTime>
      	<MsgType>< ![CDATA[news] ]></MsgType>
      	<ArticleCount>2</ArticleCount>
      	<Articles>
      		<item>
      			<Title>< ![CDATA[title1] ]></Title>
      			<Description>< ![CDATA[description1] ]></Description>
      			<PicUrl>< ![CDATA[picurl] ]></PicUrl>
      			<Url>< ![CDATA[url] ]></Url>
      		</item>
      		<item>
      			<Title>< ![CDATA[title] ]></Title>
      			<Description>< ![CDATA[description] ]></Description>
      			<PicUrl>< ![CDATA[picurl] ]></PicUrl>
      			<Url>< ![CDATA[url] ]></Url>
      		</item>
      	</Articles>
      </xml>
      
      

    主动发送消息

    主动发送消息是通过模板消息实现的,模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。

    为了发送模板消息,需要首先设置公众号所属的行业,之后公众号则只能使用该行业内的模板来发送模板消息;设置行业可以通过微信公众平台后台来完成,每月可修改一次;也可以通过接口调用的方式来修改所属行业;

    定义模板消息时需要注意的地方:

    1. 模板消息调用时主要需要模板ID和模板中各参数的赋值内容;
    2. 模板中参数内容必须以".DATA"结尾,否则视为保留字;
    3. 模板保留符号"{{ }}";
    4. 详情见:获得已添加到微信公众号里的所有模板列表;

    设置所属行业:

    请求方式: POST
    请求地址:https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN
    POST数据格式:
    {"industry_id1":"1","industry_id2":"4"}
     
    
    

    获得模板ID:

    该过程可在公众号后台完成,同时提供接口:

    请求方式: POST
    请求地址:https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=ACCESS_TOKEN
    POST数据格式:
    {"template_id_short":"TM00015"}
    正确返回消息说明:JSON
    {"errcode":0,"errmsg":"ok","template_id":"Doclyl5uP7Aciu-qZ7mJNPtWkbkYnWBWVja26EGbNyk"}
    错误返回消息说明:
    错误码通用。
    
    

    获取已添加到微信公众号里的所有模板列表:

    请求方式:GET
    请求地址:https//api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=ACCESS_TOKEN
    返回结果说明:
    {    
     "template_list": [{
          "template_id": "iPk5sOIt5X_flOVKn5GrTFpncEYTojx6ddbt8WYoV5s",
          "title": "领取奖金提醒",
          "primary_industry": "IT科技",
          "deputy_industry": "互联网|电子商务",
          "content": "{ {result.DATA} }\n\n领奖金额:{ {withdrawMoney.DATA} }\n领奖  时间:{ {withdrawTime.DATA} }\n银行信息:{ {cardInfo.DATA} }\n到账时间:  { {arrivedTime.DATA} }\n{ {remark.DATA} }",
          "example": "您已提交领奖申请\n\n领奖金额:xxxx元\n领奖时间:2013-10-10 12:22:22\n银行信息:xx银行(尾号xxxx)\n到账时间:预计xxxxxxx\n\n预计将于xxxx到达您的银行卡"
       }]
    }
    
    

    删除模板:

    该过程可在公众号后台完成,同时提供接口:

    请求方式:POST
    请求地址:https://api.weixin.qq.com/cgi-bin/template/del_private_template?access_token=ACCESS_TOKEN
    POST数据格式:
    {"template_id" : "Dyvp3-Ff0cnail_CDSzk1fIc6-9lOkxsQE7exTJbwUE"}
    正确返回消息说明:JSON
    错误返回消息说明:
    {"errcode" : 0,"errmsg" : "ok"}
    错误码通用。
    
    

    发送模板消息:

    请求方式: POST
    请求地址:https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
    POST数据格式:
    {
    	"touser":"OPENID",
    	"template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
    	"url":"http://weixin.qq.com/download",  
         "miniprogram":{
         	"appid":"xiaochengxuappid12345",
         	"pagepath":"index?foo=bar"
         },
         "data":{
         	"first": {
         		"value":"恭喜你购买成功!",
         		"color":"#173177"
         	},
         	"keyword1":{
         		"value":"巧克力",
         		"color":"#173177"
         	},
         	"keyword2": {
            	"value":"39.8元",
            	"color":"#173177"
            },
            "keyword3": {
            	"value":"2014年9月22日",
            	"color":"#173177"
            },
            "remark":{
            	"value":"欢迎再次购买!",
            	"color":"#173177"
            }
        }
    }
    正确返回消息格式:
    {"errcode":0,"errmsg":"ok","msgid":200228332}
    
    

    事件推送:

    在模版消息发送任务完成后,微信系统会将是否送达成功作为通知,发送到响应系统。

    送达成功时,推送的XML如下:
    <xml>
    	<ToUserName>< ![CDATA[gh_7f083739789a] ]></ToUserName>
    	<FromUserName>< ![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8] ]></FromUserName>
    	<CreateTime>1395658920</CreateTime>
    	<MsgType>< ![CDATA[event] ]></MsgType>
    	<Event>< ![CDATA[TEMPLATESENDJOBFINISH] ]></Event>
    	<MsgID>200163836</MsgID>
    	<Status>< ![CDATA[success] ]></Status>
    </xml>
    
    没有送达时,推送的XML如下:
    <xml>
    	<ToUserName>< ![CDATA[gh_7f083739789a] ]></ToUserName>
    	<FromUserName>< ![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8] ]></FromUserName>
    	<CreateTime>1395658984</CreateTime>
    	<MsgType>< ![CDATA[event] ]></MsgType>
    	<Event>< ![CDATA[TEMPLATESENDJOBFINISH] ]></Event>
    	<MsgID>200163840</MsgID>
    	<Status>< ![CDATA[failed:user block] ]></Status>	//failed: system failed——其他原因;failed:user block——用户拒绝接收该公众号的消息
    </xml>
    
    

    消息加密

    公众号消息加解密是为了进一步加强公众号安全保障,提供的新机制。公众账号主动调用API的情况不受影响。只有被动回复用户的消息时,才需要进行消息加解密。消息加解密方式:

    1. 明文模式:维持现有模式,没有适配加解密新特性,消息体明文收发,默认设置为明文模式;
    2. 兼容模式:公众平台发送消息内容将同时包括明文和密文,消息包长度增加到原来的3倍左右;公众号回复明文或密文均可,不影响现有消息收发;开发者可在此模式下进行调试;
    3. 安全模式(推荐):公众平台发送消息体的内容只含有密文,公众账号回复的消息体也为密文,建议开发者在调试成功后使用此模式收发消息

    启用加解密功能(即选择兼容模式或安全模式)后,微信系统在向响应系统推送消息时,URL将新增加两个参数(加密类型和消息体签名),并以此来体现新功能。加密算法采用AES。具体方案见该模块代码实现。

    获取微信系统服务器地址

    出于安全等考虑,如需要获知微信服务器的IP地址列表,可以通过该接口获得微信服务器IP地址列表或者IP网段信息。

    请求方式: GET
    请求地址:https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN
    返回消息:JSON格式
    {"ip_list":["127.0.0.1","127.0.0.2","101.226.103.0/25"]}
    
    

    客服消息及管理

    当用户和公众号产生特定动作的交互时(具体动作列表请见下方说明),微信系统将会把消息数据推送给响应系统,响应系统可以在一段时间内(目前修改为48小时)调用客服接口,通过POST一个JSON数据包来发送消息给普通用户。此接口主要用于客服等有人工消息处理环节的功能,方便开发者为用户提供更加优质的服务。

    目前允许的动作列表如下(公众平台会根据运营情况更新该列表,不同动作触发后,允许的客服接口下发消息条数不同,下发条数达到上限后,会遇到错误返回码,具体请见返回码说明页):

    1. 用户发送信息
    2. 点击自定义菜单(仅有点击推事件、扫码推事件、扫码推事件且弹出“消息接收中”提示框这3种菜单类型将会触发客服接口)
    3. 关注公众号
    4. 扫描二维码
    5. 支付成功
    6. 用户维权

    客服账号管理:

    请注意,必须先在公众平台官网为公众号设置微信号后才能使用该能力。

    1. 添加客服账号

      请求方式: POST
      请求地址:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN
      POST数据示例如下:
      {
           "kf_account" : "test1@test",
           "nickname" : "客服1",
           "password" : "pswmd5",
      }
      返回说明(正确时的JSON返回结果):
      {"errcode" : 0,"errmsg" : "ok"}
      返回错误码是通用的
      
      
    2. 删除客服账号

      请求方式: GET
      请求地址:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN
      POST数据示例如下:
      {
           "kf_account" : "test1@test",
           "nickname" : "客服1",
           "password" : "pswmd5",
      }
      返回说明(正确时的JSON返回结果):
      {"errcode" : 0,"errmsg" : "ok"}
      返回错误码是通用的
      
      
    3. 修改客服账号

      请求方式: POST
      请求地址:https://api.weixin.qq.com/customservice/kfaccount/update?access_token=ACCESS_TOKEN
      POST数据示例如下:
      {
           "kf_account" : "test1@test",
           "nickname" : "客服1",
           "password" : "pswmd5",
      }
      返回说明(正确时的JSON返回结果):
      {"errcode" : 0,"errmsg" : "ok"}
      返回错误码是通用的
      
      

    设置客服账号头像

    开发者可调用该接口来上传图片作为客服人员的头像,头像图片文件必须是jpg格式,推荐使用640*640大小的图片以达到最佳效果。该接口调用请求如下:

    请求方式: POST/FORM
    请求地址:http://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
    这里需要注意的是,官方文档中提到的是使用curl工具POST一个多媒体文件;这里我想只要通过代码将文件提交到上述地址就OK,因为curl是一个命令行工具,网上有在php中使用curl的,但是对Java的支持很惨淡,这一部分详见代码验证及图示。
    
    

    获取所有客服账号

    请求方式: GET
    请求地址:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN
    返回数据:
    {
        "kf_list": [
            {
                "kf_account": "test1@test", 
                "kf_nick": "ntest1", 
                "kf_id": "1001"	//这里需要注意的是,kf_id是第一次在这里出现,创建的时候并没有返回该字段;
                "kf_headimgurl": " http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjfUS8Ym0GSaLic0FD3vN0V8PILcibEGb2fPfEOmw/0"
            }, 
            {
                "kf_account": "test2@test", 
                "kf_nick": "ntest2", 
                "kf_id": "1002"
                "kf_headimgurl": " http://mmbiz.qpic.cn/mmbiz/4whpV1VZl2iccsvYbHvnphkyGtnvjfUS8Ym0GSaLic0FD3vN0V8PILcibEGb2fPfEOmw /0"
            }
        ]
    }
    
    

    客服接口-发送消息

    请求方式: POST
    请求地址:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
    各消息类型所需的JSON数据包如下:
    文本消息:
    {
        "touser":"OPENID",
        "msgtype":"text",
        "text":
        {
             "content":"Hello World"
        }
    }
    发送文本消息时,文本内容中可以携带跳转小程序的文字链:<a href="http://www.qq.com" data-miniprogram-appid="appid" data-miniprogram-path="pages/index/index">点击跳小程序</a>
    参数说明:
    1.data-miniprogram-appid 项,填写小程序appid,则表示该链接跳小程序;
    2.data-miniprogram-path项,填写小程序路径,路径与app.json中保持一致,可带参数;
    3.对于不支持data-miniprogram-appid 项的客户端版本,如果有herf项,则仍然保持跳href中的网页链接;
    
    注意,data-miniprogram-appid对应的小程序必须与公众号有绑定关系。
    
    发送图片消息:
    {
        "touser":"OPENID",
        "msgtype":"image",
        "image":
        {
          "media_id":"MEDIA_ID"
        }
    }
    
    发送语音消息:
    {
        "touser":"OPENID",
        "msgtype":"voice",
        "voice":
        {
          "media_id":"MEDIA_ID"
        }
    }
    
    发送视频消息:
    {
        "touser":"OPENID",
        "msgtype":"video",
        "video":
        {
          "media_id":"MEDIA_ID",
          "thumb_media_id":"MEDIA_ID",
          "title":"TITLE",
          "description":"DESCRIPTION"
        }
    }
    
    发送音乐消息:
    {
        "touser":"OPENID",
        "msgtype":"music",
        "music":
        {
          "title":"MUSIC_TITLE",
          "description":"MUSIC_DESCRIPTION",
          "musicurl":"MUSIC_URL",
          "hqmusicurl":"HQ_MUSIC_URL",
          "thumb_media_id":"THUMB_MEDIA_ID" 
        }
    }
    
    发送图文消息(点击跳转到外链) 图文消息条数限制在8条以内,注意,如果图文数超过8,则将会无响应。
    {
        "touser":"OPENID",
        "msgtype":"news",
        "news":{
            "articles": [
             {
                 "title":"Happy Day",
                 "description":"Is Really A Happy Day",
                 "url":"URL",
                 "picurl":"PIC_URL"
             },
             {
                 "title":"Happy Day",
                 "description":"Is Really A Happy Day",
                 "url":"URL",
                 "picurl":"PIC_URL"
             }
             ]
        }
    }
    
    发送图文消息:
    {
        "touser":"OPENID",
        "msgtype":"mpnews",
        "mpnews":
        {
             "media_id":"MEDIA_ID"
        }
    }
    
    发送卡券:
    {
      "touser":"OPENID", 
      "msgtype":"wxcard",
      "wxcard":{              
           "card_id":"123dsdajkasd231jhksad"        
       },
    }
    发送小程序卡片:
    {
        "touser":"OPENID",
        "msgtype":"miniprogrampage",
        "miniprogrampage":
        {
            "title":"title",
            "appid":"appid",
            "pagepath":"pagepath",
            "thumb_media_id":"thumb_media_id"
        }
    }
    
    

    微信网页开发

    网页授权获取用户基本信息

    用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

    这里需要注意的是同用户管理中的获取用户基本信息的区别:用户管理是在响应系统的完成的;而用户授权是在业务系统中完成的;两者工作的环境也是不同的,所以请求接口是不同的:第三方授权是在公众号内通过微信客户端进入网页时发生,而用户管理是公众号内部的管理行为;

    使用流程:

    1. 在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。
    2. 授权回调域名配置规范为全域名。配置后,该域名下的所有网页都可以进行OAuth2.0鉴权。

    两种请求模式:

    1. 以snsapi_base为scope发起的网页授权,用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务系统的入口);

    2. 以snsapi_userinfo为scope发起的网页授权,用来获取用户的基本信息的。这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

      注意:用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。该接口以及包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。

    在OAuth2.0用户授权机制下,存在一个反应用户是否同意授权的标记,该标记在OAuth2.0的环境下称为:access_token。这和响应系统用于请求微信接口时使用的access_token是不一样的,但是它们的性质是相同的;

    特殊环境下的静默授权:

    1. 上面已经提到,对于以snsapi_base为scope的网页授权,就静默授权的,用户无感知;
    2. 对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。

    具体而言,网页授权流程分为四步:

    1. 引导用户进入授权页面同意授权,获取code

       请求地址:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
       参数说明:APPID/REDIRECT_URI/SCOPE/StATE等为变量,其余为常量;参数顺序不可变;
      
      

      如果用户同意授权,页面将跳转至 REDIRECT_URI/?code=CODE&state=STATE。 code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。

    2. 通过code换取网页授权access_token(与基础支持中的access_token不同)

      请求地址:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
      参数说明:APPID/SECRET/CODE等为变量,其余为常量;参数顺序可变;
      返回数据:JSON
      { 
      	"access_token":"ACCESS_TOKEN",//	网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
      	"expires_in":7200,
      	"refresh_token":"REFRESH_TOKEN",//用户刷新access_token
      	"openid":"OPENID",
      	"scope":"SCOPE"
      }
      
      

      注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。即当前请求是可以通过客户端进行的。

      另外,此时已经获得open id,即如果不需要使用union id 的话,网页授权到此结束;

    3. 如果需要,开发者可以刷新网页授权access_token,避免过期

      请求地址:https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
      参数说明:APPID/REFRESH_TOKEN为变量,REFRESH_TOKEN即为第二步获得数据
      
      
    4. 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

      请求方式:GET
      请求地址:https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
      参数说明:ACCESS_TOKEN即为第二步获得网页授权数据;OPENID一样;
      返回数据说明:JSON
      {
      	"openid":" OPENID",
      	" nickname": NICKNAME,
      	"sex":"1",
      	"province":"PROVINCE"
      	"city":"CITY",
      	"country":"COUNTRY",
      	"headimgurl": "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
      	"privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
      	"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
      }
      
      

      整体来看,App Secret作为秘密数据,其获取也应该通过业务系统的验证以检查正在请求App Secret的网页是不是友军;

    微信JS-SDK的功能

    JS-SDK为第三方网页在微信客户端内使用本地资源提供JS封装的接口;这里不涉及具体API的使用,仅介绍JS-SDK的引入、调用接口权限的验证配置。

    引入JS文件:

    同普通JS文件。

    权限验证:

    所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用,同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复。

    wx.config({
        debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '', // 必填,公众号的唯一标识
        timestamp: , // 必填,生成签名的时间戳
        nonceStr: '', // 必填,生成签名的随机串
        signature: '',// 必填,签名,签名算法见后专门模块
        jsApiList: [] // 必填,需要使用的JS接口列表
    });
    
    wx.ready(function(){
        // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
    });
    
    wx.error(function(res){
        // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
    });
    
    

    所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数:

    1. success:执行成功时的回调函数;
    2. fail:调用接口失败时执行的回调函数;
    3. complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。
    4. cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
    5. trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。

    以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下:

    1. 调用成功时:“xxx:ok” ,其中xxx为调用的接口名
    2. 用户取消时:“xxx:cancel”,其中xxx为调用的接口名
    3. 调用失败时:其值为具体错误信息

    签名算法

    生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。 开发者必须在自己的服务全局缓存jsapi_ticket 。

    生成签名的流程:

    1. 获得access_token:具体见上文目录;

    2. 用第一步拿到的access_token获得jsapi_ticket

      请求方法:GET
      请求地址:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
      返回数据说明:JSON
      {
      	"errcode":0,
      	"errmsg":"ok",
      	"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
      	"expires_in":7200
      }
      
      
    3. 签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。 sha1加密后的结果即为signature;

    展开全文
  • 2、开启公众号开发模式,需要了解微信公众平台的原理。 3、微信号是在联网的环境下才能够实现各种功能的。必备条件准备,第一个是外网服务器,让我们的项目部署在上面,第二个是微信公众平台账号.可以多了解微信...
  • 在学习微信公众平台开发前,您得了解一个流程,为了更直观,商侣科技整理一些网友提供的流程图。 一、从流程图中很直观可以得出,首先您得懂一门服务器语言,这个语言没有任何限制,可以是asp.net,php,JSP...
  • 其次,通过本课程的学习,学员能够掌握微信公众平台开发的方法、技术和应用实现。例如,开发者文档怎么看,开发环境怎么搭建,基本的消息交互如何实现,常用的方法技巧有哪些,真实应用怎么开发
  • 微信开发之难点解析

    2015-12-13 21:57:03
    微信开发就是调用接口。这个难点就是调用接口时需要验证,必须按照微信要求的验证规则验证成功后才能调用接口。那么以我开发的微信小项目总结一下微信开发的难点。 1.微信开发属于web开发,要选合适的语言 尽管web...
  • 公司需要开发微信服务号,要求做有关技术验证,学习了微信公众号的开发,在这里记录总结下。 正文 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;学习微信公众号的开发,必须先阅读微信开发文档,...
  • 凭借大量活跃用户,微信已成为商家重要营销平台之一。商家为庞大用户群提供定制化服务的迫切需求,吸引了大量开发者/开发商投入到...在CSDN站内,拥有大量与微信开发相关的资源,包括技术博客、问题讨论、工具资源
  • 过年前后做了个微信公众号项目,已经过去一段时间了,抽空回忆总结下基本流程吧,不然很快估计自己就忘了。。 微信公众平台官网:https://mp.weixin.qq.com 文章目录一、注册公众号二、了解公众号管理页面三、必备...
  • 微信小程序作为现在比较火的编程开发应用场景之一,深受市场的青睐,这让不少开发者眼馋不已,对于有比较成熟的开发经验的工程师来说,微信小程序开发制作的知识对他们来说不是难事,多看几下就能明白。但是对于初学...
  • 接下来的时间里,我会以连载的形式分享微信公众号开发的相关文章,其中也会说一下我遇到的问题以及解决的思路,以及在开发过程中的一些感悟!因为是一个连载系列,所以会以一个初学者,也就是接触过编程但是没有接触...
  • 本课程是一个系列入门教程,目标是从 0 开始带领读者上手实战,课程以微信小程序的核心概念作为主线,介绍配置文件、页面样式文件、JavaScript 的基本知识并以指南针为例对基本知识进行扩展,另外加上开发工具的安装...
  • 一、微信小游戏——H5小游戏及微信小程序微信小游戏、H5小游戏以及微信小程序,他们到底是什么关系呢? 就像这个图一样,微信小游戏,目前其实就是微信小程序里能运行的H5小游戏,既属于微信小程序,也是H5小游戏。 ...
  • 微信开发视频教程-深入浅出微信公众平台实战开发(微网站、LBS云、Api接口调用、服务号高级接口) 一、微信开发实例视频教程总目录: ...二、为什么需要微信开发实例视频教程这么套课程? 2.1、用户
  • 用业余时间学习微信小程序一个月以来,我一头雾水好久。但终于逐渐搞清楚都需要学习那些东西,分享给大家。希望对大家有帮助。大体有8个知识模块:-- 1、要学习WXML(腾讯自己搞的语言--类似于 Html5)WXML(WeiXin ...
  • 技术路线自然还是走java的web应用罗 语言:java6 二次开发接口方式:RMI(webservice、MOM以后再说) 数据存储:Redis当内存库用 持久化:MongoDB,留点分析大数据的架子 功能特点:支持多个微信服务号并发运行...
  • 腾讯微信技术架构

    2016-02-25 15:24:32
    微信——腾讯战略级产品,创造移动互联网增速记录,10个月5000万手机用户,433天之内完成用户数从零到一亿的增长过程,千万级用户同时在线,摇一摇每天次数过亿... 在技术架构上,微信是如何做到的?日前,在腾讯大...
1 2 3 4 5 ... 20
收藏数 156,421
精华内容 62,568