微信公众号的端口映射及服务开发

xiaoxiao2021-02-28  44

一. 开发准备

微信公众号申请---->实名认证---->服务器开发---->绑定服务器

PS: 这里有一点需要注意的就是, 微信开发必须是80端口或者443端口, 如果我们有云服务器主机一切都好办. 但是如果没有我们还有几个备选方案:

1. 花生壳 , net123 : 这两个都需要实名认证(上传省份证的那种), 可以花6块钱买一个永久

2. ngrok : 这个貌似不能用了, 之前用的是这个, 免费的

3. Xtunnel : 刚发现的端口映射, 免费, 操作简单

这篇文章我就使用Xtunnel来进行端口映射

再加一个: holer ,  隧道魔法

 

二. 用Xtunnel进行端口映射

1.编辑映射

注意这里的映射类型选择网站映射, 内网端口设为80端口

确定之后就会有一个外网地址, 如: abc.d.ef.org, 这就是我们后面填写url所需要的地址了.

PS: 可能不太稳定, 今天挂了, 建议多准备几个端口映射工具或者买一个花生壳服务.

2.本地tomcat配置

我们需要把本地的tomcat服务器也配置为80端口. 把server.xml里的端口改为80, 如果不会请自行百度.

我们最好把服务器备份一个, 把用的服务器放在c盘目录下, 把web工程的war包放在webapps目录下, 启动服务器时会自动解压部署的.

具体的web开发我们下面慢慢说.

三. 微信服务器开发

step1 : token验证原理

先说一下token验证流程原理, 自己的资源服务器和微信服务器进行绑定, 提交的url为token验证的接口(servlet), 微信服务器会把echostr发到自己资源的服务器, 然后我们的服务器进行token验证, 看看是否匹配. 匹配之后再将echostr返回给微信服务器, 这样服务器就绑定成功. 同时, 我们自己的资源服务器和微信服务器的消息收发都是通过验证token的那个接口(servlet)来实现的.

绑定流程图:

token验证的具体实现:

import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; /** * 网关控制层 * @author later * */ @WebServlet("/TokenServlet") //这就是待会我们访问的接口 public class TokenServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static Logger log = Logger.getLogger("TokenServlet"); /** * 验证信息是否来自微信 */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String signature = request.getParameter("signature");// 微信加密签名 String timestamp = request.getParameter("timestamp");// 时间戳 String nonce = request.getParameter("nonce"); // 随机数 String echostr = request.getParameter("echostr"); // 随机字符串 PrintWriter out = response.getWriter(); //通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败 if (SignUtil.checkSignature(signature, timestamp, nonce)) { out.print(echostr); //向微信返回echostr } else{ log.error("未能通过token验证_TokenServlet"); System.out.println("未能通过token验证_TokenServlet"); } out.close(); //关闭输出流通道 out = null; } /** * 处理微信服务器发来的消息 */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // 调用核心业务类接收消息、处理消息 String respMessage = GatewayService.processRequest(request); // 响应消息 PrintWriter out = response.getWriter(); out.print(respMessage); out.close(); }  } import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; public class SignUtil { private static String token = "token"; //微信绑定页面填入的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); 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; } /** * 将字节转换为十六进制字符串 * * @param mByte * @return */ 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; } }

step2 : 服务器结构

这里的结构和我的前一篇文章类似 : java web接口开发笔记

1. main包:

   main方法1: 获取token管理

             AccessTokenManager---->TokenThread---->WeixinUtil>>getAccessToken()

            功能: 每3600s获取微信access_token并保存至本地mysql数据库

   main方法2: 菜单管理

public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";// 菜单创建(POST) 限100(次/天)

微信创建菜单接口:

用获取getAccessToken()方法获取access_token, 保存至数据库, 在从数据库查询access_token写入这个url去创建菜单.getMenu() 作为createMenu(getMenu(), at.getToken()); 这个方法的实参具体构建菜单, getMenu()是我们提前写好的菜单.

2. service包(业务层):

这层和下面一层涉及到微信服务器发送给我们的消息, 可供我们直观的看见, 所以我们也应该好好理解一下.

dao      : 与数据库的操作,增删改查等方法 model  : 一般都是javabean对象,例如与数据库的某个表相关联。 service :  供外部调用,等于对dao,model等进行了包装。 impl     : 定义的接口 util       : 通常都是工具类,如字符串处理、日期处理等

AccessTokenService.java : 包含两种方法: 获取token, 保存token

TokenService.java            : 这是网关业务层 : 用于处理微信发过来的请求

        先解析微信发过来的请求request, 解析成我们需要的数据流或者文件流. 这里使用了MessageUtil类, 处理微信发到服务器的消息.  并且新建一个文本消息, 把我们要发给微信的消息转换成xml, 通过servlet的doPost方法发送给微信.

3. utils包:

这里是一些常用的网络方法, 和数据库连接操作类

额(⊙o⊙)…思路乱了, 感觉自己解释不清楚, 继续尝试解释吧. 有错误欢迎交流.

 

step3 : 本地mysql

给大家看一下我的本地mysql的设计, 这里就很简单不必多说了.

DBHelper.java

其实这个数据库操作类可灵活了, 连接好本地mysql数据库, 写入基本的操作方法, 在写入自己需要的方法, 后面直接拿过来调用方便的很. 大家自行添加就OK啦.

import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * JDBC封装 * @author later * */ //本地mysql数据库: restful_api 表名: access_token public class DBHelper { private static final String DRIVENAME = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://127.0.0.1:3306/restful_api"; private static final String USER = "root"; private static final String PASSWORD = "root"; private Connection conn = null; private Statement st = null; private PreparedStatement ppst = null; private ResultSet rs = null; /** * 加载驱动 */ static{ try { Class.forName(DRIVENAME).newInstance(); } catch (Exception e) { System.out.println("驱动加载失败:"+e.getMessage()); } } /** * 连接数据库 * @return */ public Connection getConn(){ try { conn = DriverManager.getConnection(URL,USER,PASSWORD); } catch (SQLException e) { System.out.println("数据库连接失败:"+e.getMessage()); } return conn; } /** * 获取结果集(无参) * @param sql * @return */ private ResultSet getRs(String sql){ conn = this.getConn(); try { st = conn.createStatement(); rs = st.executeQuery(sql); } catch (SQLException e) { System.out.println("查询(无参)出错:"+e.getMessage()); } return rs; } /** * 获取结果集 * @param sql * @param params * @return */ private ResultSet getRs(String sql,Object[] params){ conn = this.getConn(); try { ppst = conn.prepareStatement(sql); if(params!=null){ for(int i = 0;i<params.length;i++){ ppst.setObject(i+1, params[i]); } } rs = ppst.executeQuery(); } catch (SQLException e) { System.out.println("查询出错:"+e.getMessage()); } return rs; } /** * 查询 * @param sql * @param params * @return */ public List<Object> query(String sql,Object[] params){ List<Object> list = new ArrayList<Object>(); ResultSet rs = null; if(params!=null){ rs = getRs(sql, params); }else{ rs = getRs(sql); } ResultSetMetaData rsmd = null; int columnCount = 0; try { rsmd = rs.getMetaData(); columnCount = rsmd.getColumnCount(); while(rs.next()){ Map<String, Object> map = new HashMap<String, Object>(); for(int i = 1;i<=columnCount;i++){ map.put(rsmd.getColumnLabel(i), rs.getObject(i)); } list.add(map); } } catch (SQLException e) { System.out.println("结果集解析出错:"+e.getMessage()); } finally { closeConn(); } return list; } /** * 更新(无参) * @param sql */ public int update(String sql){ int affectedLine = 0;//受影响的行数 conn = this.getConn(); try { st = conn.createStatement(); affectedLine = st.executeUpdate(sql); } catch (SQLException e) { System.out.println("更新(无参)失败:"+e.getMessage()); } finally { closeConn(); } return affectedLine; } /** * 更新 * @param sql * @param params * @return */ public int update(String sql,Object[] params){ int affectedLine = 0;//受影响的行数 conn = this.getConn(); try { ppst = conn.prepareStatement(sql); if(params!=null){ for(int i = 0;i<params.length;i++){ ppst.setObject(i+1, params[i]); } } affectedLine = ppst.executeUpdate(); } catch (SQLException e) { System.out.println("更新失败:"+e.getMessage()); } finally { closeConn(); } return affectedLine; } private void closeConn(){ if(rs!=null){ try { rs.close(); } catch (SQLException e) { System.out.println(e.getMessage()); } } if(st!=null){ try { st.close(); } catch (SQLException e) { System.out.println(e.getMessage()); } }     if(ppst!=null){ try { ppst.close(); } catch (SQLException e) { System.out.println(e.getMessage()); } } if(conn!=null){ try { conn.close(); } catch (SQLException e) { System.out.println(e.getMessage()); } } } }

 

 

 

四. 实验结果

测试需要在微信公众号网页里启用服务器, 然后根据我们服务器具体的实现一层一层的测试.

下图是第一次测试的结果, 我们可以看见还要很多的功能并未具体实现. 以及未到达预期的一些效果, 这时候我们就该回去继续修改我们服务器端的代码, 并继续测试直至实现自己想要的功能.

 

测试不全, 服务器开发完善后继续测试.

 

转载请注明原文地址: https://www.6miu.com/read-2621627.html

最新回复(0)