微信公众号开发 - 配置表设计以及接入公众号接口开发

微信公众号开发文章目录

1.微信公众号开发 - 环境搭建
2.微信公众号开发 - 配置表设计以及接入公众号接口开发
3.微信公众号开发 - token获取(保证同一时间段内只请求一次)
4.微信公众号开发 - 菜单按钮bean封装
5.微信公众号开发 - 创建菜单
6.微信公众号开发 - 事件处理和回复消息
7.微信公众号开发 - 发送Emoji表情

项目完整代码请访问github:https://github.com/liaozq0426/wx.git

微信公众号配置表设计

在公众号的开发过程中,很多接口都需要读取配置,如appId、appSecret、菜单配置等,因此在开发之前,配置表的设计尤为重要。创建表名为wx_cfg的配置表,建表语句如下

CREATE TABLE `wx_cfg` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `type` varchar(20) NOT NULL COMMENT '配置项类型',
  `name` varchar(20) NOT NULL COMMENT '配置项名称',
  `value` varchar(255) DEFAULT NULL COMMENT '配置项值',
  `parent_id` int(11) DEFAULT NULL COMMENT '父ID',
  `sort` tinyint(4) DEFAULT NULL COMMENT '排序',
  `platform` varchar(36) NOT NULL COMMENT '公众号标识',
  `wx_type` varchar(15) DEFAULT NULL COMMENT '公众号类型,service:服务号 , subscribe : 订阅号',
  `enabled` tinyint(1) DEFAULT '1' COMMENT '是否可用,1:可用,0:不可用',
  `desc` varchar(255) DEFAULT NULL COMMENT '描述信息',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

建表语句中每个字段含义都有注释说明,其中以下两个字段详细说明一下
1)parent_id,这个字段用于配置公众号菜单时,如果有多级菜单,那么子菜单就需要用parent_id字段标识父菜单是谁
2)platform,当同一配置表用于多公众号开发时,这个字段就可以用于区分不同公众号的配置
配置表创建好后,插入appId、appSecret、Token

INSERT INTO `wx_cfg` (`id`, `type`, `name`, `value`, `parent_id`, `platform`, `enabled`, `desc`) VALUES ('1', 'wx_token', 'wxToken', 'YmRjYjAyN2M1ZjMaYUzI1Mzg1ZmU1MGYaIzBY=', NULL, 'gavin', '1', NULL);
INSERT INTO `wx_cfg` (`id`, `type`, `name`, `value`, `parent_id`, `platform`, `enabled`, `desc`) VALUES ('2', 'wx_key', 'appId', 'd3hiODU5ODYwMDY0NmM1MKL1', NULL, 'gavin', '1', NULL);
INSERT INTO `wx_cfg` (`id`, `type`, `name`, `value`, `parent_id`, `platform`, `enabled`, `desc`) VALUES ('3', 'wx_key', 'appSecret', 'NjYzNzhkYTVlN2QxMDdhZmUyZTg3MTMwYjRlUGAZI=', NULL, 'gavin', '1', NULL);

注意
1)在开发的时候需要使用自己申请的微信公众号配置信息。
2)appId、appSecret、Token都使用了Base64加密。
插入完成后可以看一下此时wx_cfg表中已经有了三条记录
图1.基本配置

接入校验接口开发

接下来编写mapper来操作配置表,mapper接口中需要一个查询方法

package com.gavin.mapper;

import java.util.List;

import com.gavin.pojo.WxCfg;

public interface WxCfgMapper {
	public List<WxCfg> select(WxCfg wxCfg);
}

WxCfgMapper.xml实现如下

<mapper namespace="com.gavin.mapper.WxCfgMapper">
	<resultMap type="com.gavin.pojo.WxCfg" id="WxCfgMap">
		<id column="id" property="id" />
		<result column="type" property="type"  />
		<result column="name" property="name" />
		<result column="value" property="value" />
		<result column="parent_id" property="parentId" />
		<result column="platform" property="platform" />
		<result column="wx_type" property="wxType" />
		<result column="enabled" property="enabled" jdbcType="TINYINT" javaType="Boolean" />
		<result column="desc" property="desc" />
	</resultMap>

	<select id="select" parameterType="com.gavin.pojo.WxCfg" resultMap="WxCfgMap">
		select id
		,type
		,name
		,IF(type='menu' or type = 'url',concat((SELECT value FROM wx_cfg WHERE type = 'app_url' AND enabled = 1 AND platform = #{platform}) , value) , value) AS value   
		,parent_id
		,platform
		,wx_type
		,enabled
		,a.desc
		from wx_cfg a
		<trim prefix="where" suffixOverrides="and|AND|or|OR">
			<if test="id != null and id &gt; 0 ">
				id = #{id} and
			</if>
			<if test="type != null and type !='' ">
				type = #{type} and
			</if>
			<if test="name != null and name !='' ">
				name = #{name} and
			</if>
			<if test="parentId == null">
				parent_id is null and
			</if>
			<if test="parentId != null and parentId > 0">
				parent_id = #{parentId} and
			</if>
			<if test="platform != null and platform !='' ">
				platform = #{platform} and
			</if>
			<if test="enabled != null">
				enabled = #{enabled} and
			</if>
		</trim>
		<if test="orderBy != null and orderBy != '' ">
			order by ${orderBy}
		</if>
	</select>
</mapper>

接下来编写service接口,service接口中有三个方法,分别用来查询列表和单条记录,以及微信公众号的key信息

package com.gavin.service;

import java.util.List;

import com.gavin.pojo.Wechat;
import com.gavin.pojo.WxCfg;

public interface WxCfgService {
	List<WxCfg> select(WxCfg cfg) throws Exception;
	WxCfg selectOne(WxCfg cfg) throws Exception;
	Wechat selectWechat(String platform) throws Exception;
}

实现代码如下

package com.gavin.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gavin.cfg.WxCfgEnum;
import com.gavin.mapper.WxCfgMapper;
import com.gavin.pojo.Wechat;
import com.gavin.pojo.WxCfg;
import com.gavin.service.WxCfgService;

@Service
public class WxCfgServiceImpl implements WxCfgService{
	
	@Autowired
	private WxCfgMapper wxCfgMapper;

	/**
	 * @title 查询WxCfg集合
	 * @author gavin
	 * @date 2019年11月25日
	 */
	@Override
	public List<WxCfg> select(WxCfg cfg) throws Exception {
		return wxCfgMapper.select(cfg);
	}
	/**
	 * @title 查询WxCfg单条记录
	 * @author gavin
	 * @date 2019年11月25日
	 */
	@Override
	public WxCfg selectOne(WxCfg cfg) throws Exception {
		List<WxCfg> cfgList = select(cfg);
		if(cfgList.size() == 1)
			return cfgList.get(0);
		return null;
	}
	/**
	 * @title 查询微信公众号appId和appSecret
	 * @author gavin
	 * @date 2019年5月24日
	 * @param platform
	 * @return
	 * @throws Exception
	 */
	@Override
	public Wechat selectWechat(String platform) throws Exception {
		WxCfg cfgParam = new WxCfg();
		cfgParam.setPlatform(platform);
		cfgParam.setType(WxCfgEnum.WX_KEY_APPID.getType());
		List<WxCfg> cfgList = wxCfgMapper.select(cfgParam);
		
		Wechat wechat = new Wechat();
		for(WxCfg cfg : cfgList) {
			String name = cfg.getName();
			if(WxCfgEnum.WX_KEY_APPID.getName().equals(name)) {
				// 获取appId
				String appId = cfg.getValue();
				wechat.setAppId(appId);
			}else if(WxCfgEnum.WX_KEY_APPSECRET.getName().equals(name)) {
				// 获取appSecret
				String appSecret = cfg.getValue();
				wechat.setAppSecret(appSecret);
			}
		}
		return wechat;
	}
}

上述代码中使用到了一个枚举WxCfgEnum,代码如下

package com.gavin.cfg;

public enum WxCfgEnum {
	APP_URL("app_url" , "appUrl")
	,WX_TOKEN("wx_token" , "wxToken")
	,WX_KEY_APPID("wx_key" , "appId")
	,WX_KEY_APPSECRET("wx_key" , "appSecret")
	,MENU("menu")
	;
	private String type;	// 对应wx_cfg表中的type字段
	private String name;	// 对应wx_cfg表中的name字段
	private WxCfgEnum(String type , String name) {
		this.type = type;
		this.name = name;
	}
	private WxCfgEnum(String type) {
		this.type = type;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

接下来编写controller,实现接入接口

package com.gavin.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.gavin.cfg.WxAccessVo;
import com.gavin.service.WxCfgService;

@RestController
public class WxController {
	
	@Autowired
	private WxCfgService wxCfgService;
	
	@GetMapping("wxIn")
	public String wxIn(WxAccessVo accessVo) {
		try {
			return wxCfgService.wxInVerify(accessVo); 
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} 
	}
	
}

代码中用到的WxAccessVo定义如下,用于接收微信传过来的参数

package com.gavin.pojo;

/**
 * @title 公众号接口校验bean
 * @author gavin
 * @date 2019年11月25日
 */
public class WxAccessVo {

    private String signature;
    private String timestamp;
    private String nonce;
    private String echostr;
    private String platform;
    
    public String getPlatform() {
		return platform;
	}

	public void setPlatform(String platform) {
		this.platform = platform;
	}

	public String getSignature() {
        return signature;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }

    public String getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }

    public String getNonce() {
        return nonce;
    }

    public void setNonce(String nonce) {
        this.nonce = nonce;
    }

    public String getEchostr() {
        return echostr;
    }

    public void setEchostr(String echostr) {
        this.echostr = echostr;
    }

}

在WxCfgService新增wxInVerify接口

String wxInVerify(WxAccessVo accessVo) throws Exception;

实现代码如下

/**
 * @title 微信公众号接入认证
 * @author gavin
 * @date 2019年11月25日
 */
@Override
public String wxInVerify(WxAccessVo accessVo) throws Exception{
	 boolean result = false;
	if(accessVo == null)
		return null;
	String platform = accessVo.getPlatform();
	if (!StringUtils.isBlank(platform)) {
		// 从数据库中查询Token
           WxCfg wxCfg = new WxCfg();
           wxCfg.setEnabled(true);
           wxCfg.setType(WxCfgEnum.WX_TOKEN.getType());
           wxCfg.setPlatform(platform);
           WxCfg wxCfgA = this.selectOne(wxCfg);
           String token = wxCfgA.getValue();
           token = EncryptionUtil.base64Decode(token);
           // 校验签名
           result = WxUtil.verifySignature(accessVo.getSignature(), token, accessVo.getNonce(), accessVo.getTimestamp());
       }
	System.out.println(accessVo.getPlatform() + "配置微信公众号" + (result ? "成功" : "失败"));
	// 如果校验成功则返回echostr,否则返回空
	return result ? accessVo.getEchostr() : null;
}

其中签名校验封装成了WxUtil类的一个方法,verifySignature代码如下

/**
  * @title 微信公众号服务器配置校验
  * @param signatureWx
  * @param token
  * @param nonce
  * @param timestamp
  * @return
  */
 public static boolean verifySignature(String signatureWx , String token , String nonce , String timestamp) throws Exception{
     if(StringUtils.isBlank(signatureWx))
         return false;
     String[] arr = {token , nonce , timestamp};
     StringBuffer sb = new StringBuffer();
     // 排序
     Arrays.sort(arr);
     // 拼接字符串
     sb.append(arr[0]).append(arr[1]).append(arr[2]);
     // sha1算法加密
     String signature = DigestUtils.sha1Hex(sb.toString());
     return signatureWx.equals(signature);
 }

到这里,接入认证接口就开发完成了。接下来开启natapp内网穿透,登录到微信公众号后台,在基本配置中修改配置,在URL中输入刚刚开发好的wxIn接口,并带上platform参数,如下图

图2.提交基本配置
输入完成后,点击下方提交按钮,如果校验成功页面会弹出如下提示
图3.校验成功
如果校验失败页面会弹出如下提示
图4.校验失败

猜你喜欢

转载自blog.csdn.net/u012693016/article/details/103234919