开发前首先我们要知道一些概念
各公众号区别:
1、订阅号:为媒体和个人提供一种信息传播方式,主要偏于为用户传达资讯(类似报纸杂志),主要的定位是阅读,每天可以群发1条消息;
2、服务号:为企业,政府或组织提供对用户进行服务,主要偏于服务交互(类似银行提供服务查询),每个月只可群发4条消息;
3、企业号:为企业,政府,事业单位,实现生产管理和协作运营的移动化,主要用于公司内部通讯使用,旨在为用户提供移动办公,需要先有成员的通讯信息验证才可以关注成功企业号;
![img_191e68910b6bd3180e4d2a122c54af5c.png](https://i-blog.csdnimg.cn/blog_migrate/92f62d16a0ae903425716aa096442644.png)
温馨提示:
1、如果想简单的发送消息,达到宣传效果,建议选择订阅号;
2、如果想进行商品销售,进行商品售卖,为用户提供服务,建议申请服务号;
3、如果想用来管理内部企业员工、团队,对内使用,可选择申请企业号。
编辑模式:主要针对非编程人员及信息发布类公众帐号使用。
开启该模式后,可以方便地通过界面配置“自定义菜单”(认证的订阅号、服务号)和“自动回复的消息”。
好处是可视化界面配置,操作简单,快捷,但是功能有限。
由于编辑模式功能有限我们就不做多讲我们把重点方法开发模式上
首先我们自己要先申请一个公众号对立面的各个功能要熟悉然后再看开发模式
![img_15e06f3c0b8767ad1a72e0b60fa03c18.png](https://i-blog.csdnimg.cn/blog_migrate/a68e6b729048cef68ffc83f506bb5979.png)
开发模式:主要针对具备开发能力的人使用。开启该模式后,能够使用微信公众平台开放的接口,
通过编程方式实现自定义菜单的创建、删除、用户消息的交互,
这种模式更加灵活,能实现更多复杂的功能,提供个性化服务。
微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,
开发者在公众平台网站中创建公众号、获取接口权限后,可以通过阅读本接口文档来帮助开发。
文档地址:http://mp.weixin.qq.com/wiki/home/
要站在自己网站的角度看文档。
注册公众号
![img_11b3dd14e369941ca882e8b8f74b8169.png](https://i-blog.csdnimg.cn/blog_migrate/b42b8e6d0ede055101189a75e21e2dbe.png)
我们这里只对订阅号做探讨其实和服务号开发是一样的
开发模式
微信公众平台开发模式交互原理
![img_2b3b6d132b7552dc58490bf045f3df66.jpe](https://i-blog.csdnimg.cn/blog_migrate/b7cbbb8d4c8d5fd839d68a118c59235b.jpeg)
这里我们需要服务器,服务器有阿里云,bae,花生壳,nat123,natapp由于价格原因阿里云,bae价格都比较比较昂贵我们现在做测试就选择natapp内网穿透https://natapp.cn/
先下载客户端
![img_99381298ac248c86d142aa648d328b4e.png](https://i-blog.csdnimg.cn/blog_migrate/8b559ff07ebcc163a8cb16fc195f4282.png)
在官网上购买隧道5块钱的要以.top为后缀的
![img_8c23fd667e395ad789c1211881e5b6a6.png](https://i-blog.csdnimg.cn/blog_migrate/e1abdc2cc686aaf51aca38e9f5c6ba60.png)
再购买一个二级域名5块钱
![img_365a685f20b16140c7e0391f7a1aa25d.png](https://i-blog.csdnimg.cn/blog_migrate/0dceda67f80e7bb3a81aad2c7bbdb5b2.png)
安装natapp
![img_72eae03b52436c55eb15a1fd255bb4b8.png](https://i-blog.csdnimg.cn/blog_migrate/5d896ee8383409e2c9261de900fafffc.png)
![img_2ce806baeca0f2f673089f2a39bea8cb.png](https://i-blog.csdnimg.cn/blog_migrate/2d34e7590db5ee051e3b21f8ae4e26fa.png)
根据教程安装好natapp
安装成功的标识 把地址拷贝下来http://zhangshuai.nat100.top
![img_1cb23c275636e0e43ef9a2a66112ab80.png](https://i-blog.csdnimg.cn/blog_migrate/a16c46e59f1a7d16b013e49e09489063.png)
这个时候就可以通过域名在任何地方访问我们的应用了
微信公众号URL接入
进入公众平台测试帐号
![img_b06fa82bd231d22b126f9440241717fe.png](https://i-blog.csdnimg.cn/blog_migrate/7864d3d24c887a5a359817751e873333.png)
![img_c3717d44449942aecdd69b515a40843f.png](https://i-blog.csdnimg.cn/blog_migrate/acb96774a73a99a6f20c5278271b4b79.png)
可以查看文档接入指南( https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319)也可以跟我一起来(我们这里Tomcat服务器的端口是8080有些人的端口是80根据自己的情况)
![img_bb62b344b38391f5ed16ded2a23d6fee.png](https://i-blog.csdnimg.cn/blog_migrate/d72ae230fe059e3e70c88672c3ce2cbd.png)
![img_f381f63bbe9a18f76ec31794c296b051.png](https://i-blog.csdnimg.cn/blog_migrate/66d894731919678d3c92b0344e56bfd6.png)
首先我们先创建一个Maven项目
![img_643a8e73c3ebe531fef60e7ba8292ef9.png](https://i-blog.csdnimg.cn/blog_migrate/feb07d1e8019685b23107f4af9c18545.png)
先配置好项目
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!--配置字符编码过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
</web-app>
application-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 0.开启注解扫描 -->
<context:component-scan base-package="com.jd.wx"/>
<!-- 1.注解驱动支持 -->
<mvc:annotation-driven/>
<!-- 2.配置静态资源处理 -->
<mvc:default-servlet-handler/>
<!-- 3.视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
现在我们先创建一个Controller测试一下
![img_6e234f8b322373f01b3b3d8163e74483.png](https://i-blog.csdnimg.cn/blog_migrate/b2e6cfe19ecce1568f45fba646752fe0.png)
@Controller
public class WeixinController {
//验证签名
/**
* @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return
*/
@RequestMapping("/weixin")
public String checkSignature(String signature,String timestamp,String nonce,String echostr){
System.out.println(signature);
System.out.println(timestamp);
System.out.println(nonce);
System.out.println(echostr);
return null;
}
}
打印结果
![img_0f227a88fff4800d25bddb33a2278949.png](https://i-blog.csdnimg.cn/blog_migrate/2d24cad2802d9dac18a6fa2d85830a8b.png)
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
//加密/校验流程如下:
String[] arr = {WeixinUtil.TOKEN,timestamp,nonce};
//1.将token、timestamp、nonce三个参数进行字典序排序
Arrays.sort(arr);
String str = "";
//2.将三个参数字符串拼接成一个字符串进行sha1加密
for (String temp : arr) {
str += temp;
}
//3.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if (signature.equals(SecurityUtil.SHA1(str))) {
System.out.println("接入成功!");
return echostr;
}
System.out.println("接入失败!");
return null;
此时我们需要工具类
![img_0d9cf289fc1c2f4af3ef56081d13ece7.png](https://i-blog.csdnimg.cn/blog_migrate/61cd0599497d8998db10e18d4ec431fa.png)
和SecurityUtil
package com.jd.wx.util;
import java.security.MessageDigest;
public class SecurityUtil {
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* encode string
* @param algorithm
* @param str
* @return String
*/
public static String encode(String algorithm, String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
messageDigest.update(str.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Takes the raw bytes from the digest and formats them correct.
* @param bytes
* the raw bytes from the digest.
* @return the formatted bytes.
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文转换成十六进制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >>> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
public static String MD5(String content){
return SecurityUtil.encode("MD5", content);
}
public static String SHA1(String content){
return SecurityUtil.encode("SHA1", content);
}
public static void main(String[] args) {
System.out.println("111111 MD5 :"
+ SecurityUtil.encode("MD5", "111111"));
System.out.println("111111 SHA1 :"
+ SecurityUtil.encode("SHA1", "111111"));
}
}
配置成功
![img_1c3161c8e0b141847e63647874439e92.png](https://i-blog.csdnimg.cn/blog_migrate/986cd3bf0e507b34228a7566708bab9d.png)
现在有一个问题是当用户给公众号发送一个消息,消息会推送到我们给的地址上面 我们怎么知道什么时候是验证什么时候是消息推送怎么区分呢?
其实很简单 开发者通过检验signature对请求进行校验若确认此次GET请求来自微信服务器,如果是消息接收他会发送一个POST请求
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
@RequestMapping(value="/weixin",method= RequestMethod.GET)
@RequestMapping(value="/weixin",method= RequestMethod.POST)
此时他会发送一个xml数据过来我们需要jaxb(自己百度一下)工具来解析xml
![img_dc9421dbd3e8fd6f79fa2e18f157bc56.png](https://i-blog.csdnimg.cn/blog_migrate/b088aa3756d00217867c4774f2c1a18d.png)
![img_6313fc9c09c2af84c0a77a39a7219e66.png](https://i-blog.csdnimg.cn/blog_migrate/9b446a2da82b822b75989cf8effcf008.png)
@Getter@Setter@ToString
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class XmlMessageEntity {
private String ToUserName;
private String FromUserName;
private Long CreateTime;
private String MsgType;
private String Content;
private String Event;
private Long MsgId;
}
这个时候我们就可以在Controller里处理用户发送到公众号上的消息进行回复了
![img_7378e43178279abb84d3bf9ed7fd9da4.png](https://i-blog.csdnimg.cn/blog_migrate/a8f6f549d0844d589c7a92314e371c93.png)
@RequestMapping(value="/weixin",method=RequestMethod.POST)
@ResponseBody
public XmlMessageEntity handlerMessage(@RequestBody XmlMessageEntity entity){
System.out.println(entity);
XmlMessageEntity newEntity = new XmlMessageEntity();
newEntity.setFromUserName(entity.getToUserName()); //设置发送方
newEntity.setToUserName(entity.getFromUserName()); //设置接收方
newEntity.setCreateTime(new Date().getTime()); //设置发送时间
//如果是第一次关注,回复“欢迎关注!”
if("event".equals(entity.getMsgType())){
//如果是关注事件
if("subscribe".equals(entity.getEvent())){
//调用接口获取用户详细信息
String reuslt = HttpUtil.get(WeixinUtil.GET_USERINFO_URL.replace("ACCESS_TOKEN", WeixinUtil.getAccessToken())
.replace("OPENID", entity.getFromUserName()));
System.out.println(WeixinUtil.GET_USERINFO_URL.replace("ACCESS_TOKEN", WeixinUtil.getAccessToken())
.replace("OPENID", entity.getFromUserName()));
System.out.println(reuslt);
//创建客户信息,保存到数据库
//回复内容
newEntity.setContent("欢迎关注!");
}else if ("unsubscribe".equals(entity.getEvent())) {
//更新客户状态,设置为取消关注
}
}
//如果发送的是你好,回复“很高兴认识你”
if(entity.getContent().contains("你好")){
newEntity.setContent("很高兴认识你");
}else{
//否则就统一回复“帅哥好”
newEntity.setContent("帅哥好!");
}
//发送类型
newEntity.setMsgType("text");
return newEntity;
}
现在我通过手机扫二维码进行测试(也可以通过接口调试工具https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index)
![img_dce5c62e2383eaf1e6191638caaba233.png](https://i-blog.csdnimg.cn/blog_migrate/1cf6eaa9c928cd72e379f85b9f097a47.png)
测试成功
![img_0e60702587bb02407f3e1a8f6b7c0a8f.png](https://i-blog.csdnimg.cn/blog_migrate/64b1fab43f57ac9e3acca0750eab3a3f.png)
获取用户基本信息(包括UnionID机制)
开发者可通过OpenID来获取用户基本信息。请使用https协议。
接口调用请求说明
http请求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
![img_2e700650e6f66957d49a291469b2e041.png](https://i-blog.csdnimg.cn/blog_migrate/a74c20ba770ad7095f7f664ca4aea35b.png)
获取access_token
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
![img_a301208ec8977b2f8314511470809836.png](https://i-blog.csdnimg.cn/blog_migrate/c7ccd1813e11b33ad277eed593ecfcb5.png)
首先我们先创建一个网络请求的工具类HttpUtil
/**
* Http工具类
*/
public class HttpUtil {
/**
* 发送get请求
* @throws Exception
*/
public static String get(String url) {
String result = "";
InputStream in = null;
try {
// 打开和URL之间的连接
HttpURLConnection conn = (HttpURLConnection) new URL(url)
.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestMethod("GET");
// 建立实际的连接
conn.connect();
// 定义输入流来读取URL的响应
in = conn.getInputStream();
result = StreamUtils.copyToString(in, Charset.forName("utf-8"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* 发送post请求
*
* @throws Exception
*/
public static String post(String url, String paramStr) {
InputStream in = null;
OutputStream os = null;
String result = "";
try {
// 打开和URL之间的连接
HttpURLConnection conn = (HttpURLConnection) new URL(url)
.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
// 发送POST请求须设置
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
os = conn.getOutputStream();
// 注意编码格式,防止中文乱码
if (StringUtils.hasText(paramStr)) {
os.write(paramStr.getBytes("utf-8"));
os.close();
}
in = conn.getInputStream();
result = StreamUtils.copyToString(in, Charset.forName("utf-8"));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}
接着我们想获取AccessToken 通过HttpUtil发送GET请求 "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"我们把响应回来的数据转成json对象然后回去对象的access_token
public static String accessToken;
//accessToken的失效时间
public static Long expiresTime = 0L;
/**
* 获取AccessToken
* @return
*/
public static String getAccessToken() {
//如果accessToken为null或者accessToken已经失效就去重新获取(提前10秒)
if(new Date().getTime()>= expiresTime){
//发送http请求
String result = HttpUtil.get(GET_ACCESSTOKEN_URL.replace("APPID", APPID).replace("APPSECRET", SECRET));
//转成json对象
JSONObject json = JSON.parseObject(result);
accessToken = (String) json.get("access_token");
Integer expires_in = (Integer) json.get("expires_in");
//失效时间=当前时间+7200
expiresTime = new Date().getTime()+((expires_in-10)*1000);
}
return accessToken;
}
![img_905d8827bada974853edd5d4ee92001f.png](https://i-blog.csdnimg.cn/blog_migrate/74bcd3a43e31ad22d96dde4c48d928a9.png)
public class WeixinUtil {
public static final String TOKEN = "zhangshuai";
public static final String APPID = "wxa379f9e383fb5ad7";
public static final String SECRET = "ce2d71a98230f1d5d6cb35c7e5e90573";
//获取基础ACCESSTOKEN的URL
public static final String GET_ACCESSTOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
public static String accessToken;
//accessToken的失效时间
public static Long expiresTime = 0L;
/**
* 获取AccessToken
* @return
*/
public static String getAccessToken() {
//如果accessToken为null或者accessToken已经失效就去重新获取(提前10秒)
if(new Date().getTime()>= expiresTime){
//发送http请求
String result = HttpUtil.get(GET_ACCESSTOKEN_URL.replace("APPID", APPID).replace("APPSECRET", SECRET));
//转成json对象
JSONObject json = JSON.parseObject(result);
accessToken = (String) json.get("access_token");
Integer expires_in = (Integer) json.get("expires_in");
//失效时间=当前时间+7200
expiresTime = new Date().getTime()+((expires_in-10)*1000);
}
return accessToken;
}
接着我们来获取用户信息
//获取用户信息的URL(需要关注公众号)
public static final String GET_USERINFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN ";
![img_d05cb8a369085b7e5f76ca1c644d2d91.png](https://i-blog.csdnimg.cn/blog_migrate/c4765ef8e8aa1136b724ba9491671bda.png)
//调用接口获取用户详细信息
String reuslt = HttpUtil.get(WeixinUtil.GET_USERINFO_URL.replace("ACCESS_TOKEN", WeixinUtil.getAccessToken())
.replace("OPENID", entity.getFromUserName()));
System.out.println(WeixinUtil.GET_USERINFO_URL.replace("ACCESS_TOKEN", WeixinUtil.getAccessToken())
.replace("OPENID", entity.getFromUserName()));
System.out.println(reuslt);
这时我们重新关注查看日志是否打印用户信息
![img_ddad3f86a3c1075d72275851c09cb455.png](https://i-blog.csdnimg.cn/blog_migrate/7e1edc0d02ff6de1705e490e27d91e6a.png)
自定义菜单创建接口
1、自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
2、一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
3、创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
![img_79b7c9a745918db5b30995e4be8623b1.png](https://i-blog.csdnimg.cn/blog_migrate/2668772ff546b3858e8ac831a5fd3250.png)
先根据文档发送GET请求
![img_7d95494fd6ad73aaa6d3fac34cd783f9.png](https://i-blog.csdnimg.cn/blog_migrate/404276f2b71faa46170ba4f2d500c0f8.png)
//创建自定义菜单的URL
public static final String CREATEMENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
/**
*创建菜单
* @return
*/
public static void createMenu() {
String result = HttpUtil.post(CREATEMENU_URL.replace("ACCESS_TOKEN", getAccessToken()),
"{\"button\":[{ \"type\":\"click\",\"name\":\"今日歌曲\",\"key\":\"V1001_TODAY_MUSIC\"},{\"name\":\"菜单\",\"sub_button\":[{ \"type\":\"view\",\"name\":\"百度官网\",\"url\":\"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx2cd3085e1b3695d9&redirect_uri=http://chen.nat300.top/index.do&response_type=code&scope=snsapi_userinfo#wechat_redirect\"},{\"type\":\"click\",\"name\":\"赞一下我们\",\"key\":\"V1001_GOOD\"}]}]}");
System.out.println(result);
}
![img_bb5c44e18f78896a99db4701bcc4f16e.png](https://i-blog.csdnimg.cn/blog_migrate/fc97758bffcb5bbd702aee067a363954.png)
模板消息
发送模板消息调用接口
http请求方式: POST
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
![img_2ed26de1e4f557edc723e1978328d7cf.png](https://i-blog.csdnimg.cn/blog_migrate/3648431279cd736ac4ecc0cc881f8271.png)
//发送模板消息的URL
public static final String SEND_TEMPLATE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
/**
* 发送模板消息
*/
public static void sendTemplate() {
String result = HttpUtil.post(SEND_TEMPLATE_URL.replace("ACCESS_TOKEN",getAccessToken()),"{\"touser\":\"ogGJv0zE12eFlOGQuCALxtD3vT7E\",\"template_id\":\"flx-OcF_I8Uj6lj3kRLA4_hN79xVCY4OZ006JQLreVU\",\"url\":\"http://www.baidu.com\",\"data\":{\"first\":{\"value\":\"恭喜你购买成功!\",\"color\":\"#173177\"},\"keyword1\":{\"value\":\"巧克力\",\"color\":\"#173177\"},\"keyword3\":{\"value\":\"39.8元\",\"color\":\"#173177\"},\"keyword2\":{\"value\":\"2017年9月04日\",\"color\":\"#173177\"},\"remark\":{\"value\":\"欢迎再次购买!\",\"color\":\"#173177\"}}}");
System.out.println(result);
}
![img_5915b80c40fdd8ea3505431ec5293428.png](https://i-blog.csdnimg.cn/blog_migrate/626a6ca03edefcea1480c9ed0b2da426.png)
每个模板的id不固定
![img_5754dcdc11a2966e2c7f03f600630e21.png](https://i-blog.csdnimg.cn/blog_migrate/00e1bc9530590acce714775342070bfb.png)
![img_a9590f5bc94662ecba8fec5a48ac1901.png](https://i-blog.csdnimg.cn/blog_migrate/d4f968ce7b712a3b34103c20dc0370ce.png)
微信网页授权
关于网页授权回调域名的说明
1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权
3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可
关于网页授权的两种scope的区别说明
1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
关于网页授权access_token和普通access_token的区别
1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;
2、其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。
关于UnionID机制
1、请注意,网页授权获取用户基本信息也遵循UnionID机制。即如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。
2、UnionID机制的作用说明:如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为同一用户,对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号),unionid是相同的。
下面我们来进行网络授权
1 第一步:用户同意授权,获取code
2 第二步:通过code换取网页授权access_token
3 第三步:刷新access_token(如果需要)
4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
修改网页账号
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
![img_d92de38c8680070be159db484dba3a27.png](https://i-blog.csdnimg.cn/blog_migrate/fae60533868c8fb3c5e4624c87063cf1.png)
![img_6c9139c1cb3009f5043b0eaa6c15e844.png](https://i-blog.csdnimg.cn/blog_migrate/42df08db4f086fe6fc09309b3ef8854f.png)
![img_f2c5ca4b965c5c77d66f6a2843640b5d.png](https://i-blog.csdnimg.cn/blog_migrate/bc45fcb85ae61b1ad5a4307c25ed13bd.png)
![img_2a5a10d75ba3277aa825dac87e6a8df2.png](https://i-blog.csdnimg.cn/blog_migrate/d95a28d3718aa2fbac4e21904bd80434.png)
![img_4a717a4c79991e88bb0ad453a354c702.png](https://i-blog.csdnimg.cn/blog_migrate/32ca3accd063fbc3235d01a7b1a860f2.png)
请求方法
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
![img_4c6c54e8fff38cda133abb2610c53a69.png](https://i-blog.csdnimg.cn/blog_migrate/5f8033fb9bef5ce38e5f86fcb2df4b02.png)
//获取网页版的ACCESSTOKEN的URL
public static final String GET_WEB_ACCESSTOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
//通过网页获取用户信息的URL(不需要关注公众号)
public static final String GET_WEB_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
@Controller
public class IndexController {
@RequestMapping("/index")
public String index(String code){
System.out.println(code);
//获取ACCESSTOKEN
String result = HttpUtil.get(WeixinUtil.GET_WEB_ACCESSTOKEN_URL.replace("APPID", WeixinUtil.APPID)
.replace("SECRET", WeixinUtil.SECRET).replace("CODE", code));
System.out.println(result);
JSONObject json = JSON.parseObject(result);
String access_token = json.getString("access_token");
String openid = json.getString("openid");
//获取用户信息
String userinfo = HttpUtil.get(WeixinUtil.GET_WEB_USERINFO_URL.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid));
System.out.println(userinfo);
//把openid放到session
//.....
return "index";
}
}
微信JS-SDK
![img_9d863fdd464a55080af2196eb20424e7.png](https://i-blog.csdnimg.cn/blog_migrate/1488f8ad93defa61437a70ba148a2ce0.png)
第一步:绑定域名
![img_326025928da13c2692bc8c35197f49d3.png](https://i-blog.csdnimg.cn/blog_migrate/a68a57bbcea8ab29c30d1dcac6316924.png)
第二步引入JSP文件
![img_85ecb7ac3385ee30cf56d449de3b7366.png](https://i-blog.csdnimg.cn/blog_migrate/e31f805b157f96831ea715bc1e5f0270.png)
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script type="text/javascript">
//通过config接口注入权限验证配置
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wxa379f9e383fb5ad7', // 必填,公众号的唯一标识
timestamp: '666666', // 必填,生成签名的时间戳
nonceStr: 'zhangshuai', // 必填,生成签名的随机串
signature: 'c7f8ec79e45118a512820345cba07abac0caa0ca',// 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline', 'hideAllNonBaseMenuItem'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function () {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.onMenuShareTimeline({
title: '新的标题', // 分享标题
desc: '新的描述', // 分享描述
link: 'http://zhangshuai.nat100.top/index.jsp', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: 'http://zhangshuai.nat100.top/1.jpg', // 分享图标
success: function () {
window.location.href = "http://www.baidu.com";
// 用户确认分享后执行的回调函数\
},
cancel: function () {
// 用户取消分享后执行的回调函数
alert("取消分享!")
}
});
//隐藏非基础按钮
wx.hideAllNonBaseMenuItem();
});
</script>
<title>免费烧饼</title>
</head>
<body>
烧饼店
</body>
</html>
第三步:通过config接口注入权限验证配置
//通过config接口注入权限验证配置
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wxa379f9e383fb5ad7', // 必填,公众号的唯一标识
timestamp: '666666', // 必填,生成签名的时间戳
nonceStr: 'zhangshuai', // 必填,生成签名的随机串
signature: 'c7f8ec79e45118a512820345cba07abac0caa0ca',// 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline', 'hideAllNonBaseMenuItem'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
在这里值得注意的是signature生成签名要按步骤
签名说明
1.将 api_ticket、timestamp、card_id、code、openid、nonce_str的value值进行字符串的字典序排序。
2.将所有参数字符串拼接成一个字符串进行sha1加密,得到signature。
3.signature中的timestamp,nonce字段和card_ext中的timestamp,nonce_str字段必须保持一致。
4.code=1434008071,timestamp=1404896688,card_id=pjZ8Yt1XGILfi-FUsewpnnolGgZk, api_ticket=ojZ8YtyVyr30HheH3CM73y7h4jJE ,nonce_str=123 则signature=sha1(12314048966881434008071ojZ8YtyVyr30HheH3CM73y7h4jJEpjZ8Yt1XGILfi-FUsewpnnolGgZk)=f137ab68b7f8112d20ee528ab6074564e2796250。
强烈建议开发者使用卡券资料包中的签名工具SDK进行签名或使用debug工具进行校验:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=cardsign
![img_ef5f5a0cccd280b360a505793a0ccfc3.png](https://i-blog.csdnimg.cn/blog_migrate/a44672b59e8d8228607c984cc2ba6e0f.png)
![img_959768c776209e92c048a518962938ab.png](https://i-blog.csdnimg.cn/blog_migrate/1edbdc85a10de5255521c1a68f3ac3b8.png)
WeixinUtil
Dubug模式获取ticket票据
//获取jssdk使用的ticket
public static final String GET_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
public static void main(String[] args) {
//通过access_token获取ticket
String temp = HttpUtil.get(WeixinUtil.GET_TICKET_URL.replace("ACCESS_TOKEN", getAccessToken()));
System.out.println(temp);
}
将控制台输出的票据拷贝
![img_c10acd546e943c7a80d7e4df355a5156.png](https://i-blog.csdnimg.cn/blog_migrate/4c2427c6a1529a42599bdcb4f70306f0.png)
确认签名算法正确,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
![img_7b31745284c3d6aaf5bfcfcf809c9a62.png](https://i-blog.csdnimg.cn/blog_migrate/6a6e0c003c58f88a3ad5a9a615310229.png)
这时将生成的signature签名拷贝到
![img_85ecb7ac3385ee30cf56d449de3b7366.png](https://i-blog.csdnimg.cn/blog_migrate/e31f805b157f96831ea715bc1e5f0270.png)