一、思路
- 获取全局ACCESS_TOKEN
创建微信自定义菜单的接口为:
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
故需要先取得获取全局ACCESS_TOKEN - 创建自定义菜单
关于自定义菜单的相关介绍请参考微信服务号开发官方文档:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013 - 向微信接口发送创建自定义菜单的请求
二、具体代码
- 开发过程中用到的微信接口类 Constant
其中 PropertyUtil.getProperty 方法如下public class Constant { // 微信服务号开发 账号 public static final String appid = PropertyUtil.getProperty("appId"); // 微信服务号开发 密码 public static final String secret = PropertyUtil.getProperty("appSecret"); // 微信创建菜单接口 public static final String createMenu = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; // 微信基础支持access_token接口,也称为全局access_token接口 public static final String access_token = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; }
import java.util.Properties; import org.apache.log4j.Logger; import org.springframework.core.io.ClassPathResource; /** * 获取配置文件信息 * @author liguiji * */ public class PropertyUtil { private static final Logger LOG = Logger.getLogger(PropertyUtil.class); private static final Properties props = new Properties(); static { init(); } public static Properties getProperties() { return props; } private static void init() { String mpConfig = "/mp.properties"; ClassPathResource cr = null; try { cr = new ClassPathResource(mpConfig); props.load(cr.getInputStream()); LOG.info("装入default主配置文件:" + mpConfig); } catch (Exception e) { LOG.info("装入default主配置文件" + mpConfig + "失败!", e); } LOG.info("系统配置属性装载完毕"); LOG.info("******************属性列表***************************"); for(String propertyName : props.stringPropertyNames()){ LOG.info(" "+propertyName+" = "+props.getProperty(propertyName)); } LOG.info("***********************************************************"); } public static String getProperty(String name) { String value = props.getProperty(name); return value; } public static void setProperty(String name, String value) { props.setProperty(name, value); } }
- 获取全局access_token
public static AccessToken getAccessToken() { AccessToken accessToken = null; // 将接口中的APPID和APPSECRET替换为自己的appid和secret String requestUrl = Constant.access_token.replace("APPID", Constant.appid).replace("APPSECRET", Constant.secret); // 想微信发送获取AccessToken的请求 JSONObject jsonObject = HTTPSUtil.httpRequest(requestUrl, "GET", null); // 如果请求成功 if (null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in")); } catch (JSONException e) { accessToken = null; // 获取token失败 System.out.println("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg")); } } return accessToken; }
其中 AccessToken 类如下
public class AccessToken { private String token; //token private int expiresIn; //凭证有效时间 ,单位秒 public String getToken() { return token; } public void setToken(String token) { this.token = token; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } }
httpRequest方法如下
public class HTTPSUtil { private static Logger log = LoggerFactory.getLogger(HTTPSUtil.class); /** * 发起https请求并获取结果 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 设置请求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 当有数据需要提交时 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意编码格式,防止中文乱码 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 将返回的输入流转换成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 释放资源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { log.error("Weixin server connection timed out."); } catch (Exception e) { log.error("https request error:{}", e); } return jsonObject; } }
- 创建菜单类
菜单基类 ButtonBasepublic class ButtonBase { // 菜单名称 private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
二级菜单 CommonButton
public class CommonButton extends ButtonBase { // 菜单名称 private String name; // 菜单类型 private String type; // 菜单key值 private String key; // 菜单url private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } 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; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
一级菜单 ComplexButton
public class ComplexButton extends ButtonBase{ // 菜单名称 private String name; // 二级菜单数组 private ButtonBase[] sub_button; // 菜单key值 private String key; // 菜单类型 private String type; // 菜单url private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } public ButtonBase[] getSub_button() { return sub_button; } public void setSub_button(ButtonBase[] sub_button) { this.sub_button = sub_button; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
菜单类 Menu
public class Menu { // 一级菜单数组 private ComplexButton[] button; public ComplexButton[] getButton() { return button; } public void setButton(ComplexButton[] button) { this.button = button; } }
三、因为基础支持ACCESS_TOKEN每天只能请求2000次,每个ACCESS_TOKEN的失效是2小时,当ACCESS_TOKEN的请求次数超过2000次后,会使得服务号无法正常运行,所以最好每隔1到2小时请求一次,期间所有需要ACCESS_TOKEN均使用该ACCESS_TOKEN,而不是请求新的ACCESS_TOKEN。此处我是将ACCESS_TOKEN存在数据库中,每隔1小时重新请求一次,网上有中控服务器的方法,可自行研究。代码如下,定时任务使用的是spring的task定时任务调度。
import org.springframework.scheduling.annotation.Scheduled;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import *.Constant; // 此处路径涉及到公司隐私,故用*代替
import *.AccessToken;
import *.ServiceAccountService;
/**
* 每隔1个小时更新一次access_token,并保存在数据库中
*
* @author Administrator
*
*/
public class GetAccessToken {
// spring service 类,用来跟数据库交互
private static ServiceAccountService serviceAccountService;
public static void setServiceAccountService(
ServiceAccountService serviceAccountService) {
GetAccessToken.serviceAccountService = serviceAccountService;
}
/**
* 从微信接口获取accessToken
* @return
*/
public static AccessToken getAccessToken() {
AccessToken accessToken = null;
// 将接口中的APPID和APPSECRET替换为自己的appid和secret
String requestUrl = Constant.access_token.replace("APPID",
Constant.appid).replace("APPSECRET", Constant.secret);
// 想微信发送获取AccessToken的请求
JSONObject jsonObject = HTTPSUtil.httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
accessToken = new AccessToken();
accessToken.setToken(jsonObject.getString("access_token"));
accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
accessToken = null;
// 获取token失败
System.out.println("获取token失败 errcode:{} errmsg:{}"
+ jsonObject.getInt("errcode")
+ jsonObject.getString("errmsg"));
}
}
return accessToken;
}
/**
* 从本地数据库获取accessToken
* @return
*/
public static AccessToken getAccessTokenFromLocal(){
return serviceAccountService.getAccessTokenFromLocal();
}
/**
* 每隔1小时50分钟更新一次
*/
@Scheduled(cron = "0 0 */1 * * ?")
public static void updateAccessToken(){
AccessToken accessToken = getAccessToken();
if(serviceAccountService.updateAccessToken(accessToken)>0){
System.out.println("更新accessToken成功");
}else{
System.out.println("更新accessToken失败");
}
}
}