JAVA微信公众号

2023-11-13

1 微信公众号介绍

账号分为服务号订阅号、小程序

服务号和订阅号开发类似,但是申请服务号必须是企业,所以学习的话申请一个订阅号+测试账号即可

2 注册订阅号

第一步:访问:微信公众平台 点击立即注册按钮

第二步:注册类型页面选择订阅号

第三步:填写相关信息,点击注册即可

3 注册测试号

因为订阅号的接口权限是有限的,为了熟悉更多的微信公众号接口,所以需要申请一个测试号。

第一步:用注册的订阅号登录

第二步:在目录中【设置与开发】—>【开发者工具】下选择公众平台测试账号,点击进入后申请即可。

申请成功之后,就可以配置相关信息进行开发了,具体怎么配置后面再解释

4 搭建开发环境

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- 阿里云小蜜-自动回复机器人 -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-chatbot</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.2</version>
        </dependency>
        <!-- xml操作相关依赖 -->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!-- 阿里json解析 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>
        <!-- 这个是编码解码的 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>


<!--httpClient需要的依赖-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>
<!--//httpclient缓存-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient-cache</artifactId>
    <version>4.5</version>
</dependency>
<!--//http的mime类型都在这里面-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.3.2</version>
</dependency>


    </dependencies>

开发接入

接入之后微信服务器和我们自己的项目就接通了。那么如何接入呢?

接入的官方文档

 

上图中的url就是自己电脑的项目

点击上图的提交按钮之后,微信会向上图中的url发送一个get请求,请求参数如下:

参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串
  • 第二步:编写代码校验,用代码实现下面的逻辑

1)将token、timestamp、nonce三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信,如果比对成功,请原样返回echostr参数内容

需要用到的加解密工具类


import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SecurityUtil {
    protected static char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
    protected static MessageDigest messageDigest = null;
    protected static String convert="SHA1";

    static{
        try{
            // 拿到一个MD5转换器(如果想要SHA1参数换成”SHA1”)
            messageDigest = MessageDigest.getInstance(convert);
        }catch (NoSuchAlgorithmException e) {
            System.err.println(SecurityUtil.class.getName()+"初始化失败,MessageDigest不支持MD5Util.");
            e.printStackTrace();
        }
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {

        char c0 = hexDigits[(bt & 0xf0) >> 4];
        char c1 = hexDigits[bt & 0xf];
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    /**
     * 字符串的md5加密
     * @param input
     * @return
     */
    public static String stringMD5(String input) {
        // 输入的字符串转换成字节数组
        byte[] inputByteArray = input.getBytes();
        // inputByteArray是输入字符串转换得到的字节数组
        messageDigest.update(inputByteArray);
        // 转换并返回结果,也是字节数组,包含16个元素
        byte[] resultByteArray = messageDigest.digest();
        // 字符数组转换成字符串返回
        return bufferToHex(resultByteArray);
    }
    /**
     * 文件的md5加密
     * @param inputFile
     * @return
     * @throws IOException
     */
    public static String fileMD5(String inputFile) throws IOException {
        // 缓冲区大小(这个可以抽出一个参数)
        int bufferSize = 256 * 1024;
        FileInputStream fileInputStream = null;
        DigestInputStream digestInputStream = null;
        try {
            // 使用DigestInputStream
            fileInputStream = new FileInputStream(inputFile);
            digestInputStream = new DigestInputStream(fileInputStream,messageDigest);
            // read的过程中进行MD5处理,直到读完文件
            byte[] buffer =new byte[bufferSize];
            byte[] resultByteArray =null;
            while (digestInputStream.read(buffer) > 0) {
                // 获取最终的MessageDigest
                messageDigest= digestInputStream.getMessageDigest();
                // 拿到结果,也是字节数组,包含16个元素
                resultByteArray= messageDigest.digest();
                // 同样,把字节数组转换成字符串
            }
            return bufferToHex(resultByteArray);
        } finally {
            try {
                digestInputStream.close();
            } catch (Exception e) {
            }
            try {
                fileInputStream.close();
            } catch (Exception e) {
            }
        }
    }


}

微信调用的接口

    /**
     *  进行公众号开发配置,联通服务器与微信
     * @return
     */
    @GetMapping("/getWXSign")
    public String getWXSign(String timestamp,String nonce,String echostr,String signature) {
        String token = "120";  //token 必须跟自己设置的一致
//        1)将token、timestamp、nonce三个参数进行字典序排序
        String[] arr={token,timestamp,nonce};
        Arrays.sort(arr);
//    2)将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuilder  scs=new StringBuilder();
        for (String temp:arr){
            scs.append(temp);
        }
//获得自己的加密
        String mySignature=  SecurityUtil.stringMD5(scs.toString());
//    3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        if(mySignature.equals(signature)){
            System.out.println("接入成功");
            return echostr;
        }

        System.out.println("接入失败");
        return null;
    }

 

★access token的获取

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存.access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效

access token文档

目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token

总结:调用很多接口需要access_token,获取access_token之后需要保存起来,过期了再重新获取,而不是每次都重新获取。

接口调用请求说明

https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

参数说明

参数 是否必须 说明
grant_type 获取access_token填写client_credential
appid 第三方用户唯一凭证
secret 第三方用户唯一凭证密钥,即appsecret

返回说明

正常情况下,微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

参数说明

参数 说明
access_token 获取到的凭证
expires_in 凭证有效时间,单位:秒

★封装请求工具类

因为需要发送请求给微信服务器,所以需要有请求的工具类。罗老师用的是java自带的请求类,相对来说比较繁琐。所以我这里采用的是Apache HttpClient,这个用起来更加的简单。

基于Apache HttpClient封装HttpUtils工具类,我封装了4个方法,可以支持get请求和post请求。后面很多需要用的地方直接调用即可。

 

public class HttpUtils {

    public static void main(String[] args) {
        // 1.测试get请求
        /*
         String getUrl = "http://localhost:8080/user/searchPage?pageNum=1&pageSize=2";
         System.out.println(sendGet(getUrl));
         */
        
        // 2.测试post请求 携带x-www-form-urlencoded数据格式
        /*String postUrlForm = "http://localhost:8080/user";
        Map paramMap = new HashMap();
        paramMap.put("name", "杰克");
        paramMap.put("age", "20");
        paramMap.put("gender", "1");
        System.out.println(sendPost(postUrlForm, paramMap));*/
        
        //3.测试post请求 携带json数据格式
        /*String postUrlJson = "http://localhost:8080/user";
        String jsonParam = "{\"name\":\"jack\",\"age\":\"18\",\"gender\":\"2\"}";
        System.out.println(sendPost(postUrlJson,jsonParam));*/
        
        //4 测试post 携带文件
        String postUrlFile = "http://localhost:8080/user/upload";
        Map paramMap = new HashMap();
        paramMap.put("name", "tom");
        String localFile = "d:\\logo.png";
        String fileParamName = "file";
        System.out.println(sendPost(postUrlFile, paramMap,localFile,fileParamName));
    }

    // 1.httpClient发送get请求
    public static String sendGet(String url) {
        String result = "";
        CloseableHttpResponse response = null;
        try {
            // 根据地址获取请求
            HttpGet request = new HttpGet(url);// 这里发送get请求
            // 获取当前客户端对象
            CloseableHttpClient httpClient = HttpClients.createDefault();
            // 通过请求对象获取响应对象
            response = httpClient.execute(request);
            // 判断网络连接状态码是否正常(0--200都数正常)
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                result = EntityUtils.toString(response.getEntity(), "utf-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    // 2.httpClient发送post请求 携带x-www-form-urlencoded数据格式
    public static String sendPost(String url, Map<String, String> map) {
        CloseableHttpResponse httpResponse = null;
        String result = "";
        try {
            // 1、创建一个httpClient客户端对象
            CloseableHttpClient httpClient = HttpClients.createDefault();
            // 2、创建一个HttpPost请求
            HttpPost httpPost = new HttpPost(url);
            // 设置请求头
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); // 设置传输的数据格式
            // 携带普通的参数params的方式
            List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
            Set<String> keys = map.keySet();
            for (String key : keys) {
                params.add(new BasicNameValuePair(key, map.get(key)));
            }
            String str = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8));
            // 这里就是:username=kylin&password=123456
            System.out.println(str);

            // 放参数进post请求里面 从名字可以知道 这个类是专门处理x-www-form-urlencoded 添加参数的
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));

            // 7、执行post请求操作,并拿到结果
            httpResponse = httpClient.execute(httpPost);
            // 获取结果实体
            HttpEntity entity = httpResponse.getEntity();
            if (entity != null) {
                result = EntityUtils.toString(entity, "UTF-8");
            } else {
                EntityUtils.consume(entity); 如果entity为空,那么直接消化掉即可
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != httpResponse) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    // 3.httpClient发送post请求 携带json数据格式
    public static String sendPost(String url, String jsonStr) {
        CloseableHttpResponse httpResponse = null;
        String result = "";
        try {
            // 1.创建httpClient
            CloseableHttpClient httpClient = HttpClients.createDefault();
            // 2.创建post请求方式实例
            HttpPost httpPost = new HttpPost(url);

            // 2.1设置请求头 发送的是json数据格式
            httpPost.setHeader("Content-type", "application/json;charset=utf-8");
            httpPost.setHeader("Connection", "Close");

            // 3.设置参数---设置消息实体 也就是携带的数据
            /*
             * 比如传递: { "username": "aries", "password": "666666" }
             */
            //String jsonStr = " {\"username\":\"aries\",\"password\":\"666666\"}";
            StringEntity entity = new StringEntity(jsonStr.toString(), Charset.forName("UTF-8"));
            entity.setContentEncoding("UTF-8"); // 设置编码格式
            // 发送Json格式的数据请求
            entity.setContentType("application/json");
            // 把请求消息实体塞进去
            httpPost.setEntity(entity);

            // 4.执行http的post请求
            // 4.执行post请求操作,并拿到结果
            httpResponse = httpClient.execute(httpPost);
            // 获取结果实体
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                result = EntityUtils.toString(httpEntity, "UTF-8");
            } else {
                EntityUtils.consume(httpEntity); 如果httpEntity为空,那么直接消化掉即可
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != httpResponse) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    // 4.httpClient发送post请求 携带文件
    public static String sendPost(String url, Map<String, String> map,String localFile, String fileParamName) {
        HttpPost httpPost = new HttpPost(url);
        CloseableHttpClient httpClient = HttpClients.createDefault();
        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 把文件转换成流对象FileBody
            FileBody bin = new FileBody(new File(localFile));

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();

            // 相当于<input type="file" name="fileParamName"/> 其中fileParamName以传进来的为准
            builder.addPart(fileParamName, bin);
            // 相当于<input type="text" name="userName" value=userName>
            /*builder.addPart("filesFileName",
                    new StringBody(fileParamName, ContentType.create("text/plain", Consts.UTF_8)));*/
            if (map != null) {
                for (String key : map.keySet()) {
                    builder.addPart(key,
                            new StringBody(map.get(key), ContentType.create("text/plain", Consts.UTF_8)));
                }
            }
            HttpEntity reqEntity = builder.build();
            httpPost.setEntity(reqEntity);
            // 发起请求 并返回请求的响应
            response = httpClient.execute(httpPost, HttpClientContext.create());
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        }  catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null)
                    response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }
}

调用时需设置白名单,不然调用不了

 

 

创建AccessToken类


import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;


@Data
@Slf4j
public class AccessToken {
    private String token;
    private long expiresTime;//过期时间

    public AccessToken(String token, String expiresIn) {
        super();
        this.token = token;
        //当前时间+有效期 = 过期时间
        this.expiresTime = System.currentTimeMillis()+Integer.parseInt(expiresIn);
    }

    /**
     * 判断token是否过期
     * @return
     */
    public boolean isExpire() {
        return System.currentTimeMillis() > expiresTime;
    }
    //get and set ...
    private static AccessToken at;//token获取的次数有限,有效期也有限,所以需要保存起来
    private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";


    private static String APPID = "wx1e953fbcxxxxxxxx";
    private static String APPSECRET = "103854f56cf397e1bd2xxxxxxxxx";

    /**
     * 发送get请求获取AccessToken
     */
    private static String getToken() {
        String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);

        String tokenStr = HttpUtils.sendGet(url);//调用工具类发get请求
        System.out.println(tokenStr);
        JSONObject jsonObject = JSONObject.parseObject(tokenStr);
        String token = jsonObject.getString("access_token");
        String expiresIn = jsonObject.getString("expires_in");
        at = new AccessToken(token, expiresIn);
        return at.token;
    }

    /**
     * todo 获取AccessToken  向外提供   调用的ip必须在公众号设置白名单。不然获取不到token!!!!!!!!!!
     */
    public static String getAccessToken() {
        //过期了或者没有值再去发送请求获取
        if(at == null || at.isExpire()) {
            getToken();
        }
        return at.getToken();
    }

    public static void main(String[] args) {
        String accessToken = getAccessToken();
        System.out.println("accessToken = " + accessToken);
    }


}

自定义菜单 

自定义菜单文档

请注意:

  1. 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
  2. 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“…”代替。
  3. 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

自定义菜单接口可实现多种类型按钮,如下:

  1. click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
  2. view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
  3. scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
  4. scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
  5. pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
  6. pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
  7. pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
  8. location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
  9. media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
  10. view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。

接口调用请求说明

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

url中的ACCESS_TOKEN就是之前获取的,调用这个接口需要带上

请求需携带json参数

{
 "button":[
     {
           "type":"click",
          "name":"一级点击",
          "key":"1"
     },
     {
           "type":"view",
          "name":"个人博客",
          "url":"https://heliufang.gitee.io/"
     },
     {
          "name":"有子菜单",
          "sub_button":[
              {
                  "type":"click",
                  "name":"三一点击",
                  "key":"31"
            },
            {
                  "type":"view",
                  "name":"码云博客",
                  "url":"https://heliufang.gitee.io/"
            },
            {
                "type":"pic_photo_or_album",
                "name":"拍照或发图",
                "key":"33"
            }
          ]
     }
 ]
}

参数说明

参数 是否必须 说明
button 一级菜单数组,个数应为1~3个
sub_button 二级菜单数组,个数应为1~5个
type 菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
name 菜单标题,不超过16个字节,子菜单不超过60个字节
key click等点击类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view、miniprogram类型必须 网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。
media_id media_id类型和view_limited类型必须 调用新增永久素材接口返回的合法media_id
appid miniprogram类型必须 小程序的appid(仅认证公众号可配置)
pagepath miniprogram类型必须 小程序的页面路径

返回结果

正确时的返回JSON数据包如下:

{"errcode":0,"errmsg":"ok"}

错误时的返回JSON数据包如下(示例为无效菜单名长度):

{"errcode":40018,"errmsg":"invalid button name size"}

和前面xml的类似,我们需要对着请求的json数据封装按钮类,这样后面操作起来就比较方便,而且也方便维护。

封装菜单类

<1>AbstractButton类

//所有菜单(按钮)的父类
public abstract class AbstractButton {
    private String name;//按钮标题

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

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

    public AbstractButton(final String name) {
        this.name = name;
    }
}

<2>Button类

//一级菜单对象
public class Button {
    private List<AbstractButton> button;

    public Button() {
        this.button = new ArrayList<AbstractButton>();
    }

    public List<AbstractButton> getButton() {
        return this.button;
    }

    public void setButton(final List<AbstractButton> button) {
        this.button = button;
    }
}

 <3>ClickButton类

//点击类型的菜单
public class ClickButton extends AbstractButton {
    private String type;
    private String key;

    public String getType() {
        return this.type;
    }

    public void setType(final String type) {
        this.type = type;
    }

    public String getKey() {
        return this.key;
    }

    public void setKey(final String key) {
        this.key = key;
    }

    public ClickButton(final String name, final String key) {
        super(name);
        this.type = "click";//点击类型
        this.key = key;
    }
}

<4>ViewButton类

//网页类型的菜单
public class ViewButton extends AbstractButton {
    private String type;
    private String url;

    public String getType() {
        return this.type;
    }

    public void setType(final String type) {
        this.type = type;
    }

    public String getUrl() {
        return this.url;
    }

    public void setUrl(final String url) {
        this.url = url;
    }

    public ViewButton(final String name, final String url) {
        super(name);
        this.type = "view";//网页类型
        this.url = url;
    }
}

 <5> PhotoOrAlbumButton

//拍照或传图菜单
public class PhotoOrAlbumButton extends AbstractButton{
    private String type;
    private String key;

    public PhotoOrAlbumButton(String name,String key) {
        super(name);
        this.type = "pic_photo_or_album";//拍照获取传图
        this.key = key;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

<6>SubButton

import java.util.ArrayList;
import java.util.List;

//二级菜单对象
public class SubButton extends AbstractButton {
    private List<AbstractButton> sub_button;

    public List<AbstractButton> getSub_button() {
        return this.sub_button;
    }

    public void setSub_button(final List<AbstractButton> sub_button) {
        this.sub_button = sub_button;
    }

    public SubButton(final String name) {
        super(name);
        this.sub_button = new ArrayList<AbstractButton>();
    }
}

 测试


import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.web.jxxt.util.wxGz.*;

import java.util.ArrayList;
import java.util.List;

/**
 * 进行设置三级菜单进行跳转功能页面
 */
public class AAWxGzh {

    public static void main(String[] args) {
        //创建一级菜单
        Button button = new Button();
        //在第三个菜单中创建二级菜单
        SubButton subButton = new SubButton("驾校点击");
        List<AbstractButton> list2 = new ArrayList();
        list2.add(new ViewButton("一刀999", "http://jiaxiaoh5.heiwangke.cn/"));
        list2.add(new ViewButton("百度点击", "https://www.baidu.com/"));
        list2.add(new PhotoOrAlbumButton("拍照或发图","33"));
        subButton.setSub_button(list2);

        //在第一个模块中进行创建二级菜单
        SubButton subButton1 = new SubButton("第一个");
        List<AbstractButton> list21 = new ArrayList();
        list21.add(new ClickButton("三一点击", "31"));
        list21.add(new ViewButton("百度点击", "https://www.baidu.com/"));
        list21.add(new PhotoOrAlbumButton("拍照或发图","33"));
        subButton1.setSub_button(list21);


        //在一级菜单中添加三个按钮,
        List<AbstractButton> list = new ArrayList();
        list.add(subButton1);
        list.add(new ViewButton("个人博客", "https://blog.csdn.net/Java_Mr_Jin?type=blog"));
        list.add(subButton);
        button.setButton(list);
        //转成json格式字符串
        String jsonString = JSONObject.toJSONString(button);
        //System.out.println(jsonString);
        //发送请求
        String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
        url = url.replace("ACCESS_TOKEN", AccessToken.getAccessToken());//把token带上
        String result = HttpUtils.sendPost(url, jsonString);
        System.out.println(result);
    }
}

测试结果:

 

获取已关注的用户信息

获取用户基本信息(UnionID机制)

在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。

获取用户基本信息(包括UnionID机制)

开发者可通过OpenID来获取用户基本信息。请使用https协议。

接口调用请求说明 http请求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

参数说明

参数 是否必须 说明
access_token 调用接口凭证
openid 普通用户的标识,对当前公众号唯一
lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

openid可以登录测试号管理界面获取,对应关注者的微信号



    @ApiOperation("获取用户token")
    @PostMapping("/getToken")
    public R getToken(String openId){
        String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
        url = url.replace("ACCESS_TOKEN", AccessToken.getAccessToken());
        url = url.replace("OPENID", openId);
        String string = HttpUtils.sendGet(url);
        System.out.println(string);//这里就可以看到打印的用户信息了

        return R.ok().data("one",string);
    }

 网页授权 获取未关注的用户信息

可以获取未关注的用户信息,这部分需要有域名才能测试。

网页授权


@RestController
@RequestMapping("/jx/getUserInfo")
@Api(tags = "公众号登录")
public class GetUserInfoServlet {
    private static final long serialVersionUID = 1L;
    private static String APPID = "wxde22cf3xxxxxxbea";
    private static String APPSECRET = "e8186658cb4b33xxxxxxxxxxxx";



    @ApiOperation("/用户登录")
    @PostMapping("/doGets")
    public R doGets(String code){
        //1.用户同意授权,获取code
        //2.通过code获取网页授权的access_token
        String url = " https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
        url = url.replace("APPID", APPID).replace("CODE", code).replace("SECRET", APPSECRET);
        String string = HttpUtils.sendGet(url);
        JSONObject jsonObject = JSONObject.parseObject(string);
        //{"openid":"okUsy6TJldDxcpNa0SKmxacVP6J0","nickname":"刘亚方","sex":0,"language":"","city":"","province":"","country":"","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/DYAIOgq83eqOicpjaCCDfU5RcHBJAjNNCRg65RLjdNanjWgwB7Ria7T9FrnLqWicibjlQacI0FeVVHXppTeLCD1Vfw\/132","privilege":[]}
        String accessToken = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");

        //3.刷新access_token(如果需要)
        //4.通过token获取用户信息

        String getUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
        getUserInfoUrl = getUserInfoUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openid);
        String userInfoJsonStr = HttpUtils.sendGet(getUserInfoUrl);
        System.out.println(userInfoJsonStr);
        JSONObject jsonObject1 = JSONObject.parseObject(userInfoJsonStr);//获取姓名 和头像

        String nickname = jsonObject1.getString("nickname");
        String headimgurl = jsonObject1.getString("headimgurl");

        ...

        return R.ok();
    }


}

微信公众号开发框架

前面的开发都是原生的写法,github上有很多现成的公众号开发框架。

比如这个基于springboot的公众号开发框架:

仓库:GitHub - binarywang/weixin-java-mp-demo: 基于Spring Boot 和 WxJava 实现的微信公众号Java后端Demo,支持多公众号

文档:公众号开发文档 · Wechat-Group/WxJava Wiki · GitHub

最后多说一句只有把原生的基础打好了,才能更好的理解和使用框架,所以建议先学原生的公众号开发,再上手框架。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA微信公众号 的相关文章

  • Keytool 应用程序在哪里?

    我需要在android中使用mapview控件 但我似乎不明白如何运行keytool 是用eclipse安装的吗 我好像找不到下载链接 Thanks keytool http docs oracle com javase 7 docs te
  • 具有更高可见性的重写方法是良好的实践吗?

    回答这个问题 如何使用 GUI 使用 PaintComponent 初始化 GUI 然后添加基于鼠标的 GUI https stackoverflow com questions 21336141 how to gui using pain
  • “_加载小部件时出现问题”消息

    加载小部件时 如果找不到资源或其他内容 则会显示 加载小部件时出现问题 就这样 惊人的 此消息保留在主屏幕上 甚至没有说明加载时遇到问题的小部件 我通过反复试验弄清楚了这一点 但我想知道发生这种情况时是否有任何地方可以找到错误消息 Andr
  • 来自数据库的 jfreechart 散点图

    如何使用java中的jfreechart绘制mysql数据库表中数据的散点图 我使用过 Swing 库 任何链接都会有帮助 我搜索了谷歌但找不到理解的解决方案 如果您有代码 请提供给我 实际上我确实做了条形图并使用 jfreechart 绘
  • Java套接字:在连接被拒绝异常时重试的最佳方法?

    现在我正在这样做 while true try SocketAddress sockaddr new InetSocketAddress ivDestIP ivDestPort downloadSock new Socket downloa
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 具有共享依赖项的多模块项目的 Gradle 配置

    使用 gradle 制作第一个项目 所以我研究了 spring gradle hibernate 项目如何组织 gradle 文件 并开始制作自己的项目 但是 找不到错误 为什么我的配置不起作用 子项目无法解决依赖关系 所以项目树 Root
  • Java 服务器-客户端 readLine() 方法

    我有一个客户端类和一个服务器类 如果客户端向服务器发送消息 服务器会将响应发送回客户端 然后客户端将打印它收到的所有消息 例如 如果客户端向服务器发送 A 则服务器将向客户端发送响应 1111 所以我在客户端类中使用 readLine 从服
  • Java 8 中函数式接口的使用

    这是来自的后续问题Java 8 中的 双冒号 运算符 https stackoverflow com questions 20001427 double colon operator in java 8其中 Java 允许您使用以下方式引用
  • RSA OAEP、Golang 加密、Java 解密 -BadPaddingException:解密错误

    我正在尝试解密使用 RSA OAEP 在 Golang 中加密的字符串 但出现 BadPaddingException 解密错误 很难弄清楚我错过了什么 这是Golang加密方法 func encryptString rootPEM io
  • Spring Security OAuth2简单配置

    我有一个简单的项目 需要以下简单的配置 我有一个 密码 grant type 这意味着我可以提交用户名 密码 用户在登录表单中输入 并在成功时获得 access token 有了该 access token 我就可以请求 API 并获取用户
  • Android Studio 将音乐文件读取为文本文件,如何恢复它?

    gameAlert mp3是我的声音文件 运行应用程序时 它询问我该文件不与任何文件类型关联 请定义关联 我选择TextFile错误地 现在我的音乐文件被读取为文本文件 我如何将其转换回music file protected void o
  • Espresso 和 Proguard 的 Java.lang.NoClassDefFoundError

    我对 Espresso 不太有经验 但我终于成功地运行了它 我有一个应用程序需要通过 Proguard 缩小才能处于 56K 方法之下 该应用程序以 3 秒的动画开始 因此我需要等到该动画结束才能继续 这就是我尝试用该方法做的事情waitF
  • 逃离的正确方法是什么?使用 Oracle 12c MATCH_RECOGNIZE 时 JDBCPreparedStatement 中的字符?

    以下查询在 Oracle 12c 中是正确的 SELECT FROM dual MATCH RECOGNIZE MEASURES a dummy AS dummy PATTERN a DEFINE a AS 1 1 但它不能通过 JDBC
  • 解析输入,除了 System.in.read() 之外不使用任何东西

    我很难找到具体的细节System in read 有效 也许有人可以帮助我 似乎扫描仪会更好 但我不允许使用它 我被分配了一个任务 我应该以 Boolean Operator Boolean 的形式读取控制台用户输入 例如T F 或 T T
  • 如何通过 Inno Setup for NetBeans 使用自定义 .iss 文件

    我将 Inno Setup 5 与 NetBeans 8 一起使用 并且我已经能够创建一个安装程序来安装该应用程序C users username local appname 但是我希望将其安装在C Programfiles 我如何在 Ne
  • 如何在 Quartz 调度程序中每 25 秒运行一次?

    我正在使用 Java 的 Quartz Scheduling API 你能帮我使用 cron 表达式每 25 秒运行一次吗 这只是一个延迟 它不必总是从第 0 秒开始 例如 序列如下 0 00 0 25 0 50 1 15 1 40 2 0
  • 挂钩 Eclipse 构建过程吗?

    我希望在 Eclipse 中按下构建按钮时能够运行一个简单的 Java 程序 目前 当我单击 构建 时 它会运行一些 JRebel 日志记录代码 我有一个程序可以解析 JRebel 日志文件并将统计信息存储在数据库中 是否可以编写一个插件或
  • JSON 到 hashmap (杰克逊)

    我想将 JSON 转换为 HashMapJackson http jackson codehaus org 这是我的 JSON String json Opleidingen name Bijz trajecten zorg en welz
  • Android AutoCompleteTextView 带芯片

    我不确定我是否使用了正确的词语来描述此 UI 功能 但我已附上我希望在我的应用程序中实现的目标的快照 它由 Go SMS 使用 用户在编辑文本中键入联系人 在用户从完成下拉列表中选择联系人后 该联系人将被插入到编辑文本中 如附图所示 编辑文

随机推荐

  • web项目部署到某云Linux服务器的详细步骤

    一 安装xshell 和 xftp 1 xshell连接服务器 方式有几种 这里只介绍其中之一 在 某云 密钥对创建密钥对 然后会得到下载的密钥对文件 打开xshell 打开 新建 上图中的主机填某云 实例 中的 然后在xshell 用户身
  • Python3,Pandas这4种高频使用的筛选数据的方法,不得不说,确实挺好。

    Pandas数据筛选方法 1 引言 2 4种高频使用数据筛选方法 2 1 布尔索引 2 2 isin 方法 2 3 query 方法 2 4 loc 方法 3 总结 1 引言 小屌丝 鱼哥 share一下 数据筛选的方法呗 小鱼 Excel
  • 精度 vs 效率:模型越小,精度就一定越低吗?

    导语 深度学习是否朝着正确的方向发展 以下是我最近在伦敦 O Reilly AI Conference 和 DroidCon 上的两次谈话的改编 今年早些时候 NVIDIA 的研究人员发布了 MegatronLM 这是一个拥有 83 亿个参
  • 东方财富choice金融终端研究笔记

    东方财富choice金融终端研究笔记 最近在研究 东方财富choice金融终端 我就搞不懂了 他们弄这个东西是存心不让人懂的吗 说明PDF第四页 方式一 使用激活工具 适用于有图形界面 根据所用系统环境 运行接口激活工具LoginActiv
  • 第一节:Keras深度学习框架之环境搭建

    请在学习本节前阅读我们之前的预热课程 卷积神经网络的框架解读 上 BBM的开源HUB的博客 CSDN博客 卷积神经网络的框架解读 下 BBM的开源HUB的博客 CSDN博客 从本节开始 我们将进入到Keras的详细介绍和代码精读 为开始我们
  • 02-编写单个字节设备模块的驱动套路

    目录 1 单字节设备和多字节设备的区别 2 单字节设备 LED设备驱动 的驱动套路 2 1 头文件 2 2 定义设备驱动相关的变量 2 3 编写file operations 相关操作的函数 2 3 1 open函数 2 3 2 relea
  • matlab做角谱传播代码_AI

    运筹OR帷幄 转载 作者 机器之心 编者按 对python语言有所了解的人都知道Numpy这个数学处理工具包 而它在机器学习中也有很重要的地位 通过合理的使用Numpy这个工具 可以简单快速地搭建模型的数学计算流程 可以说是一把 利剑 普林
  • 阿里云:网络编程 bind:cannot assign requested address errno:99 问题

    解决方案 阿里云上的服务器代码绑定的 IP 需要时内网 IP ifconfig 查看 其他客户端连接服务器时所用的 IP 得是阿里云的外网 IP 查看实例即可 分析思路 猜想1 bind cannot assign requested ad
  • 解决“Pycharm中用Install Package 安装第三库出错”问题的经验

    1 问题描述 在PyCharm中通过Install Package 安装第三库 requests 报错了 但是可以通过终端Terminal可以安装 报错信息如图 2 问题分析及方法对策 根据我自己的经历以及网上搜寻所得 大致总结出如下原因
  • Python Selenium UI自动化测试

    1 自动化测试基础 1 1 自动化测试的定义 将人为的测试行为转化为机器自动执行的过程 1 2 自动化测试的目的 减少成本 提高测试效率 减少人为因素对测试的影响 1 3 什么项目适合做自动化测试 项目界面稳定 需求明确 项目周期长 测试脚
  • robot framework 接口自动化测试(2)get方式传递token

    之前介绍了get请求头不需要传递参数的方式 那么对于需要传入登录状态的token接口我们怎么测试呢 下面介绍一下 首先先做post的接口自动化获取到token token实时更新 所以每次调用需要token的get接口测试都需要post的t
  • postman-post格式报文接口的配置

    post格式报文接口的配置 1 新建请求 2 配置请求报文 输入接口URL authorization界面可配置授权信息 header界面可以配置请求头 body界面可以配置请求体 非作者允许 严禁转载 http接口有许多格式 post格式
  • pymysql中 execute 和 executemany 性能对比,以及与原生SQL 相比如何

    今天在mysql中插入大批量数据时 突然想起pymysql 还有executemany 方法 那么这两个方法到底谁快 快多少 测试环境 python3 mysql pymysql 老规矩 先上测试代码 class IN sql def in
  • [1144]Hive常用日期格式转换

    文章目录 获取当前时间 Hive中处理毫秒级别的时间戳 日期格式转换 返回日期中的年 月 日 时 分 秒 当前的周数 返回当月或当年的第一天 计算日期差值 返回结束日期减去开始日期的天数 返回开始日期startdate增加days天后的日期
  • webpack5 学习(六)—— 管理资源:自定义 JSON 模块 parser

    通过使用 自定义 parser 替代特定的 webpack loader 可以将任何 toml yaml 或 json5 文件作为 JSON 模块导入 在 src 文件夹下创建一个 data toml 一个 data yaml 以及一个 d
  • 矩阵求和

    include
  • 第四十章 Unity 按钮 (Button) UI

    本章节我们介绍一下按钮UI 首先 我们创建一个新的场景 SampleScene3 unity 然后 在菜单栏中点击 GameObject gt UI gt Button 截图如下 我们选中刚刚创建的Button 然后查看它的Inspecto
  • 深度优先搜索——搜索与回溯,从n个数中取出r个数的排列

    5 2 1 include
  • IDFA 单元测试以及单元测试覆盖率步骤

    一 单元测试类 1 新建java类 随意选择java类文件 新建一个Java类CountVowel 用来统计字符串中元音的个数 代码如下 public class CountVowels private static boolean isV
  • JAVA微信公众号

    1 微信公众号介绍 账号分为服务号 订阅号 小程序 服务号和订阅号开发类似 但是申请服务号必须是企业 所以学习的话申请一个订阅号 测试账号即可 2 注册订阅号 第一步 访问 微信公众平台 点击立即注册按钮 第二步 注册类型页面选择订阅号 第