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,端口記得要開放而且要在防火牆的白名單中放行端口
}
}