微信公众号开发之"你问我答"demo

微信开发

微信的开发主要是两个平台:微信公众平台和微信开放平台;基于这两个不同的平台可以开发不同的需求

  • 微信公众平台 开发服务号、订阅号、小程序、企业微信

  • 微信开放平台 开发移动应用、网站应用、公众账号、第三方平台

两个服务平台的区别:

  1. 微信公众平台面向的是普通的用户,比如自媒体和媒体,企业官方微信公众账号运营人员使用,当然你所在的团队或者公司有实力去开发一些内容,也可以调用公众平台里面的接口,比如自定义菜单,自动回复,查询功能。目前大多数微信在认证之后,都在做这个事情。
  2. 开放平台面向的是开发者和第三方独立软件开发商。开发者或软件开发商通过微信开放提供的平台和接口,可以开发适合企业的电子商务网站,比如微信登录,微信支付等。

公众号开发实战

准备工具

  1. 内网穿透工具:NATAPP
  2. 微信公众订阅号一个:订阅号

内网穿透

问题一:什么是内网穿透?

内网穿透简单来说就是将内网外网通过natapp隧道打通,让内网的数据让外网可以获取。比如常用的办公室软件等,一般在办公室或家里,通过拨号上网,这样办公软件只有在本地的局域网之内才能访问,那么问题来了,如果是手机上,或者公司外地的办公人员,如何访问到办公软件呢?这就需要natapp内网穿透工具了。运行natapp隧道之后,natapp会分配一个专属域名/端口,办公软件就已经在公网上了,在外地的办公人员可以在任何地方愉快的访问办公软件了~~

问题二:能干什么?

  1. 上文举例的办公软件

  2. 放在家里的树莓派,服务器等,需要远程ssh管理,这样打通服务器的22端口即可远程通过ssh操作服务器了.

  3. 微信/支付宝等本地开发.现在微信/支付宝等应用,需要服务器接收微信/支付宝发送的回调信息,然而在本地开发程序的话,还得实时上传到服务器,以便支持微信/支付宝的回调信息,如果使用了natapp内网穿透软件,将回调地址设置成natapp提供的地址,回调数据立即传递回本地,,这样很方便的在本地就可以实时调试程序,无须再不断上传服务器等繁琐且无意义的步骤.

  4. 一些企业内部数据库,由于安全等原因,不愿意放到云服务器上,可以将数据库放到办公室本地,然后通过natapp的tcp隧道映射,这样既保证安全,又保证公网可以正常访问.

  5. 一些开发板做的监控等信息,每台设备运行一条隧道,可以方便的管理监控各个设备的运行情况.

  6. 一些本地运行的游戏,想和好基友一起联网玩,一条命令运行natapp即可实现联网游戏.

  7. 群辉上运行natapp之后,随时随地在任何地方可以访问到群辉上应用

问题三:如何使用?

  1. 注册登录natapp网站,先下载natapp,放到本地
  2. 在网站上创建自己的免费隧道,并将端口映射到本地的80端口上(微信平台的要求)
  3. 记录下自己的 authtoken
  4. 在本地创建一个bat脚本,内容是natapp的启动命令natapp -authtoken=ad6fa1b675****
  5. 查看映射的域名,值得注意的是这个域名会隔一段时间变化一次

本地开发:

步骤一:创建SpringBoot项目

该项目依赖于SpringBoot的2.0.6.RELEASE版本

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

导入依赖,其中注释了的一个依赖是关于Spring配置文件的,可加可不加

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <!--<dependency>-->
            <!--<groupId>org.springframework.boot</groupId>-->
            <!--<artifactId>spring-boot-configuration-processor</artifactId>-->
            <!--<optional>true</optional>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.codehaus.xfire</groupId>
            <artifactId>xfire-core</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.60</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>
    </dependencies>

配置application.yml文件(其中的token是自己定义的;appid和sceret需要在公众号平台查看;tokenUrl是公众号平台提供的获取access-token的接口),暂时先把选项添加上

server:
  port: 80
spring:
  application:
    name: hello
data:
  wechat:
    token: admin
    appid: wx9f1c0f01*****
    sceret: 53275ff4638a0e32d4436bd9e*****
    tokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${data.wechat.appid}&secret=${data.wechat.sceret}

创建配置类

创建controller类,来和公众号平台获取连接,值得注意的是,本地开发提供一个Get请求接口

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private WeChatUtils weChatUtils;

    @GetMapping("/check")
    public String checkLink(@RequestParam String signature,
                            @RequestParam String timestamp,
                            @RequestParam String nonce,
                            @RequestParam String echostr
                            ){
        return weChatUtils.checkLink(signature, timestamp, nonce, echostr);

    }
}

WeChatUtils工具类中提供一个checkLink方法

    public String checkLink(String signature,String timestamp,String nonce,String echostr){
        //1)将token、timestamp、nonce三个参数进行字典序排序
        String[] arr = {weChatConfig.getToken(),timestamp,nonce};
        Arrays.sort(arr);
        //2)将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuilder stringBuilder = new StringBuilder();
        for (String s:arr) {
            stringBuilder.append(s);
        }
        String sha1 = getSha1(stringBuilder.toString());
        //3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        if (signature.equals(sha1)) {
            return echostr;
        }else {
            return "";
        }
    }

步骤二:订阅号配置

  1. 登录公众号开发平台,点击跳转至链接
  2. 获取appid及secret;并将这两个数据填入application.yml
  3. 设置本机IP白名单( 在IP白名单内的IP来源,获取access_token接口才可调用成功)
  4. 启动本地SpringBoot项目,并且natapp保持开启
  5. 在左侧<开发>菜单项下的<基本配置>中设置好内网穿透的URL(能够提供checkLink校验的)、Token(本地项目设置的)等;点击提交,提示通过就可继续开发。如果没有校验通过,看看自己的natapp的域名是否发生变化;还有就是后台接口是否正确。
  6. 至此算是本地服务器与微信服务器建立了连接

步骤三:自定义消息回复

在本地服务中校验连接的controller中,添加一个支持Post请求的接口,并且该接口的url和校验的一致;其功能就是能够解析微信服务器传过来的一个xml数据,并根据msgType类型进行响应

    @PostMapping(value = "/check")
    public String messageMonitor(HttpServletRequest request) {
        return service.message(request);
    }

开发MessageService,下面给出代码片段

 @Override
    public String message(HttpServletRequest request){
        Map<String, String> map = null;
        try {
            map = utils.xmlToMap(request);
            HashMap<String, String> hashMap = new HashMap<>();
            hashMap.put(TO_USER_NAME,map.get(FROM_USER_NAME));
            hashMap.put(FROM_USER_NAME,map.get(TO_USER_NAME));
            hashMap.put(CREATETIME,String.valueOf(System.currentTimeMillis()));
            String msgType = map.get(MSGTYPE);
            String message = "success";
            String content = "";
            String mediaId = "";
            switch (msgType) {
                case "text" :
                    content = map.get(CONTENT);
                    if (content.contains("笑话")) {
                        hashMap.put(MSGTYPE,"voice");
                        hashMap.put("MediaId",luoSmartReply.voiceReply(content));
                    }else if (content.contains("图片")) {
                        hashMap.put(MSGTYPE,"image");
                        hashMap.put("MediaId",luoSmartReply.pictureReply(content));
                    }else {
                        hashMap.put(MSGTYPE,msgType);
                        hashMap.put(CONTENT,luoSmartReply.textReply(content));
                    }
                    try {
                        LOGGER.info("{} success",content);
                        message = utils.mapToXml(hashMap);
                        break;
                    } catch (IOException | DocumentException e) {
                        e.printStackTrace();
                    }
                case "image":
                    hashMap.put(MSGTYPE,msgType);
                    mediaId = map.get("MediaId");
                    hashMap.put("MediaId",mediaId);
                    try {
                        message = utils.mapToXml(hashMap);
                        break;
                    } catch (IOException | DocumentException e) {
                        e.printStackTrace();
                    }
                case "voice":
                    hashMap.put(MSGTYPE,msgType);
                    mediaId = map.get("MediaId");
                    hashMap.put("MediaId",mediaId);
                    try {
                        message = utils.mapToXml(hashMap);
                        break;
                    } catch (IOException | DocumentException e) {
                        e.printStackTrace();
                    }
                default:
                    break;
            }
            return message;
        } catch (IOException | DocumentException e) {
            e.printStackTrace();
        }
        return "success";

    }

除此之外还需要提供能够获取access_token的接口,该接口需要向微信提供的接口发送Get请求,下面是一个get请求方法。当我们能够获取的access_token的时候,就可以进行下一步操作了。

    public static Object get(String uri) {
        HttpGet httpGet = new HttpGet(uri);
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            String res = EntityUtils.toString(entity,DEFAULT_ENCODING);
            return JSONObject.parse(res);
        } catch (IOException e) {
            LOGGER.error(e.getMessage());
            return null;
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                LOGGER.error(e.getMessage());
            }
        }
    }

在微信的debug平台,进行本地服务的api测试,后台是否能够支持;根据返回值判断

当本地服务写好启动之后,还需要在微信平台启动该服务配置

demo代码:码云

本地测试:

posted @ 2020-03-13 13:41  春刀c  阅读(396)  评论(0编辑  收藏  举报