微信公众平台:
https://mp.weixin.qq.com/cgi-bin/loginpage
微信开放平台:
https://open.weixin.qq.com/
微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
两者的区别:
开放平台是网站或app使用的接口平台,利用开放平台可在自己的网站或app上开发微信帐户登录、微信分享等功能!
公众平台是微信号的一种,也具有开发功能,是在公众号中开发出更多功能,例如微网站等!
简单来讲,公众平台的开发功能是在微信平台的基础上的,而开放平台是在你自己的平台上开发的与微信相关的一些功能。
备注:本文开发内容是微信公众平台的内容,涉及官方文档如下:
获取全局token
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
网页授权
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
获取用户基本信息(UnionID机制)
https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
个人学习时可申请测试帐号:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Requesting_an_API_Test_Account.html
注意:公众平台接口调用仅支持80端口,且项目支持外网访问,个人学习时可在本地配置内网穿透,参考https://blog.csdn.net/qq_37718403/article/details/106093162
前提:正式帐号接入微信公众平台的配置参考
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
官方描述:
AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)(appSecret只展示一次,需保存下来,否则需要重置获取)。
获取access_token时需要添加IP白名单。
点击查看
点击设置,input框中输入授权回调页的域名参考第1点(只能填写一个),下载第3点中的txt文档,上传至服务器的根目录。
微信开放接口全局返回码说明参考:https://mp.weixin.qq.com/wiki…
WxController
package com.controller;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.ParseException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.util.WeiXinUtil;
import net.sf.json.JSONObject;
@Controller
@RequestMapping("/wx")
public class WxController {
public static final String APPID="";
public static final String APPSECRET ="";
private static final Logger logger = Logger.getLogger(WxController.class);
@RequestMapping(value = "/wxLogin", method = RequestMethod.GET)
public String wxLogin(HttpServletRequest request, HttpServletResponse response)throws ParseException, UnsupportedEncodingException {
//这个地址是成功后的回调地址,域名必须和公众号中配置的域名一致
String backUrl="http://xiayehuimou.free.idcfengye.com/ssm_demo/wx/callBack";
// 第一步:用户同意授权,获取code
String url ="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ APPID
+ "&redirect_uri="
+URLEncoder.encode(backUrl, "utf-8")
+ "&response_type=code"
+ "&scope=snsapi_userinfo"
+ "&state=STATE#wechat_redirect";
logger.info("forward重定向地址{" + url + "}");
return "redirect:" + url ;
}
@RequestMapping(value = "/callBack", method = RequestMethod.GET)
public String callBack(ModelMap modelMap,HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String code =req.getParameter("code");
//第二步:通过code换取网页授权access_token
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+APPID
+ "&secret="+APPSECRET
+ "&code="+code
+ "&grant_type=authorization_code";
System.out.println("url:"+url);
JSONObject jsonObject = WeiXinUtil.httpsRequest(url, "GET", null);
String openid = jsonObject.getString("openid");
String access_token = jsonObject.getString("access_token");
String refresh_token = jsonObject.getString("refresh_token");
//第五步验证access_token是否失效;展示都不需要
String chickUrl="https://api.weixin.qq.com/sns/auth?access_token="+access_token+"&openid="+openid;
JSONObject chickuserInfo =WeiXinUtil.httpsRequest(chickUrl, "GET", null);
System.out.println(chickuserInfo.toString());
if(!"0".equals(chickuserInfo.getString("errcode"))){
// 第三步:刷新access_token(如果需要)-----暂时没有使用,参考文档https://mp.weixin.qq.com/wiki,
String refreshTokenUrl="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+openid+"&grant_type=refresh_token&refresh_token="+refresh_token;
JSONObject refreshInfo = WeiXinUtil.httpsRequest(refreshTokenUrl, "GET", null);
System.out.println(refreshInfo.toString());
access_token=refreshInfo.getString("access_token");
}
// 第四步:拉取用户信息(需scope为 snsapi_userinfo)
// 这个url的token是网页授权token(获取的基本信息内容相对较少,没有是否关注公众号字段)
//String infoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+access_token
// + "&openid="+openid
// + "&lang=zh_CN";
// 此处的token是全局token
String infoUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+WeiXinUtil.getToken(APPID,APPSECRET).getAccessToken()
+ "&openid="+openid
+ "&lang=zh_CN";
System.out.println("infoUrl:"+infoUrl);
JSONObject userInfo = WeiXinUtil.httpsRequest(infoUrl, "GET", null);
System.out.println("JSON-----"+userInfo.toString());
System.out.println("名字-----"+userInfo.getString("nickname"));
System.out.println("头像-----"+userInfo.getString("headimgurl"));
//获取到用户信息后就可以进行重定向,走自己的业务逻辑了。。。。。。
//接来的逻辑就是你系统逻辑了,请自由发挥
// 我此处是打开file.html页面
return "file";
}
}
WeiXinUtil
package com.util;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.entity.Token;
/**
* 类名: CommonUtil
* 描述: 通用工具类
* 开发人员: howin
* 创建时间: 2016-08-19
* 发布版本:V1.0
*/
public class WeiXinUtil {
private static Logger log = LoggerFactory.getLogger(WeiXinUtil.class);
private static long tokenTime = 0;
private static long jsTicketTime = 0;
private static Token token = null;
private static String ticket = null;
// (全局token)凭证获取(GET)限2000(次/天)
public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* 发送https请求
*
* @param requestUrl
* 请求地址
* @param requestMethod
* 请求方式(GET、POST)
* @param outputStr
* 提交的数据
* @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
*/
public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
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 conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
} catch (ConnectException ce) {
log.error("连接超时:{}", ce);
} catch (Exception e) {
log.error("https请求异常:{}", e);
}
return jsonObject;
}
/**
* 获取接口访问凭证
*
* @param appid
* 凭证
* @param appsecret
* 密钥
* @return
*/
public static Token getToken(String appid, String appsecret) {
long now = new Date().getTime();
if (tokenTime != 0 && now - tokenTime < 7000000) {// token有效时间 7e6 毫秒
return token;
}
String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
// 发起GET请求获取凭证
JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
if (null != jsonObject) {
try {
token = new Token();
token.setAccessToken(jsonObject.getString("access_token"));
token.setExpiresIn(jsonObject.getInt("expires_in"));
tokenTime = now;
} catch (JSONException e) {
token = null;
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"),
jsonObject.getString("errmsg"));
}
}
return token;
}
/**
* 获取jsapi_ticket访问凭证
*/
public static String getJsTicket() {
long now = new Date().getTime();
if (jsTicketTime != 0 && now - jsTicketTime < 7000000) {// token有效时间 7e6 毫秒
return ticket;
}
// 得到token
String accessToken = getToken(null, null).getAccessToken();
// GET方法获得jsapi_ticket
String requestUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"
.replace("ACCESS_TOKEN", accessToken);
// 发起GET请求获取凭证
JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
if (jsonObject != null) {
ticket = jsonObject.getString("ticket");
jsTicketTime = now;
}
return ticket;
}
/**
* URL编码(utf-8)
*
* @param source
* @return
*/
public static String urlEncodeUTF8(String source) {
String result = source;
try {
result = java.net.URLEncoder.encode(source, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
/**
* 根据内容类型判断文件扩展名
*
* @param contentType
* 内容类型
* @return
*/
public static String getFileExt(String contentType) {
String fileExt = "";
if ("image/jpeg".equals(contentType))
fileExt = ".jpg";
else if ("audio/mpeg".equals(contentType))
fileExt = ".mp3";
else if ("audio/amr".equals(contentType))
fileExt = ".amr";
else if ("video/mp4".equals(contentType))
fileExt = ".mp4";
else if ("video/mpeg4".equals(contentType))
fileExt = ".mp4";
return fileExt;
}
// 菜单创建(POST) 限100(次/天)
public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
/**
* 创建菜单
*
* @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 =
* httpsRequest(url, "POST", jsonMenu); System.out.println(jsonMenu); if (null
* != jsonObject) { if (0 != jsonObject.getInt("errcode")) { result =
* jsonObject.getInt("errcode"); log.error("创建菜单失败 errcode:{} errmsg:{}",
* jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); } }
*
* return result; }
*/
/**
* 对Map数组进行排序
*/
public static String FormatQueryParaMap(HashMap<String, String> parameters) throws UnsupportedEncodingException {
String buff = "";
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(parameters.entrySet());
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
for (int i = 0; i < infoIds.size(); i++) {
Map.Entry<String, String> item = infoIds.get(i);
if (item.getKey() != "") {
// buff += item.getKey() + "="+ URLEncoder.encode(item.getValue(), "utf-8") +
// "&";
buff += item.getKey() + "=" + item.getValue() + "&";
}
}
if (buff.isEmpty() == false) {
buff = buff.substring(0, buff.length() - 1);
}
return buff;
}
/**
* 生成32位随机字符串
*/
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;
}
/**
* SHA1加密
*/
public final static String Sha1(String s) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
byte[] btInput = s.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("sha-1");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
MyX509TrustManager
package com.util;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* 类名: MyX509TrustManager
* 描述:信任管理器
* 开发人员: howin
* 创建时间: 2016-08-19
* 发布版本:V1.0
*/
/*
* 证书管理器的作用是让它新人我们指定的证书,
* 此类中的代码意味着信任所有的证书,不管是不是权威机构颁发的。
*/
public class MyX509TrustManager implements X509TrustManager {
// 检查客户端证书
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
// 检查服务器端证书
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
// 返回受信任的X509证书数组
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
如果使用测试号,用户必须要先关注测试号,否则获取code时不会执行回调方法
微信公众号测试账号获取授权须关注