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

一. 开发准备

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

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());
			}
		}
	}

}

四. 实验结果

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

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

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

猜你喜欢

转载自blog.csdn.net/qq_37832932/article/details/80629661
今日推荐