微信公众号发送关键字领取CDK

写在前面

最近跟朋友搞了个网游公会,主体运作是《御龙在天》这款游戏,因为公会做了微信公众号,所以提出这个需求:用户在公众号发送关键字,然后领取CDK,每个用于不可重复领取。多方资料查阅,发现这方面的资料很少,所以就独立的自己研究开发了一下,不存在任何商业利益,纯粹个人的爱好和贡献。

原文地址(我的博客):http://zhuxinfeng.com/2019/08/12/wei-xin-gong-zhong-hao-fa-song-guan-jian-zi-ling-qu-cdk/#more

注册订阅号

前往微信公众平台注册,这里就不多说了。点击下面机票,go!

微信公众平台

微信公众号开发文档

开发前准备

服务器:阿里云或者华为云都可以,配置不需要很高

域名:已备案的域名,或者你的服务器没有其它服务,80端口给即将开发的程序用

Nginx:这个东西没什么好说的。
MySQL:数据库,用来存储CDK信息。

架构设计

涉及技术:Spring Boot、Spring Mvc、MyBatis、Druid、MySQL、Lombok、Dom4j、Xstream

Spring Boot、Spring Mvc、MyBatis不需要多说,经典的ssm架构;Druid是阿里爸爸的数据库连接池;Lombok是用来简化开发的框架,比如快速省略日志,无需生成Get/Set方法;Dom4j主要用来解析Xml消息;Xstream用来将消息转换为Xml;

新建项目,创建数据表

初始化一个Spring Boot项目就不在这提了,不会的点击下面机票,查看另外一篇文章;

初始化一个Spring Boot项目

创建数据表:

CREATE TABLE `cdk_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `cdk` varchar(50) NOT NULL DEFAULT '' COMMENT 'CDK',
  `type` varchar(50) DEFAULT '' COMMENT 'CDK类型',
  `open_id` varchar(50) DEFAULT '' COMMENT '微信的openid',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `modify_time` timestamp NULL DEFAULT NULL COMMENT '修改时间',
  `dr` int(10) DEFAULT '1' COMMENT '删除标识: [1 有效; 0 删除;]',
  PRIMARY KEY (`id`,`cdk`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='CDK信息表';

Maven依赖:


    org.springframework.boot
    spring-boot-starter



    org.springframework.boot
    spring-boot-starter-web



    org.mybatis
    mybatis
    3.4.6


    org.mybatis
    mybatis-spring
    1.3.2



    org.mybatis.spring.boot
    mybatis-spring-boot-starter
    1.3.2




    com.alibaba
    druid-spring-boot-starter
    1.1.9



    mysql
    mysql-connector-java
    5.1.46



    org.projectlombok
    lombok
    1.18.6
    provided




    dom4j
    dom4j
    1.6




    com.thoughtworks.xstream
    xstream
    1.4

创建微信消息实体类

@Getter
@Setter
@ToString
public class WeChatMessageBean implements Serializable {

    private static final long serialVersionUID = 1L;

    /** 开发者微信号 **/
    private String ToUserName;
    /** 发送方帐号(一个OpenID) **/
    private String FromUserName;
    /** 消息创建时间 (整型) **/
    private Long CreateTime;
    /** 消息类型,文本为text **/
    private String MsgType;
    /** 文本消息内容 **/
    private String Content;
    /** 消息id,64位整型 **/
    private String MsgId;
}

创建XML与JavaBean转换工具类

public class XmlConverterUtil implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 微信公众号使用:XML转换为Map
     * 使用dom4j进行xml读取
     *
     * @param request HTTP请求
     * @return 返回map
     * @date 2019/8/12 11:14
     */
    public static Map xmlToMap(HttpServletRequest request) throws Exception {
        Map map = new HashMap<>();
        SAXReader reader = new SAXReader();
        ServletInputStream inputStream = request.getInputStream();
        Document document = reader.read(inputStream);
        // 获取跟节点
        Element rootElement = document.getRootElement();
        // 获取根下所有节点
        List elements = rootElement.elements();
        for (Element element : elements) {
            map.put(element.getName(), element.getText());
        }
        // 关闭流
        inputStream.close();
        return map;
    }

    /**
     * 微信公众号使用:对象转换为XML
     *
     * @param weChatMessage 消息体
     * @return 返回字符串
     * @date 2019/8/12 11:18
     */
    public static String objectToXml(WeChatMessageBean weChatMessage){
        XStream xStream = new XStream();
        xStream.alias(Constants.MSG_TYPE,weChatMessage.getClass());
        return xStream.toXML(weChatMessage);
    }
}

开发服务器基本配置

登录微信公众平台后,基本配置 -> 服务器配置 -> 修改配置

URL:配置一个api接口,用来接收服务器推送的消息,url自定义,一会项目中用的接口

例如:http://wx.dbnewyouth.com/api/v1/cdk/msg

Token:Token不是校验用的access_token,是单独校验服务器的token,自定义即可,与服务器配置相同

EncodingAESKey:随机生成即可,因为是个人开发使用,所以消息加解密是明文,这个用处不大。

消息加解密方式:选择明文模式,个人开发完,就不去解密了。

微信公众号发送关键字领取CDK_第1张图片
服务器配置

接收公众号推送的消息

主要思路:接收公众号的消息推送 -> 解析xml消息体,识别消息类型 -> 处理text文本消息,其它消息直接返回success -> 获取xml消息体中的content内容,与设定的关键词进行对比,符合进行下一步,不符合直接返回success -> 获取FromUserName,其实就是openid,查询数据库中对应类型的cdk是否领取过,领取过返回消息,没领取过返回cdk消息 -> 封装xml消息体,返回。

PS:需要注意,在返回xml消息体的时候,要将接收的消息体FromUserNameToUserName相反设置,即将接收方设置为用户,发送方设置为开发者。

/**
  * 获取cdk
  *
  * @param signature 签名
  * @param timestamp 时间戳
  * @param nonce     随机数
  * @param echostr   密文
  * @param request   微信公众平台的Http请求
  * @return 返回xml数据或接收成功(success)
  * @date 2019/8/12 11:26
  */
@RequestMapping("/msg")
public String msg(@Param("signature") String signature, @Param("timestamp") String timestamp,
    @Param("nonce") String nonce, @Param(value = "echostr") String echostr, HttpServletRequest request) {
// 校验签名和token
if (StringUtil.isNotEmpty(echostr)) {
    logger.info("正在校验签名等参数");
    if (WeixinUtil.checkSignature(signature, timestamp, nonce)) {
        return echostr;
    }
    return "";
} else {
    try {
        // 将request中的xml信息转换为map
        Map map = XmlConverterUtil.xmlToMap(request);
        // 消息类型
        String msgType = map.get(Constants.MSG_TYPE_KEY);
        // 发送方账号openid
        String fromUserName = map.get(Constants.FROM_USER_NAME_KEY);
        // 开发者微信号
        String toUserName = map.get(Constants.TO_USER_NAME_KEY);
        // 用户发送的消息
        String content = map.get(Constants.CONTENT_KEY);
        // 标记数值
        String msgId = map.get(Constants.MSGID_KEY);
        logger.info("消息类型为:{} ,消息内容为:{}", msgType, content);
        // 如果是文本消息则进行如下处理
        if (msgType.equalsIgnoreCase(Constants.MSG_TYPE_VALUE)) {
            // 如果关键词不是“公测CDK”或者“经典CDK”,直接返回success
            if (!Constants.CDK_TYPE_GC_VALUE.equalsIgnoreCase(content) && !Constants.CDK_TYPE_JD_VALUE.equalsIgnoreCase(content)) {
                return Constants.DEFAULT_MSG;
            }
            // 通过content校验出cdk类型,GC或者JD
            String cdkType = checkCdkType(content);
            // 获取cdk
            String cdk = cdkInfoService.getCdk(fromUserName, cdkType);
            // 返回信息给用户
            WeChatMessageBean weChatMessage = new WeChatMessageBean();
            weChatMessage.setContent(cdk);
            weChatMessage.setMsgType(msgType);
            weChatMessage.setToUserName(fromUserName);
            weChatMessage.setFromUserName(toUserName);
            weChatMessage.setCreateTime(System.currentTimeMillis());
            weChatMessage.setMsgId(msgId);
            // 将消息实体转换为xml
            String xml = XmlConverterUtil.objectToXml(weChatMessage);
            logger.info("消息发送成功,时间:{}", getTimeStr());
            return xml;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    logger.info("消息发送成功,时间:{}", getTimeStr());
    return Constants.DEFAULT_MSG;
}
}

实现类:

@Override
public String getCdk(String openId,String cdkType){
    // 参数校验
    if(StringUtil.isBlank(openId)){
        return Constants.CDK_MSG_OPENID;
    }
    // 查询是否领取过
    CdkInfoBean params = new CdkInfoBean();
    params.setOpenId(openId);
    params.setType(cdkType);
    CdkInfoBean bean = cdkInfoMapper.queryByOpenId(params);
    // 如果是空 则该用户领取过CDK
    if(bean != null){
        return Constants.CDK_MSG_RECEIVED;
    }
    // 获取一个新的CDK,如果拿不到,说明没有cdk了。
    CdkInfoBean cdk = cdkInfoMapper.queryByAsc(cdkType);
    if(cdk == null){
        return Constants.CDK_MSG_NULL;
    }
    cdk.setOpenId(openId);
    // 更新为使用状态
    cdkInfoMapper.update(cdk);
    // 返回cdk信息
    return Constants.CDK_MSG_RECEIVE + cdk.getCdk();
}

常量类:

public class Constants {

    /** 消息类型 **/
    public static final String MSG_TYPE = "xml";

    /** 微信公众平台配置的Token **/
    public static final String WECHAT_TOKEN = "xxxxxxxx";

    /** 发送消息的(open_id) **/
    public static final String FROM_USER_NAME_KEY ="FromUserName";
    
    /** 我方公众号 **/
    public static final String TO_USER_NAME_KEY="ToUserName";
    
    /** 创建消息时间 **/
    public static final String CREATE_TIME_KEY="CreateTime";
    
    /** 创建消息时间 **/
    public static final String CONTENT_KEY="Content";
    
    /** 当前消息类型 **/
    public static final String MSG_TYPE_KEY = "MsgType";
    public static final String MSG_TYPE_VALUE = "text";
    
    /** 当前语音格式已知的有amr MP3 **/
    public static final String FORMAT_KEY = "Format";
    
    /** 消息ID **/
    public static final String MSGID_KEY = "MsgId";
    
    /** CDK的类型 **/
    public static final String CDK_TYPE_GC_KEY = "GC";
    public static final String CDK_TYPE_JD_KEY = "JD";

    /** 领取CDK的关键词 **/
    public static final String CDK_TYPE_GC_VALUE = "公测CDK";
    public static final String CDK_TYPE_JD_VALUE = "经典CDK";
    
    /** 重复领取 **/
    public static final String CDK_MSG_RECEIVED = "您已经领取过CDK啦!";
    /** CDK前缀 **/
    public static final String CDK_MSG_RECEIVE = "感谢您关注Yy3191明月网游,CKD为:";
    /** CDK库存不足 **/
    public static final String CDK_MSG_NULL = "很抱歉,CDK没有啦,请去Yy3191联系管理吧!";
    /** 领取出现异常 **/
    public static final String CDK_MSG_OPENID = "领取出现异常,请去Yy3191联系管理吧!";

    /** 默认回复消息,如果不是你想回复的消息,默认回复success,防止公众号5s重试 **/
    public static final String DEFAULT_MSG = "success";
}

实际效果测试

微信公众号发送关键字领取CDK_第2张图片
效果测试

源码地址

点击下方蓝色飞机票,失效联系我

公众号发送关键字自动发放CDK源码

你可能感兴趣的:(微信公众号发送关键字领取CDK)