微信公众号开发--服务号

前言

因公司需要开发一款手机打卡程序,本人没有安卓APP开发经验,所以决定将写个服务号的公众号,集外出打卡,打卡查询等功能;

 

一,开发前测试帐号申请

以下是官方给出的建议,大家可以多参考参考

1)如果想简单的发送消息,达到宣传效果,建议可选择订阅号;
2)如果想用公众号获得更多的功能,例如开通微信支付,建议可以选择服务号;
3)如果想用来管理内部企业员工、团队,对内使用,可申请企业号;
4)订阅号可通过微信认证资质审核通过后有一次升级为服务号的入口,升级成功后类型不可再变;
5)服务号不可变更成订阅号。

申请地址:

http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

 

二,选择natapp进行内网外穿,为什么?这是因为自制程序可以连外网,但怎样让微信服务器与你的自制程序通信?那只能把自己的程序也设置为外网,但用传统的打包布署发布到LINUX?那将会对开发极为不便。natapp就是解决我们可以直接将tomcat设置为内网外穿,这样每次开发完,直接运行,外网就可以找到你这个内网外穿的外网。我个人喜好natapp,当然也有其它优秀的内网外穿工具,可自行选择。

(1). 下载
在以下地址:https://natapp.cn/,进行下载,打开链接之后,选择客户端下,如下图:

 

图1:客户端下载
我在这里下载的是windows64位。
(2). 注册并登录
同时还需要进行注册,(注册过程不详述)并进行登陆。登陆后的页面如下图:

 

图2:登录成功后的页面
(3). 购买隧道
此时我们需要进行购买隧道,如下图。

图3:购买隧道
如果是自己的项目可以使用免费隧道。不过我推荐大家使用VIP_2型,根据自己的需要进行选择。我使用的是付费隧道。(如下图),我购买是一起花了13元,建议购买前去网上找优惠码,可便宜2元。然后建议不要买太便宜的,哈哈。

图4:隧道购买成功
(4). 配置已购买隧道
下面是购买成功后,我的隧道,我之前申请了一个免费隧道,一个付费隧道,所以我的里面有两个隧道。如下图

选择第二个隧道中的配置按钮,进行隧道配置。打开如图所示:

图5:配置已购买隧道
此时为止,natapp的配置已完成。

(5). 启动natapp
新建config.ini文件,存储在下载的客户端同一个目录下。该文件中需要添加内容如下

[default]
authtoken= 6fdb788dce71d7e2
clienttoken= #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

双击natapp程序启动成功如下图所示:


至此,大功告成.

 

三.在微信平台进行配置.

完成上面第一步的注册后,即下面这个网址.

http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

说明一下.

 appid:是公众号开发识别码,配合开发者密码可调用公众号的接口能力。
 appsecret:是校验公众号开发者身份的密码,具有极高的安全性。

再往下看,我们会看到URL和Token这两个属性,和上面appid/appsecret不同的是,上面的是微信分配给我们的,但是下面这两个是需要我们填进去的。

我们先来了解一下,这两个属性有什么作用。

URL:就是指我们自己的服务器地址
该URL是开发者用来接收和响应微信消息和事件的接口URL
(必须以http://或https://开头,分别支持80端口和443端口,这就是为什么我刚刚第二步说要进行内网外穿,哈哈)

Token:可任意填写,用作生成签名(必须为英文或数字,长度为3-32字符)
该签名在后边会用到,这里暂时随便填个内容也可以

接下来我们需要了解的是微信与我们的服务器交互的过程:

当我们在微信app上,给公众号发送一条内容的时候,实际会发送到微信的服务器上,此时微信的服务器就会对内容进行封装成某种格式的数据比如xml格式,再转发到我们配置好的URL上,所以该URL实际就是我们处理数据的一个请求路径。所以该URL必须是能暴露给外界访问的一个公网地址,不能使用内网地址,生产环境可以申请腾讯云,阿里云服务器等,但是在开发环境中可以暂时利用一些软件来完成内网穿透,便于修改和测试,如NATAPP,花生壳等软件,使用起来也很方便,在本地安装对应的软件,配置运行后,直接使用软件分配的临时域名来访问本地应用即可,只是偶尔会存在网络不稳定的情况。这里不详细介绍如何使用了,具体教程可参考软件官网。

在开发的过程中,我们会经常使用到微信公众号提供给开发者的开发文档
具体地址:https://mp.weixin.qq.com/wiki
大家打开后可以选择”接入指南”,参考微信提供的一些帮助信息。

四.正式开发

(1),URL接入

我们需要先来了解一下接入的过程是怎么样的。下图是微信官方对接入过程的介绍。

由以上介绍可知,当我们填入url与token的值,并提交后,微信会发送一个get请求到我们填写的url上,并且携带4个参数,而signature参数结合了开发者填写的token参数和请求中的timestamp参数、nonce参数来做的加密签名,我们在后台需要对该签名进行校验,看是否合法。实际上,我们发现微信带过来的4个参数中并没有带token参数,仅有signature是和token有关的,所以我们应该在本地应用中也准备一个和填入的token相同的参数,再通过微信传入的timestamp与nonce做相同算法的加密操作,若结果与微信传入的signature相同,即为合法,则原样返回echostr参数,代表接入成功,否则不做处理,则接入失败。

我这里是使用的springboot+springmvc+mybaties

1.首先新建一个servlet,为什么是servlet而不是controller?因为servlet支持一个入口对应两个方法,即get post.开发到后面你就会明白这个意义.

 

package com.cosun.cosunp.weixin;

import com.cosun.cosunp.service.IPersonServ;
import com.cosun.cosunp.tool.Constants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import javax.servlet.ServletConfig;
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 java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author:homey Wong
 * @Date: 2019/9/11 上午 9:03
 * @Description:
 * @Modified By:
 * @Modified-date:
 */
@WebServlet(urlPatterns = "/weixin/hello")
public class WeiXinServlet extends HttpServlet {

    private static Logger logger = LogManager.getLogger(WeiXinServlet.class);

    @Autowired
    IPersonServ personServ;

    public static final String tooken = "homeyhomeyhomey";
    private static JedisPool pool;
    private static Jedis jedis;


    public WeiXinServlet() {
        //空构造函数
    }


    @Override
    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 outPW = null;
        try {
            outPW = response.getWriter();
            if (CheckUtil.checkSignature(signature, timestamp, nonce, tooken)) {
                System.out.println("签名成功!");
                outPW.write(echostr);
            } else {
                System.out.println("签名失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            outPW.close();
        }
    }
}

工具类CheckUtil

package com.cosun.cosunp.weixin;

import java.security.MessageDigest;
import java.util.Arrays;

/**
 * @author:homey Wong
 * @Date: 2019/9/11 0011 上午 9:10
 * @Description:
 * @Modified By:
 * @Modified-date:
 */
public class CheckUtil {
   
    public static boolean checkSignature(String singnature, String timestamp, String nonce, String tooken) {
        String[] arr = {tooken, timestamp, nonce};
        Arrays.sort(arr);
        StringBuilder sb = new StringBuilder();
        for (String s : arr) {
            sb.append(s);
        }
        String temp = getSha1(sb.toString());
        return temp.equals(singnature);
    }

    public static String getSha1(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }

        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f'};

        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");

            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;

            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }

            return new String(buf);

        } catch (Exception e) {
            return null;

        }
    }


}

可以拿你的手机测试一下.OK后,如下,可以互动了

 

@WebServlet(urlPatterns = "/weixin/hello")
public class WeiXinServlet extends HttpServlet {

    private static Logger logger = LogManager.getLogger(WeiXinServlet.class);

    @Autowired
    IPersonServ personServ;

    public static final String tooken = "homeyhomeyhomey";
    private static JedisPool pool;
    private static Jedis jedis;
 
@Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        pool = new JedisPool(new JedisPoolConfig(), "127.0.0.1");
        jedis = pool.getResource();
        System.out.println(jedis.get(Constants.accessToken));
        System.out.println(jedis.get(Constants.jsapi_ticket));
        try {
            // https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd5109277d8902606&redirect_uri=http://homey.nat100.top/weixin/getMobileLocate&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect

            PrintWriter out = response.getWriter();
            Map<String, String> map = WeiXinUtil.xmlToMap(request);
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            String msgType = map.get("MsgType");
            String content = map.get("Content");
            String key = map.get("EventKey");
            String message = null;
            StringBuilder returnMes = new StringBuilder();
            if ("event".equals(msgType)) {
                if (clickquery.equals(key)) {
                    List<OutClockIn> allOutClockIn = personServ.findAllOutClockInByOpenId(fromUserName);
                    if (allOutClockIn.size() > 0) {
                        for (OutClockIn on : allOutClockIn) {
                            returnMes.append(on.getClockInDateStr() + ":");
                            returnMes.append(on.getClockInDateAMOnStr() + ",");
                            returnMes.append(on.getClockInAddrAMOn() + ",");
                            if (on.getAmOnUrl() != null && on.getAmOnUrl().trim().length() > 0) {
                                returnMes.append("上午已摄像.");
                            } else {
                                returnMes.append("上午还未摄像.");
                            }
                            returnMes.append(on.getClockInDatePMOnStr() + ",");
                            returnMes.append(on.getClockInAddrPMOn() + ".");
                            if (on.getPmOnUrl() != null && on.getPmOnUrl().trim().length() > 0) {
                                returnMes.append("下午已摄像.");
                            } else {
                                returnMes.append("下午还未摄像.");
                            }
                            returnMes.append(on.getClockInDateNMOnStr() + ",");
                            returnMes.append(on.getClockInAddNMOn() + ".");
                            if (on.getNmOnUrl() != null && on.getNmOnUrl().trim().length() > 0) {
                                returnMes.append("晚上已摄像.");
                            } else {
                                returnMes.append("晚上还未摄像.");
                            }
                        }
                    } else {
                        returnMes.append("暂无考勤信息");
                    }
                    InMsgEntity text = new InMsgEntity();
                    text.setFromUserName(toUserName); //原来的信息发送者,将变成信息接受者
                    text.setToUserName(fromUserName); //原理的接受者,变成发送者
                    text.setMsgType("text"); //表示消息的类型是text类型
                    text.setCreateTime(new Date().getTime());
                    text.setContent("您的考勤信息是:" + returnMes);
                    message = WeiXinUtil.textMessageToXml(text); //装换成 xml 格式发送给微信解析
                }
            } else if ("text".equals(msgType)) {
                InMsgEntity text = new InMsgEntity();
                text.setFromUserName(toUserName); //原来的信息发送者,将变成信息接受者
                text.setToUserName(fromUserName); //原理的接受者,变成发送者
                text.setMsgType("text"); //表示消息的类型是text类型
                text.setCreateTime(new Date().getTime());
                text.setContent("您发送的信息是:" + content);
                message = WeiXinUtil.textMessageToXml(text); //装换成 xml 格式发送给微信解析
            }
            out.print(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 public void setRedisValue(AccessToken accessToken) {
        // 初始化Redis连接池
        pool = new JedisPool(new JedisPoolConfig(), "127.0.0.1");
        jedis = pool.getResource();
        jedis.set(Constants.accessToken, accessToken.getAccessToken());
        jedis.set(Constants.expiresin, accessToken.getExpiresin() + "");
        //jedis.set(Constants.jsapi_ticket, accessToken.getJsapi_ticket());
    }


至此微信公众号的接入就已经完成了,后继可以进行功能开发啦.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值