1.测试环境
申请测试账号
地址: https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
填写内容:
①:接口配置信息
url:配置http://+域名或者http://+外网ip也可以
token:自定义(任意)
js安全域名(要和url的中的域名)
提交配置信息时微信会先进行验证所以你需要在能访问的url对应的服务上面进行验证代码如下: 能够打印出System.out.println("echostr:"+echostr);的值验证成功.
微信会通过你配置的url+代码中(/) 在你提交接口配置信息时,微信服务会请求到前面叙述的那个路径,来验证成功即能打印出echostr值(关键的一步,如果验证不通过,后续开发没办法进行)
/**
* 微信消息接收和token验证
*
* @param reqDate
* @param request
* @param response
* @throws IOException
value的值你可以自定义,但是要保证和url的一致性
*/
@RequestMapping(value = {"/"}, method = {RequestMethod.POST, RequestMethod.GET})
public void get(@RequestBody(required = false) String reqDate, HttpServletRequest request,
HttpServletResponse response) throws Exception {
boolean isGet = request.getMethod().toLowerCase().equals("get");
PrintWriter print;
if (isGet) {
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (signature != null && CheckoutUtil.checkSignature(signature, timestamp, nonce)) {
try {
print = response.getWriter();
print.write(echostr);
print.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("echostr:"+echostr);
}
}
public class CheckoutUtil {
private static String token = "weixin"; //与在后台配置的Token一样
/**
* 验证签名
*
* @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);
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;
}
/**
* 将字节转换为十六进制字符串
*/
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;
}
public static void sort(String a[]) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j].compareTo(a[i]) < 0) {
String temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
}
②:JS接口安全域名(注意:是域名不是url不要携带 http://,直接填写域名否侧报错:invalid url domain)
2.关注测试号二维码(不重要)
url/token/js域名配置都成功之后就可以开始开发了
3.前端页面代码如下:
$.ajax({
type: "POST",
url: 'http://www.innshine.com/wechat/token',
// contentType: "application/json; charset=utf-8",
dataType: "JSON",
data:{"url":location.href.split('#')[0]},//url一定要是当前的页面的路径而且出去#后面的(微信规定),后台加需要url
success: function (res) {
console.log('res', res)
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wxae743829eed54eaa', // 必填,公众号的唯一标识
timestamp: res.timestamp, // 必填,生成签名的时间戳
nonceStr: res.noncestr, // 必填,生成签名的随机串
signature: res.signature,// 必填,签名
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'] // 必填,需要使用的JS接口列表
});
wx.ready(function () { //需在用户可能点击分享按钮前就先调用
wx.updateAppMessageShareData({
title: 'test', // 分享标题
desc: 'test列表', // 分享描述
link: 'http://www.innshine.com/wechat/index.html', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: 'http://www.innshine.com/wechat/images/down.jpg', // 分享图标
}, function (res) {
console.info("success")
});
wx.updateTimelineShareData({
title: 'test', // 分享标题
link: 'http://www.innshine.com/wechat/index.html', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: 'http://www.innshine.com/wechat/images/down.jpg', // 分享图标
}, function (res) {
console.info("success")
});
});
wx.error(function (res) {
//打印错误消息。及把 debug:false,设置为debug:ture就可以直接在网页上看到弹出的错误提示
alert("错误error" + JSON.stringify(res));
});
}
})
4.后端代码:
controller
@Autowired
WxJsApiServer wxJsApiServer;
@Autowired
Properties properties;
@RequestMapping("token")
public Map<String, String> getJsapiTicket(String url)throws DigestException {
String token = wxJsApiServer.getAccessTokenByAppIdAndSecret(properties.getAPPID(),properties.getAPPSECRET());
String ticket = wxJsApiServer.getJsApiTicketByToken(token);
return getSign(url,ticket);
}
//url:去掉不包含#之后的路径
public Map<String, String> getSign(String url,String ticket ) throws DigestException {
//注意哥哥蚕食都是小写的不要有大写(微信加密参数规则要求)
HashMap<String,String> map = new HashMap<String,String>();
map.put("jsapi_ticket",ticket);
map.put("noncestr",SHA1.generateNonceStr());
map.put("timestamp",String.valueOf(SHA1.getCurrentTimestamp()));//注意:一定要重视这一点参加加密的时间戳是秒级的值 不要使用毫秒值
map.put("url",url);
//生成signature
String signature=SHA1.getSha1Encode(map);
map.put("signature",signature);
logger.info("signature:{}",signature);
return map;
}
properties:参数实体类
@Component
@Data
public class Properties {
/**
* 获取token接口
*/
@Value("${wx.token}")
private String GetPageAccessTokenUrl;
/**
* 获取ticket接口
*/
@Value("${wx.ticket}")
private String GetJsapiTicketUrl;
/**
* 公众号标识
*/
@Value("${wx.appid}")
private String APPID;
/**
* 公众号验证所需参数(必须)
*/
@Value("${wx.secret}")
private String APPSECRET;
}
server
@Autowired
Properties properties;
@Override
public String getAccessTokenByAppIdAndSecret(String APPID, String SECRET) {
String wxUrl = properties.getGetPageAccessTokenUrl().replace("APPID", properties.getAPPID()).replace("SECRET", properties.getAPPSECRET());
HttpClient client = null;
String accessToken = null;
try {
client = new DefaultHttpClient();
HttpGet httpget = new HttpGet(wxUrl);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String response = client.execute(httpget, responseHandler);
JSONObject OpenidJSONO = JSONObject.parseObject(response);
if (OpenidJSONO.get("access_token")==null){
return "FALSE";
}
accessToken = String.valueOf(OpenidJSONO.get("access_token"));
//缓存
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
client.getConnectionManager().shutdown();
}
return accessToken;
}
@Override
public String getJsApiTicketByToken(String accessToken) {
String requestUrl = properties.getGetJsapiTicketUrl().replace("ACCESS_TOKEN", accessToken);
HttpClient client = null;
String ticket = null;
try {
client = new DefaultHttpClient();
HttpGet httpget = new HttpGet(requestUrl);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String response = client.execute(httpget, responseHandler);
JSONObject OpenidJSONO = JSONObject.parseObject(response);
if (OpenidJSONO.get("ticket") == null) {
return "FALSE";
}
ticket = String.valueOf(OpenidJSONO.get("ticket"));
} catch (Exception e) {
e.printStackTrace();
} finally {
client.getConnectionManager().shutdown();
}
return ticket;
}
sha1加密
public class SHA1 {
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5','6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 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();
// Formatter formatter = new Formatter();
// for (byte b : bytes)
// {
// formatter.format("%02x", b);
// }
// String result = formatter.toString();
// formatter.close();
// return result;
}
public static String encode(String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(str.getBytes("UTF-8"));
byte[] bytes= messageDigest.digest();
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文转换成十六进制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0xf]);
buf.append(HEX_DIGITS[bytes[j] & 0xf]);
}
return buf.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getSha1(String str){
if(str == null || str.length()==0){
return null;
}
char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
try {
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));
byte[] md = mdTemp.digest();
int j = md.length;
char buf[] = new char[j*2];
int k = 0;
for(int i=0;i<j;i++){
byte byte0 = md[i];
buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
buf[k++] = hexDigits[byte0 & 0xf];
}
return new String(buf);
} catch (Exception e) {
return null;
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* SHA1 安全加密算法
*
* @return
* @throws DigestException
*/
public static String getSha1Encode(Map<String, String> map) {
String encode = encode(getOrderByLexicographic(map));
return encode;
}
public static String getOrderByLexicographic(Map<String, String> map) {
return splitParams(lexicographicOrder(getParamsName(map)), map);
}
/**
* 拼接参数
*
* @param paramNames
* @param maps
* @return
*/
public static String splitParams(List<String> paramNames, Map<String, String> maps) {
StringBuilder params = new StringBuilder();
for (String paramName : paramNames) {
params.append(paramName);
for (Map.Entry<String, String> entry : maps.entrySet()) {
if (paramName.equals(entry.getKey())) {
params.append("=" + String.valueOf(entry.getValue()) + "&");
}
}
}
params.deleteCharAt(params.length() - 1);
return params.toString();
}
/**
* 参数按字典顺序排序
*
* @param paramNames 参数集合
* @return 返回排序集合
*/
public static List<String> lexicographicOrder(List<String> paramNames) {
Collections.sort(paramNames);
return paramNames;
}
/**
* 获取参数名称 key
*
* @param maps
* @return
*/
public static List<String> getParamsName(Map<String, String> maps) {
List<String> paramNames = new ArrayList<String>();
for (Map.Entry<String, String> entry : maps.entrySet()) {
paramNames.add(entry.getKey());
}
return paramNames;
}
httputile:
public class HttpUtils {
/**
* get请求,参数拼接在地址上
* @param url 请求地址加参数
* @return 响应
*/
public static String get(String url,String AccessToken)
{
String result = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
url = url.replaceAll(" ","%20");
HttpGet get = new HttpGet(url);
if(AccessToken != null && !AccessToken.equals("")){
get.addHeader("Authorization",AccessToken);
}
CloseableHttpResponse response = null;
try {
response = httpClient.execute(get);
if(response != null && response.getStatusLine().getStatusCode() == 200)
{
HttpEntity entity = response.getEntity();
result = entityToString(entity);
}
return result;
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
if(response != null)
{
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* get请求,参数放在map里
* @param url 请求地址
* @param map 参数map
* @return 响应
*/
public static String getMap(String url,String AccessToken,Map<String,String> map)
{
String result = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
for(Map.Entry<String,String> entry : map.entrySet())
{
pairs.add(new BasicNameValuePair(entry.getKey(),entry.getValue()));
}
CloseableHttpResponse response = null;
try {
URIBuilder builder = new URIBuilder(url);
builder.setParameters(pairs);
HttpGet get = new HttpGet(builder.build());
if(AccessToken != null && !AccessToken.equals("")){
get.addHeader("Authorization",AccessToken);
}
response = httpClient.execute(get);
if(response != null && response.getStatusLine().getStatusCode() == 200)
{
HttpEntity entity = response.getEntity();
result = entityToString(entity);
}
return result;
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
if(response != null)
{
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 发送post请求,参数用map接收
* @param url 地址
* @param map 参数
* @return 返回值
*/
public static String postMap(String url,Map<String,String> map) {
String result = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost post = new HttpPost(url);
post.addHeader("Content-Type", "application/json");
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
if (map != null){
for(Map.Entry<String,String> entry : map.entrySet())
{
pairs.add(new BasicNameValuePair(entry.getKey(),entry.getValue()));
}
}
CloseableHttpResponse response = null;
try {
post.setEntity(new UrlEncodedFormEntity(pairs,"UTF-8"));
response = httpClient.execute(post);
if(response != null && response.getStatusLine().getStatusCode() == 200)
{
HttpEntity entity = response.getEntity();
result = entityToString(entity);
}
return result;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
if(response != null)
{
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 发送post请求,参数用map<String object>接收
* @param url
* @param map
* @param encoding
* @return
*/
public static String mapPost(String url, Map<String,Object> map, String encoding){
CloseableHttpClient httpClient = null;
HttpPost httpPost = null;
String result = null;
try{
httpClient = HttpClients.createDefault();
httpPost = new HttpPost(url);
//设置参数
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<String,String> elem = (Map.Entry<String, String>) iterator.next();
list.add(new BasicNameValuePair(elem.getKey(),String.valueOf(elem.getValue())));
}
if(list.size() > 0){
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list,encoding);
httpPost.setEntity(entity);
}
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
result = EntityUtils.toString(resEntity,encoding);
}
}
}catch(Exception ex){
ex.printStackTrace();
}
System.out.println(result);
return result;
}
/**
* post请求,参数为json字符串
* @param url 请求地址
* @param jsonString json字符串
* @return 响应
*/
public static String postJson(String url,String jsonString,String AccessToken)
{
String result = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost post = new HttpPost(url);
post.addHeader("Content-Type", "application/json");
if(AccessToken != null && !AccessToken.equals("")){
post.addHeader("Authorization",AccessToken);
}
CloseableHttpResponse response = null;
try {
if(jsonString != null && !jsonString.equals("")){
post.setEntity(new ByteArrayEntity(jsonString.getBytes("UTF-8")));
}
response = httpClient.execute(post);
if(response != null && response.getStatusLine().getStatusCode() == 200)
{
HttpEntity entity = response.getEntity();
result = entityToString(entity);
}
return result;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
if(response != null)
{
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private static String entityToString(HttpEntity entity) throws IOException {
String result = null;
if(entity != null)
{
long lenth = entity.getContentLength();
if(lenth != -1 && lenth < 2048)
{
result = EntityUtils.toString(entity,"UTF-8");
}else {
InputStreamReader reader1 = new InputStreamReader(entity.getContent(), "UTF-8");
CharArrayBuffer buffer = new CharArrayBuffer(2048);
char[] tmp = new char[1024];
int l;
while((l = reader1.read(tmp)) != -1) {
buffer.append(tmp, 0, l);
}
result = buffer.toString();
}
}
return result;
}
}
正式环境开发:
登录公众号平台:
配置两处:
1.导航"基础配置"中配置url/token,配置要求和测试配置一样.
2.再在"基础配置"的"ip白名单"添加项目所要部署到的服务器的ip.
3.导航"公众号设置"中再点击"功能设置"然后配置"js安全域配置,注意此时需要下载一个文件MP_verify_gt5yNwRcoQWm4SIk.txt,文件可在弹出窗口中的提示出获取,文件下载以后*(springboot项目为例)放在static目录下,然后通过上面配置的url+文件名,页面响应一串字符串即代表成功.
正是开发代码和测试代码一样.
微信 JS 接口签名校验工具: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
nginx配置
安装和配置过程可以自己百度:
注意点给大家说一下:nginx.conf文件
server {
listen 8089;//只是服务器开放的端口并不是项目端口号,但是却要和项目的端口号保持一致.默认是80端口
server_name www.innshines.com;//你自己的域名
location /wxshare {
# root html;
# index index.html index.htm;
proxy_pass http://127.0.0.1:8089;//项目部署所在的服务器的本地ip,端口记得要开放而且要在防火墙的白名单中放行端口
}
}