·后端配置详解
微信公众平台文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277
注:本博文使用的的框架结构是 SpringCloud+Springboot+Mybatis-Plus
1.yml常量配置
#公众号
wxpublic:
appid: x #公众号appid
secret: x #公众号secret
template_id:
remind: x #公众号模板一id(名字见名知意即可)
reply: x #公众号模板二id
#rediskey名字
rediskey:
access_token: WechatPublic:access_token #access_token 存储在redis的key名字
2.1 获取access_token
若是不同业务场景每次拿accesstoken都去请求微信,重复获取将导致上次获取的access_token失效,需定时刷新,保证一致性。access_token默认有7200秒(俩小时)过期时间,所以采取redis将其缓存起来,并设置少于7200秒的时间。每次取用access_token之前都先从redis中获取,若已过期再重新获取。
//============公众号配置参数
@Resource
private RedisTemplate<String, Object> stringRedisTemplate;
@Value("${wxpublic.appid}")
private String wxpublicAppId;
@Value("${wxpublic.secret}")
private String wxpublicAppSecret;
@Value("${wxpublic.rediskey.access_token}")
private String wxpublicAccessTokenRediskey;
//通知模板id
@Value("${wxpublic.template_id.remind}")
private String wxpublicRemindTemplateId;
//回复模板id
@Value("${wxpublic.template_id.reply}")
private String wxpublicReplyTemplateId;
public ResultVO<?> getWxPublicAccessToken() {
//redis中获取access_token
String tokenRedis = (String) stringRedisTemplate.opsForValue().get(wxpublicAccessTokenRediskey);
if (tokenRedis == null) {
//若没有(从未,或者已过期),请求微信获取
//请求微信
JSONObject myAccessToken = WechatApiUtil.getMyAccessToken(wxpublicAppId, wxpublicAppSecret);
if (myAccessToken.containsKey("access_token")) {
String newAccessToken = myAccessToken.getString("access_token");
stringRedisTemplate.opsForValue().set(wxpublicAccessTokenRediskey, newAccessToken, 7000, TimeUnit.SECONDS);
log.info("公众号请求wechat request获取access_token");
//请求成功将记录保存到数据库中
WxApplet entity = new WxApplet();
entity.setNo(CodeNoEnum.WX_APPLET.getTableNO() + CommentUtil.createNo());
entity.setCategory(WeChatRequestTypeEnum.GET_WX_PUBLIC_ACCESS_TOKEN.getCode());
entity.setAccessToken(newAccessToken);
entity.setReturnResult(myAccessToken.toJSONString());
wxAppletMapper.insert(entity);
return ResultVOUtil.returnSuccess(newAccessToken);
} else {
//请求有误,返回错误信息
//请求有无也将记录保存到数据库中
log.info("获取access_token失败");
WxApplet entity = new WxApplet();
entity.setNo(CodeNoEnum.WX_APPLET.getTableNO() + CommentUtil.createNo());
entity.setCategory(WeChatRequestTypeEnum.GET_WX_PUBLIC_ACCESS_TOKEN.getCode());
entity.setReturnResult(myAccessToken.toJSONString());
wxAppletMapper.insert(entity);
return ResultVOUtil.returnFail(myAccessToken.getString("errmsg"));
}
} else {
return ResultVOUtil.returnSuccess(tokenRedis);
}
}
封装的请求微信方法(使用hutool工具包):
工具类方法:
//获取AccessToken
public static JSONObject getMyAccessToken(String appId, String appSecret) {
String apiUrl = StrUtil.format(
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}",
appId, appSecret
);
String body = HttpRequest.get(apiUrl).execute().body();
JSONObject jsonObject = myThrowErrorMessageIfExists(body);
return jsonObject;
}
2.2根据网页授权code 换取公众号用户openid及相关信息
微信公众号消息推送以及涉及到用户的操作均需要用户openid,需要请求微信获取。
为保持session_key的。最新时效性,也将获取到的用户openid存入redis,但是每次取用之前进行判断,若不存在就存入redis,若存在,先删除然后重新获取对其更新。
public ResultVO<?> wechatPbulicGetOpenIdByCode(WxAppletDTO dto) {
//请求微信获得openid和sessionkey
JSONObject myOpenIdByCode = WechatApiUtil.wechatPbulicGetOpenIdByCode(wxpublicAppId, wxpublicAppSecret, dto.getCode());
if (myOpenIdByCode.containsKey("errcode")) {
//若请求微信失败(无效code情况)
//失败也要每次请求记录到数据库中
WxApplet entity = new WxApplet();
entity.setNo(CodeNoEnum.WX_APPLET.getTableNO() + CommentUtil.createNo());
entity.setWechatCode(dto.getCode());
entity.setCategory(WeChatRequestTypeEnum.GET_WX_OPENID.getCode());
entity.setReturnResult(myOpenIdByCode.toJSONString());
wxAppletMapper.insert(entity);
return ResultVOUtil.returnFail(-1, "fail", myOpenIdByCode);
}
//正常请求到了微信
//用户唯一标识
String openid = myOpenIdByCode.getString("openid");
//临时会话秘钥
String access_token = myOpenIdByCode.getString("access_token");
//每次成功请求记录到数据库中
WxApplet entity = new WxApplet();
entity.setNo(CodeNoEnum.WX_APPLET.getTableNO() + CommentUtil.createNo());
entity.setWechatCode(dto.getCode());
entity.setCategory(WeChatRequestTypeEnum.GET_WX_OPENID.getCode());
entity.setOpenid(openid);
entity.setAccessToken(access_token);
entity.setReturnResult(myOpenIdByCode.toJSONString());
wxAppletMapper.insert(entity);
//根据openid为key查询skey_redis是否存在
String skey_redis = (String) stringRedisTemplate.opsForValue().get(openid);
if (!StringUtils.isEmpty(skey_redis)) {
//存在,删除此数据,重新获取并返回(保持session_key的最新时效性)
stringRedisTemplate.delete(skey_redis);
}
//缓存一份新的
//uuid生成唯一key
String skey = UUID.randomUUID().toString();
JSONObject sessionObj = new JSONObject();
sessionObj.put("openId", openid);
sessionObj.put("access_token", access_token);
//[更新]以openid为key,唯一sky为value,存redis
stringRedisTemplate.opsForValue().set(openid, skey);
//重新生成一份带有openid和session_key的数据
stringRedisTemplate.opsForValue().set(skey, sessionObj.toJSONString());
return ResultVOUtil.returnSuccess(myOpenIdByCode);
}
工具类方法:
//微信公众号通过网页授权code获取openid和session_key
public static JSONObject wechatPbulicGetOpenIdByCode(String appId, String appSecrct, String code) {
String apiUrl = StrUtil.format(
" https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code",
appId, appSecrct, code
);
String body = HttpRequest.get(apiUrl).execute().body();
return throwErrorMessageIfExists(body);
}
工具类打印请求日志方法:
//微信请求异常处理
public static JSONObject throwErrorMessageIfExists(String body) {
String callMethodName = (new Throwable()).getStackTrace()[1].getMethodName();
log.info("#请求微信方法名:{},body={}", callMethodName, body);
JSONObject jsonObject = JSON.parseObject(body);
return jsonObject;
}
附:通用vo类
/*
* @JsonInclude(JsonInclude.Include.NON_NULL)标记是jackson包提供的json序列化方法,
* 已经集成于Springboot2.0中,此方法的配置意在可以对实体json序列化的时候进行对应的数值处理,
//将该标记放在属性上,如果该属性为NULL则不参与序列化
//如果放在类上边,那对这个类的全部属性起作用
//Include.Include.ALWAYS 默认
//Include.NON_DEFAULT 属性为默认值不序列化
//Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
//Include.NON_NULL 属性为NULL 不序列化
* */
@Data //lombook
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultVO<T> implements Serializable {
private static final long serialVersionUID = -3032060746893382446L;
// 错误码
private Integer code;
// 提示信息
private String msg;
// 具体内容
private T data;
}
通用成功/失败返回类:
public class ResultVOUtil {
public static ResultVO<?> returnSuccess(Object object) {
ResultVO<Object> resultVO = new ResultVO<Object>();
resultVO.setCode(0);
resultVO.setMsg("success");
resultVO.setData(object);
return resultVO;
}
public static ResultVO<?> returnSuccess(String key, Object object) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(0);
resultVO.setMsg("success");
Map<String, Object> map = new HashMap<>();
map.put(key, object);
resultVO.setData(map);
return resultVO;
}
public static ResultVO<?> returnSuccess() {
return returnSuccess(null);
}
public static ResultVO<?> returnFail(Integer code, String msg) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(code);
resultVO.setMsg(msg);
return resultVO;
}
public static ResultVO<?> returnFail(Integer code, String msg,Object object) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(code);
resultVO.setMsg(msg);
resultVO.setData(object);
return resultVO;
}
public static ResultVO<?> returnFail(String msg) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(ResultEnum.FAIL.getCode());
resultVO.setMsg(msg);
return resultVO;
}
public static ResultVO<?> returnFail() {
return returnFail(ResultEnum.FAIL);
}
public static ResultVO<?> returnFail(ResultEnum resultEnum) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(resultEnum.getCode());
resultVO.setMsg(resultEnum.getMessage());
return resultVO;
}
}