一, 前言
微信公众号开发 (1) 微信接入认证成为开发者
微信公众号开发 (2) 消息处理
本文将实现
根据 AppID 和 AppSecret 获取 access_token
自定义菜单(创建菜单, 查询菜单, 删除菜单)
微信文档中提示的一些注意点:
access_token 的存储至少要保留
512
个字符空间.
access_token 的有效期为 2 小时, 需定时刷新, 重复获取将导致上次获取的 access_token 失效
自定义菜单最多 3 个一级菜单, 每一级菜单最多 5 个二级菜单
一级菜单最多 4 个汉字
,
二级菜单最多 7 个汉字
菜单刷新策略:
5 分钟之后更新菜单. 测试时可以尝试取消关注公众账号后再次关注, 则可以看到创建后的效果
二, RestTemplate 配置 (用于远程调用微信 http 接口方法)
RestTemplate 是 Spring 提供的用于访问 REST 服务的客户端, RestTemplate 提供了多种便捷访问远程 Http 服务的方法, 能够大大提高客户端的编写效率.
- @Configuration
- public class RestTemplateConfig {
- @Bean
- public RestTemplate restTemplate() {
- RestTemplate restTemplate = new RestTemplate();
- // 解决 post 请求中文乱码问题
- restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
- return restTemplate;
- }
- }
三, 微信接口调用说明
获取 access_token 接口 : [GET 请求]
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
查询菜单接口 : [GET 请求]
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
删除菜单接口 : [GET 请求]
https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
创建菜单接口 : [POST 请求]
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
封装微信接口所需变量
- public class Constants {
- /**
- * TODO 填写自己的 `appID` 和 `appsecret`
- */
- public static final String APP_ID = "xxx";
- public static final String APP_SECRET = "xxx";
- /**
- * 通过 `GET 请求方式 ` 获取 `access_token`
- */
- public static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
- /**
- * TODO 只做临时方便测试使用
- */
- public static final String ACCESS_TOKEN = "xxx";
- /**
- * 查询菜单接口 - GET 请求
- */
- public static final String GET_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
- /**
- * 删除菜单接口 - GET 请求 (注意, 在个性化菜单时, 调用此接口会删除默认菜单及全部个性化菜单)
- */
- public static final String DELETE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
- /**
- * 创建菜单接口 - POST 请求
- */
- public static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
- }
四, 根据 AppID 和 AppSecret 获取 access_token
access_token 是公众号的全局唯一接口调用凭据, 公众号调用各接口 (下面的创建菜单, 查询菜单, 删除菜单等) 时都需使用 access_token!
这里可查看微信文档获取 access_token 方式
1 封装响应结果 AccessTokenVO
- @Data
- @ApiModel(description = "access_token: 公众号的全局唯一接口调用凭据")
- public class AccessTokenVO {
- @ApiModelProperty(value = "获取到的凭证")
- private String access_token;
- @ApiModelProperty(value = "凭证有效时间, 单位: 秒(微信目前暂 7200 秒, 即 2 小时, 过期后需再次获取)")
- private int expires_in;
- }
2 服务类
- public interface IWeixinService {
- /**
- * 根据 AppID 和 AppSecret 获取 access_token
- *
- * @param appId:
- * @param appSecret:
- * @return: com.zhengqing.demo.modules.weixin.model.AccessTokenVO
- */
- AccessTokenVO getAccessToken(String appId, String appSecret);
- }
3 服务实现类
- @Slf4j
- @Service
- public class WeixinServiceImpl implements IWeixinService {
- @Autowired
- private RestTemplate restTemplate;
- @Override
- public AccessTokenVO getAccessToken(String appId, String appSecret) {
- AccessTokenVO accessTokenVO = restTemplate.getForObject(Constants.GET_ACCESS_TOKEN_URL.replace("APPID", appId).replace("APPSECRET", appSecret), AccessTokenVO.class);
- return accessTokenVO;
- }
- }
五, 自定义菜单处理
click 和 view 请求示例
- {
- "button":[
- {
- "type":"click",
- "name":"今日歌曲",
- "key":"V1001_TODAY_MUSIC"
- },
- {
- "name":"菜单",
- "sub_button":[
- {
- "type":"view",
- "name":"搜索",
- "url":"http://www.soso.com/"
- },
- {
- "type":"miniprogram",
- "name":"wxa",
- "url":"http://mp.weixin.qq.com",
- "appid":"wx286b93c14bbf93aa",
- "pagepath":"pages/lunar/index"
- },
- {
- "type":"click",
- "name":"赞一下我们",
- "key":"V1001_GOOD"
- }]
- }]
- }
1, 封装菜单数据
温馨小提示: 这里封装数据建议多看下微信文档中给出的数据, 不然可能会对最后组装菜单树数据创建菜单的时候感到迷惑 ~
1 菜单类型枚举类
- public enum MenuType {
- // 点击式菜单
- CLICK("click"),
- // 链接式菜单
- VIEW("view");
- }
2 菜单 - 基类
- @Data
- @ApiModel(description = "菜单 - 基类")
- public class Button {
- @ApiModelProperty(value = "菜单标题, 不超过 16 个字节, 子菜单不超过 60 个字节")
- private String name;
- }
3 点击式菜单
- @Data
- @ApiModel(description = "用户点击菜单可接收消息推送")
- public class ClickButton extends Button {
- @ApiModelProperty(value = "菜单的响应动作类型, view 表示网页类型, click 表示点击类型, miniprogram 表示小程序类型")
- private String type = MenuType.CLICK.getType();
- @ApiModelProperty(value = "菜单 KEY 值, 用于消息接口推送, 不超过 128 字节")
- private String key;
- }
4 链接式菜单
- @Data
- @ApiModel(description = "用户点击菜单可打开链接")
- public class ViewButton extends Button {
- @ApiModelProperty(value = "菜单的响应动作类型, view 表示网页类型, click 表示点击类型, miniprogram 表示小程序类型")
- private String type = MenuType.VIEW.getType();
- @ApiModelProperty(value = "(view,miniprogram 类型必须) 网页 链接, 用户点击菜单可打开链接, 不超过 1024 字节. type 为 miniprogram 时, 不支持小程序的老版本客户端将打开本 url")
- private String url;
- }
5 含二级菜单的一级菜单
- @Data
- @ApiModel(description = "含二级菜单的一级菜单")
- public class ComplexButton extends Button {
- @ApiModelProperty(value = "二级菜单数组, 个数应为 1~5 个")
- private Button[] sub_button;
- }
6 最外层的菜单树
- @Data
- @ApiModel(description = "菜单树")
- public class Menu {
- @ApiModelProperty(value = "一级菜单数组, 个数应为 1~3 个")
- private Button[] button;
- }
2, 服务类
- public interface IMenuService {
- /**
- * 查询菜单
- *
- * @param accessToken: 访问凭据
- * @return: java.lang.Object
- */
- Object getMenu(String accessToken);
- /**
- * 删除菜单
- *
- * @param accessToken: 访问凭据
- * @return: com.zhengqing.demo.modules.weixin.model.WeixinResponseResult
- */
- WeixinResponseResult deleteMenu(String accessToken);
- /**
- * 创建菜单
- *
- * @param menu : 创建的菜单数据
- * @param accessToken : 访问凭据
- * @return: com.zhengqing.demo.modules.weixin.model.WeixinResponseResult
- */
- WeixinResponseResult createMenu(Menu menu, String accessToken);
- }
3, 服务实现类
- @Slf4j
- @Service
- public class MenuServiceImpl implements IMenuService {
- @Autowired
- private RestTemplate restTemplate;
- @Override
- public Object getMenu(String accessToken) {
- Object menu = restTemplate.getForObject(Constants.GET_MENU_URL.replace("ACCESS_TOKEN", accessToken), Object.class);
- return menu;
- }
- @Override
- public WeixinResponseResult deleteMenu(String accessToken) {
- WeixinResponseResult result = restTemplate.getForObject(Constants.DELETE_MENU_URL.replace("ACCESS_TOKEN", accessToken), WeixinResponseResult.class);
- return result;
- }
- @Override
- public WeixinResponseResult createMenu(Menu menu, String accessToken) {
- // 将菜单对象转换成 JSON 字符串
- String jsonMenu = JSON.toJSONString(menu);
- WeixinResponseResult result = restTemplate.postForObject(Constants.CREATE_MENU_URL.replace("ACCESS_TOKEN", accessToken), jsonMenu, WeixinResponseResult.class);
- return result;
- }
- }
六, 测试
1, 获取 access_token
- @Slf4j
- @RunWith(SpringRunner.class)
- @SpringBootTest(classes = DemoApplication.class)
- public class WeixinTest {
- @Autowired
- private IWeixinService weixinService;
- @Test // 获取 `access_token`
- public void getAccessToken() throws Exception {
- AccessTokenVO accessTokenVO = weixinService.getAccessToken(Constants.APP_ID, Constants.APP_SECRET);
- log.info("======================================== \n" + accessTokenVO.getAccess_token());
- }
- }
- 2,
创建自定义菜单
, 查询菜单, 删除菜单
注: 这里小编将获取到的 access_token 写死到常量 Constants.ACCESS_TOKEN 中做测试, 实际项目中可将 access_token 保存到缓存中, 每隔快到 2 个小时的时候去重新获取一次刷新缓存数据 ~
- @Slf4j
- @RunWith(SpringRunner.class)
- @SpringBootTest(classes = DemoApplication.class)
- public class MenuTest {
- @Autowired
- private IMenuService menuService;
- @Test // 查询菜单
- public void getMenu() {
- Object menu = menuService.getMenu(Constants.ACCESS_TOKEN);
- log.info("======================================== \n" + JSON.toJSONString(menu));
- }
- @Test // 删除菜单
- public void deleteMenu() {
- WeixinResponseResult result = menuService.deleteMenu(Constants.ACCESS_TOKEN);
- log.info("======================================== \n" + result);
- }
- @Test // 创建菜单
- public void createMenu() {
- WeixinResponseResult result = menuService.createMenu(createMenuTree(), Constants.ACCESS_TOKEN);
- log.info("======================================== \n" + result);
- }
- /**
- * 菜单数据
- */
- private Menu createMenuTree() {
- // 链接式菜单
- ViewButton btn11 = new ViewButton();
- btn11.setName("CSDN");
- btn11.setUrl("https://zhengqing.blog.csdn.net/");
- ViewButton btn12 = new ViewButton();
- btn12.setName("个人博客");
- btn12.setUrl("http://zhengqingya.gitee.io/blog/");
- // 点击式菜单
- ClickButton mainBtn2 = new ClickButton();
- mainBtn2.setName("点我吖");
- mainBtn2.setKey("hello");
- ViewButton btn31 = new ViewButton();
- btn31.setName("码云");
- btn31.setUrl("https://gitee.com/zhengqingya/projects");
- ViewButton btn32 = new ViewButton();
- btn32.setName("GitHub");
- btn32.setUrl("https://github.com/zhengqingya?tab=repositories");
- // 含二级菜单的一级菜单
- ComplexButton mainBtn1 = new ComplexButton();
- mainBtn1.setName("博客");
- mainBtn1.setSub_button(new ViewButton[]{btn11, btn12});
- ComplexButton mainBtn3 = new ComplexButton();
- mainBtn3.setName("仓库");
- mainBtn3.setSub_button(new ViewButton[]{btn31, btn32});
- Menu menu = new Menu();
- menu.setButton(new Button[]{mainBtn1, mainBtn2, mainBtn3});
- return menu;
- }
- }
最终自定义的菜单
本文案例 demo 源码
https://gitee.com/zhengqingya/java-workspace
来源: https://www.cnblogs.com/zhengqing/p/12207346.html