springboot开发微信公众号(一)创建、查询、删除菜单(附源码)

1.使用微信公众号测试账号进行开发,申请测试公众号http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
2.登录之后,能获取微信公众号的appID和appsecret

3.查看接口文档可以发现,每次请求接口都需要带access_token,所以首先需要查询token

4.查询token放入Redis(启动时查询一次token放入Redis,设置生效时间一小时,设置定时任务,每隔一小时刷新下token,也可以不存储token,每次请求接口都重新刷新下token)

 @Scheduled(cron = "0 0/60 * * * ?")
    public void execute() {
        log.info("定时更新token,每60分钟跑一次,appId={},appSecret={}",appId,appSecret);
        CtBossSender ctBossSender=new CtBossSender(appId,appSecret);
        NutMap nutMap=ctBossSender.sendHttpGet();
        log.info("获取到的Token为={}",JSON.toJSONString(nutMap));
        try {
            redisTemplate.opsForValue().set(MessageTypeEnum.ACCESS_TOKEN.getInfo(),nutMap.getString(MessageTypeEnum.ACCESS_TOKEN.getInfo()),60, TimeUnit.MINUTES);
        }catch (Exception e){
            log.error(e.getMessage(),e);
            log.info("token更新异常....={}",JSON.toJSONString(nutMap));
            return;
        }
        log.info("token更新成功....");
    }
    
/**
 * @Date 10:49 2020/7/21
 * 项目启动时更新token信息  放入内存中
 */
@Component
@Slf4j
public class InitTokenUtil implements CommandLineRunner {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${appid}")
    private String appId;
    @Value("${appsecret}")
    private String appSecret;
    /*
     * @Author 
     * @Description //项目启动时获取token,放入Redis
     * @Date 15:06 2020/7/23
     * @Param [args]
     * @return
     **/
    @Override
    public void run(String... args) throws Exception {
        log.info("项目启动时更新token,appId={},appSecret={}",appId,appSecret);
        CtBossSender ctBossSender=new CtBossSender(appId,appSecret);
        NutMap nutMap=ctBossSender.sendHttpGet();
        log.info("获取到的Token为={}",JSON.toJSONString(nutMap));
        try {
            redisTemplate.opsForValue().set(MessageTypeEnum.ACCESS_TOKEN.getInfo(),nutMap.getString(MessageTypeEnum.ACCESS_TOKEN.getInfo()),60, TimeUnit.MINUTES);
        }catch (Exception e){
            log.error(e.getMessage(),e);
            log.info("token更新异常....={}",JSON.toJSONString(nutMap));
            return;
        }
        log.info("token更新成功....");
    }
}
5.查看文档获取创建、删除、获取菜单URL,文档里给出了URL和菜单示例

6.创建枚举类,放入URL
public enum ApiEum {
    ACCESS_TOKEN_URL(" https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"),
    BASE_URL(" https://api.weixin.qq.com/cgi-bin/"),
    DELETE_PERSONAL_MENU_URL("menu/delconditional"),//删除个性化菜单
    CREATE_PERSONALIZED_MENU_URL("menu/addconditional"),//创建个性化菜单
    MENU_GET_URL("menu/get"),//自定义菜单的查询接口
    MENU_DELETE_URL("menu/delete"),//自定义菜单删除
    MENU_CREATE_URL("menu/create");//自定义菜单创建
    private String info;
    ApiEum(String info) {
        this.info = info;
    }
    public String getInfo() {
        return info;
    }
}
7.创建菜单请求实体
@Data
public class Button {
    private String type;//菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
    private String name;//菜单标题,不超过16个字节,子菜单不超过40个字节
    private String url;//网页链接,用户点击菜单可打开链接,不超过1024字节。当type为miniprogram时,不支持小程序的老版本客户端将打开本url
    private String key;//菜单KEY值,用于消息接口推送,不超过128字节

    private String appid;//小程序的appid
    private String pagepath;//小程序的页面路径
    private String media_id;//调用新增永久素材接口返回的合法media_id
    private Button[] sub_button;//二级菜单数组,个数应为1~5个

}
@Data
public class Menu {
    private Button[] button;//一级菜单数组,个数应为1~3个
}
8.请求微信平台创建菜单
 /*
     * @Author 
     * @Description //获取文档里的Menu示例,也可以根据格式一步一步的拼接
     * @Date 14:40 2020/7/21
     * @Param []
     * @return {@link com.boot.wechart.entity.Menu}
     **/
    public Menu getSyStemMenu(){
        String message=" {\n" +
                "     \"button\":[\n" +
                "     {\t\n" +
                "          \"type\":\"click\",\n" +
                "          \"name\":\"今日歌曲\",\n" +
                "          \"key\":\"V1001_TODAY_MUSIC\"\n" +
                "      },\n" +
                "      {\n" +
                "           \"name\":\"菜单\",\n" +
                "           \"sub_button\":[\n" +
                "           {\t\n" +
                "               \"type\":\"view\",\n" +
                "               \"name\":\"搜索\",\n" +
                "               \"url\":\"http://www.soso.com/\"\n" +
                "            },\n" +
                "            {\n" +
                "               \"type\":\"click\",\n" +
                "               \"name\":\"赞一下我们\",\n" +
                "               \"key\":\"V1001_GOOD\"\n" +
                "            }]\n" +
                "       }]\n" +
                " }";
        return JSON.parseObject(message, Menu.class);
    }
   @RequestMapping("/createMenu")
    public String createMenu(){
        Menu menu= MenuUtil.me().getSyStemMenu();//获取文档示例菜单
        return menuService.creatMenu(menu);
    }
/*
     * @Author 
     * @Description //创建菜单
     * @Date 13:44 2020/7/21
     * @Param
     * @return {@link null}
     **/
    @Override
    public String creatMenu(Menu menu) {
        //发起POST请求创建菜单
        CtBossSender ctBossSender=new CtBossSender(ApiEum.MENU_CREATE_URL.getInfo());        //获取菜单结构
        String accessToken=redisTemplate.opsForValue().get(MessageTypeEnum.ACCESS_TOKEN.getInfo());
        log.info("创建菜单请求参数为={}",JSON.toJSONString(menu));
        NutMap nutMap = ctBossSender.sendHttpPost(JSON.toJSONString(menu),accessToken);
        log.info("创建菜单返回参数为={}",JSON.toJSONString(nutMap));
        return JSON.toJSONString(nutMap);
    }
9.获取、删除自定义菜菜单
 @Override
    public String deleteMenu() {
        //发起GET请求删除菜单
        CtBossSender ctBossSender=new CtBossSender(ApiEum.MENU_DELETE_URL.getInfo());        //获取菜单结构
        String accessToken=redisTemplate.opsForValue().get(MessageTypeEnum.ACCESS_TOKEN.getInfo());
        NutMap nutMap = ctBossSender.sendHttpGet(accessToken);
        log.info("删除菜单返回参数为={}",JSON.toJSONString(nutMap));
        return JSON.toJSONString(nutMap);
    }

    @Override
    public String getMenu() {
        CtBossSender ctBossSender=new CtBossSender(ApiEum.MENU_GET_URL.getInfo());        //获取菜单结构
        String accessToken=redisTemplate.opsForValue().get(MessageTypeEnum.ACCESS_TOKEN.getInfo());
        NutMap nutMap = ctBossSender.sendHttpGet(accessToken);
        log.info("获取菜单返回参数为={}",JSON.toJSONString(nutMap));
        return JSON.toJSONString(nutMap);
    }
10.代码中用到的工具类
public enum MessageTypeEnum {
    EVENT_TYPE_TEMPLATESENDJOBFINISH("TEMPLATESENDJOBFINISH"),//事件类型:TEMPLATESENDJOBFINISH(模板消息送达情况提醒)
    ACCESS_TOKEN("access_token"),
    EVENT_TYPE_VIEW("VIEW"),//事件类型:VIEW(自定义菜单URl视图)
    EVENT_TYPE_CLICK("CLICK"),//事件类型:CLICK(点击菜单拉取消息)
    EVENT_TYPE_LOCATION("location"),//事件类型:location(上报地理位置)
    EVENT_TYPE_SCAN("EVENT_TYPE_SCAN"),//事件类型:scan(关注用户扫描带参二维码)
    EVENT_TYPE_UNSUBSCRIBE("unsubscribe"),//事件类型:取消订阅
    EVENT_TYPE_SUBSCRIBE("subscribe"),//事件类型:订阅
    RESP_MESSAGE_TYPE_TEXT ("text"),//返回消息类型:文本
    RESP_MESSAGE_TYPE_IMAGE("image"),//消息返回类型:图片
    RESP_MESSAGE_TYPE_VOICE ("voice"),//消息返回类型:语音
    RESP_MESSAGE_TYPE_MUSIC ("music"),//消息返回类型:音乐
    RESP_MESSAGE_TYPE_NEWS ("news"),//消息返回类型:图文
    RESP_MESSAGE_TYPE_VIDEO("video"),//消息返回类型:视频
    REQ_MESSAGE_TYPE_SHORTVIDEO("shortvideo"),//请求消息类型:小视频
    REQ_MESSAGE_TYPE_EVENT("event"),//请求消息类型:事件推送
    REQ_MESSAGE_TYPE_LINK("link"),//请求消息:类型链接
    REQ_MESSAGE_TYPE_LOCATION("地理位置"),//请求消息类型:地理位置
    REQ_MESSAGE_TYPE_VIDEO("vedio"),//请求消息类型:视频
    REQ_MESSAGE_TYPE_VOICE("voice"),//请求消息类型:语音
    REQ_MESSAGE_TYPE_IMAGE("image"),//请求消息类型:图片
    REQ_MESSAGE_TYPE_TEXT("text");//请求消息类型:文本
    private String info;
    MessageTypeEnum(String info) {
        this.info = info;
    }
    public String getInfo() {
        return info;
    }
}
public class CtBossSender {
    private String url;
    private OkHttpClient httpRequestClient;
    /*
     * @Author 
     * @Description //token url初始化
     * @Date 10:29 2020/7/21
     * @Param [appid, secret]
     * @return {@link null}
     **/
    public CtBossSender(String appid, String secret) {
        StringBuilder urls=new StringBuilder(ApiEum.ACCESS_TOKEN_URL.getInfo()).append("&appid=").append(appid).append("&secret=").append(secret);
        this.url =urls.toString() ;
        httpRequestClient = OKHttp3Utils3.getHttpClient(60);
    }
    /*
     * @Author 
     * @Description //请求微信公众号接口方法的初始化
     * @Date 10:31 2020/7/21
     * @Param [function]
     * @return {@link null}
     **/
    public CtBossSender(String function) {
        StringBuilder urls=new StringBuilder(ApiEum.BASE_URL.getInfo()).append(function);
        this.url = urls.toString();
        httpRequestClient = new OkHttpClient();
    }
    public CtBossSender() {
        httpRequestClient = new OkHttpClient();
    }
    /*
     * @Author 
     * @Description //url+token
     * @Date 10:32 2020/7/21
     * @Param [token]
     * @return
     **/
    public void init(String token) {
        StringBuilder reqUrl = new StringBuilder(this.url).append("?");
        reqUrl.append("&access_token=").append(token);
        this.url = reqUrl.toString();
    }
    /**
     * 发送GET请求获取token
     *
     * @return
     * @throws IOException
     */
    public NutMap sendHttpGet(){
        log.info("发送请求的url:url={}",url);
        Response response = null;
        NutMap responseBody =null;
        try {
            response = OKHttp3Utils3.get(url);
            responseBody= JSON.parseObject(response.body().string(),NutMap.class);
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }
        log.info("Send iot request end... responseBody={}", JSON.toJSONString(responseBody));
        return responseBody;
    }
    /**
     * 发送GET请求获取数据
     *
     * @return
     * @throws IOException
     */
    public NutMap sendHttpGet(String token){
        init(token);
        log.info("发送请求的url:url={}",url);
        Response response = null;
        NutMap responseBody =null;
        try {
            response = OKHttp3Utils3.get(url);
            responseBody= JSON.parseObject(response.body().string(),NutMap.class);
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }
        log.info("Send iot request end... responseBody={}", JSON.toJSONString(responseBody));
        return responseBody;
    }
    /**
     * 发送POST请求获取数据
     *
     * @return
     * @throws IOException
     */
    public NutMap sendHttpPost(String requestBody,String token){
        init(token);
        log.info("发送请求的url:url={}",url);
        NutMap response = JSON.parseObject(OKHttp3Utils3.post(url,requestBody),NutMap.class);

        log.info("Send iot request end... responseBody={}", JSON.toJSONString(response));
        return response;
    }
}
public final class OKHttp3Utils3 {

    public static int DEFAULT_TIME_OUT = 10;

    /**
     * 全局实例可以保持http1.1 连接复用,线程池复用, 减少tcp的网络连接,关闭,
     * 如果每次一个请求,在高并发下,thread增多到1W,close_wait持续增加到6k。
     */
    private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
            .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
            .connectTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).build();

    private static final MediaType mediaType = MediaType.parse("application/json; charset=utf-8");

    private static final MediaType FORM_TYPE = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8");


    /**
     * 不同timeout的连接池
     */
    public static ConcurrentHashMap<Integer, OkHttpClient> cacheClients = new ConcurrentHashMap();


    public static OkHttpClient getHttpClient(int timeout) {

        if (timeout == 0 || DEFAULT_TIME_OUT == timeout) {
            return OK_HTTP_CLIENT;
        } else {
            OkHttpClient okHttpClient = cacheClients.get(timeout);
            if (okHttpClient == null) {
                return syncCreateClient(timeout);
            }
            return okHttpClient;
        }
    }

    private static synchronized OkHttpClient syncCreateClient(int timeout) {
        OkHttpClient okHttpClient;

        okHttpClient = cacheClients.get(timeout);
        if (okHttpClient != null) {
            return okHttpClient;
        }

        okHttpClient = new OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.SECONDS).readTimeout(timeout, TimeUnit.SECONDS).writeTimeout(timeout, TimeUnit.SECONDS).build();
        cacheClients.put(timeout, okHttpClient);
        return okHttpClient;

    }


    /**
     * GET请求
     *
     * @param url
     * @return Optional<String>
     */
    public static Response get(String url, int timeout) throws IOException {
        Request request = new Request.Builder().url(url)
                .build();
        return getHttpClient(timeout).newCall(request).execute();

    }

    public static Response get(String url) throws IOException {
        return get(url, 60);
    }

    /**
     * POST请求,参数为json格式。
     *
     * @param url
     * @param json
     * @return Optional<String>
     */
    public static String post(String url, String json, int timeout) throws Exception {
        long start = System.currentTimeMillis();
        try {
            RequestBody body = RequestBody.create(mediaType, json);
            Request request = new Request.Builder().url(url).post(body).build();
            return getHttpClient(timeout).newCall(request).execute().body().string();
        } catch (Exception e) {
            throw e;
        } finally {
            log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start));

        }


    }


    public static String post(String url, String json)  {
        try {
            return post(url, json, 60);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String postByFormType(String url, String form) throws Exception {
        long start = System.currentTimeMillis();
        try {
            RequestBody body = RequestBody.create(FORM_TYPE, form);
            Request request = new Request.Builder().url(url).post(body).build();
            return getHttpClient(0).newCall(request).execute().body().string();

        } catch (Exception e) {
            throw e;
        } finally {
            log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start));

        }
    }


    /**
     * 根据不同的类型和requestbody类型来接续参数
     *
     * @param url
     * @param mediaType
     * @param inputStream
     * @return
     * @throws Exception
     */
    public static String post(String url, MediaType mediaType, InputStream inputStream) throws Exception {
        RequestBody body = createRequestBody(mediaType, inputStream);
        Request request = new Request.Builder().url(url).post(body).build();
        return OK_HTTP_CLIENT.newCall(request).execute().body().string();
    }

    private static RequestBody createRequestBody(final MediaType mediaType, final InputStream inputStream) {
        return new RequestBody() {
            // @Nullable
            @Override
            public MediaType contentType() {
                return mediaType;
            }

            @Override
            public long contentLength() throws IOException {
                try {
                    return inputStream.available();
                } catch (IOException e) {
                    return 0;
                }
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                Source source = null;
                try {
                    source = Okio.source(inputStream);
                    sink.writeAll(source);
                } finally {
                    Util.closeQuietly(source);
                }
            }
        };
    }

}
ps.代码中实体对象没有get.set方法,是因为引入了lombok插件,附源码地址https://gitee.com/bjiangAnhui/yichengyoushaonian/tree/master/boot-wechart
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值