一 首先实现内网穿透,公众号需要连接我们的服务器,内外无法访问,所以先实现自己的内网可以测试时连接外网,下载natapp,选择windows,顺便下载config,ini 配置文件。注册好购买免费的隧道 然后将token写入配置文件中,操作很简单,windows中需要加入环境变量,因为我直接放到D盘的natapp文件夹中,所以路劲为D:\natapp即可,双击.exe 就能启动 http://phxgaj.natappfree.cc -> 127.0.0.1:90 通过前面的网址phxgaj.natappfree.cc 访问就能连接我们自己的内网环境了。
二 自然是注册微信公众号了,注册的时候要瞪大眼睛了,微信公众平台 微信开发平台 服务号订阅号小程序一定要分清楚,一个邮箱只能注册一个功能,所以要慎重选,我注册的是 微信公众平台的 服务号,适合公司使用
三 公众号接入,在开发者配置中,写进这个方法,我用的是springmvc,所以要写到这个指定的路径
import java.io.IOException;5 import java.io.UnsupportedEncodingException;6 import java.security.MessageDigest;7 import java.security.NoSuchAlgorithmException;8 import java.util.Arrays;9 import java.util.Map; 10 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 14 import org.slf4j.Logger; 15 import org.slf4j.LoggerFactory; 16 import org.springframework.beans.BeansException; 17 import org.springframework.context.ApplicationContext; 18 import org.springframework.context.ApplicationContextAware; 19 import org.springframework.stereotype.Component; 20 import org.springframework.stereotype.Controller; 21 import org.springframework.web.bind.annotation.RequestMapping; 22 import org.springframework.web.bind.annotation.RequestMethod; 23 24 import com.alibaba.fastjson.JSON; 25 import com.alibaba.fastjson.JSONObject; 26 import com.fuyin.mp.entity.AccessToken; 27 import com.fuyin.mp.utils.GetMenuJson; 28 import com.fuyin.mp.utils.MessageHandlerUtil; 29 import com.fuyin.mp.utils.NetWorkHelper; 30 import com.fuyin.mp.utils.WxaApi; 31 @Controller 32 @RequestMapping("wxconnect.action") 33 public class WxBase { 34 private static final Logger logger = LoggerFactory.getLogger(WxBase.class); 35 /* 36 * 自定义token, 用作生成签名,从而验证安全性 37 * */ 38 private final String TOKEN = WxaApi.TOKEN; 39 /** 40 * 检验签名 41 * @param req 42 * @param res 43 */ 44 @RequestMapping(method = RequestMethod.GET) 45 public void wxconnect(HttpServletRequest req,HttpServletResponse res){ 46 logger.debug("-----开始校验签名-----"); 47 /** 48 * 接收微信服务器发送请求时传递过来的参数 49 */ 50 String signature = req.getParameter("signature"); 51 String timestamp = req.getParameter("timestamp"); 52 String nonce = req.getParameter("nonce"); //随机数 53 String echostr = req.getParameter("echostr");//随机字符串 54 55 /** 56 * 将token、timestamp、nonce三个参数进行字典序排序 57 * 并拼接为一个字符串 58 */ 59 String sortStr = sort(TOKEN,timestamp,nonce); 60 /** 61 * 字符串进行shal加密 62 */ 63 String mySignature = shal(sortStr); 64 /** 65 * 校验微信服务器传递过来的签名 和 加密后的字符串是否一致, 若一致则签名通过 66 */ 67 if(!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)){ 68 try { 69 logger.debug("-----签名校验通过-----"); 70 res.getWriter().write(echostr); 71 } catch (IOException e) { 72 // TODO Auto-generated catch block 73 e.printStackTrace(); 74 } 75 }else { 76 logger.debug("-----校验签名失败-----"); 77 } 78 } 79 @RequestMapping(method = RequestMethod.POST) 80 protected void doPost(HttpServletRequest request, HttpServletResponse response) { 81 // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息 82 // 将请求、响应的编码均设置为UTF-8(防止中文乱码) 83 try { 84 request.setCharacterEncoding("UTF-8"); 85 response.setCharacterEncoding("UTF-8"); 86 System.out.println("请求进入"); 87 String result = ""; 88 try { 89 Map<String,String> map = MessageHandlerUtil.parseXml(request); 90 System.out.println("开始构造消息"); 91 result = MessageHandlerUtil.buildResponseMessage(map, "你好"); 92 System.out.println(result); 93 if(result.equals("")){ 94 result = "未正确响应"; 95 } 96 } catch (Exception e) { 97 e.printStackTrace(); 98 System.out.println("发生异常:"+ e.getMessage()); 99 } 100 response.getWriter().write(result); 101 } catch (UnsupportedEncodingException e) { 102 // TODO Auto-generated catch block 103 e.printStackTrace(); 104 } catch (IOException e) { 105 // TODO Auto-generated catch block 106 e.printStackTrace(); 107 } 108 } 109 110 /** 111 * 参数排序 112 * @param token 113 * @param timestamp 114 * @param nonce 115 * @return 116 */ 117 public String sort(String token, String timestamp, String nonce) { 118 String[] strArray = {token, timestamp, nonce}; 119 Arrays.sort(strArray); 120 StringBuilder sb = new StringBuilder(); 121 for (String str : strArray) { 122 sb.append(str); 123 } 124 return sb.toString(); 125 } 126 127 /** 128 * 字符串进行shal加密 129 * @param str 130 * @return 131 */ 132 public String shal(String str){ 133 try { 134 MessageDigest digest = MessageDigest.getInstance("SHA-1"); 135 digest.update(str.getBytes()); 136 byte messageDigest[] = digest.digest(); 137 138 StringBuffer hexString = new StringBuffer(); 139 // 字节数组转换为 十六进制 数 140 for (int i = 0; i < messageDigest.length; i++) { 141 String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); 142 if (shaHex.length() < 2) { 143 hexString.append(0); 144 } 145 hexString.append(shaHex); 146 } 147 return hexString.toString(); 148 149 } catch (NoSuchAlgorithmException e) { 150 e.printStackTrace(); 151 } 152 return ""; 153 } 154 }
WxApi 这个是工具类,因为测试用的穿透所以一直要改Ip,干脆提取出来修改方便,上面代码是接入微信服务。
import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fuyin.mp.entity.AccessToken; import com.fuyin.mp.utils.GetMenuJson; import com.fuyin.mp.utils.NetWorkHelper; import com.fuyin.mp.utils.WxaApi; /** * 线程 自动获得token * @author Administrator * */ @Controller public class AccessTokenInit implements ApplicationContextAware{ private static final Logger logger = LoggerFactory.getLogger(AccessTokenInit.class); @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { new Thread(new Runnable() { @Override public void run() { while (true) { try { //获取accessToken System.out.println("开始获取TOKEN"); WxaApi.accessToken = getAccessToken(WxaApi.appID, WxaApi.appsecret); //获取成功 if ( WxaApi.accessToken== null) { //获取失败 Thread.sleep(1000 * 3); //获取的access_token为空 休眠3秒 } else { //获取到access_token 休眠7000秒,大约2个小时左右 Thread.sleep(7000 * 1000); //Thread.sleep(10 * 1000);//10秒钟获取一次 } } catch (Exception e) { System.out.println("发生异常:" + e.getMessage()); e.printStackTrace(); try { Thread.sleep(1000 * 10); //发生异常休眠1秒 } catch (Exception e1) { } } } } }).start(); } /** * 获取access_token * * @return AccessToken */ private AccessToken getAccessToken(String appId, String appSecret) { NetWorkHelper netHelper = new NetWorkHelper(); String Url=WxaApi.GetTokenApi.replace("APPID", appId).replaceAll("APPSECRET", appSecret);; //此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200} String result = netHelper.getHttpsResponse(Url, "",null); System.out.println("获取到的access_token="+result); //使用FastJson将Json字符串解析成Json对象 JSONObject json = JSON.parseObject(result); AccessToken token = new AccessToken(); token.setAccessToken(json.getString("access_token")); token.setExpiresin(json.getInteger("expires_in")); return token; } }
这是一个springmvc启动以后就会启动的线程,用来自动获取token
工具类 用来发送https请求
1 import javax.net.ssl.*;2 import java.io.BufferedReader;3 import java.io.InputStream;4 import java.io.InputStreamReader;5 import java.io.OutputStream;6 import java.io.UnsupportedEncodingException; 7 import java.net.URL; 8 import java.net.URLEncoder; 9 import java.security.cert.CertificateException; 10 import java.security.cert.X509Certificate; 11 12 /** 13 * 访问网络用到的工具类 14 */ 15 public class NetWorkHelper { 16 17 /** 18 * 发起Https请求 19 * @param reqUrl 请求的URL地址 20 * @param requestMethod 21 * @return 响应后的字符串 22 */ 23 public String getHttpsResponse(String reqUrl, String requestMethod,String outputStr) { 24 URL url; 25 InputStream is; 26 String resultData = ""; 27 try { 28 url = new URL(reqUrl); 29 HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); 30 TrustManager[] tm = {xtm}; 31 32 SSLContext ctx = SSLContext.getInstance("TLS"); 33 ctx.init(null, tm, null); 34 con.setSSLSocketFactory(ctx.getSocketFactory()); 35 con.setHostnameVerifier(new HostnameVerifier() { 36 @Override 37 public boolean verify(String arg0, SSLSession arg1) { 38 return true; 39 } 40 }); 41 42 43 con.setDoInput(true); //允许输入流,即允许下载 44 45 //在android中必须将此项设置为false 46 con.setDoOutput(true); //允许输出流,即允许上传 47 con.setUseCaches(false); //不使用缓冲 48 if (null != requestMethod && !requestMethod.equals("")) { 49 con.setRequestMethod(requestMethod); //使用指定的方式 50 } else { 51 con.setRequestMethod("GET"); //使用get请求 52 } 53 con.setRequestProperty("content-type", 54 "application/x-www-form-urlencoded"); 55 // 当outputStr不为null时向输出流写数据 56 if (null != outputStr) { 57 OutputStream outputStream = con.getOutputStream(); 58 // 注意编码格式 59 outputStream.write(outputStr.getBytes("UTF-8")); 60 outputStream.close(); 61 } 62 is = con.getInputStream(); //获取输入流,此时才真正建立链接 63 InputStreamReader isr = new InputStreamReader(is); 64 BufferedReader bufferReader = new BufferedReader(isr); 65 String inputLine; 66 while ((inputLine = bufferReader.readLine()) != null) { 67 resultData += inputLine + "\n"; 68 } 69 // System.out.println(resultData); 70 71 } catch (Exception e) { 72 e.printStackTrace(); 73 } 74 return resultData; 75 } 76 77 X509TrustManager xtm = new X509TrustManager() { 78 @Override 79 public X509Certificate[] getAcceptedIssuers() { 80 return null; 81 } 82 83 @Override 84 public void checkServerTrusted(X509Certificate[] arg0, String arg1) 85 throws CertificateException { 86 87 } 88 89 @Override 90 public void checkClientTrusted(X509Certificate[] arg0, String arg1) 91 throws CertificateException { 92 93 } 94 }; 95 /** 96 * 对URL地址进行EnCode处理 97 * @param url 98 * @return 99 */ 100 public static String urlEnCode(String url) 101 { 102 String enCodedUrl = ""; 103 104 try 105 { 106 enCodedUrl = URLEncoder.encode(url, "utf-8"); 107 } 108 catch (UnsupportedEncodingException e) 109 { 110 // TODO Auto-generated catch block 111 e.printStackTrace(); 112 System.out.println("转码失败!"); 113 } 114 115 return enCodedUrl; 116 } 117 }
线程启动可能会获取两次token是因为springmvc把项目的Bean扫描了两次,可以把applicationContext.xml配置成只扫描mapper,其他不扫,由springmvc.xml去扫面serveice 和controller,因为我这样还是失败了,所以把线程的类也定义成controller,这样用前面的方法只会扫描一次