刚刚写了一个微信开发的项目,今天就来讲一讲微信开发的内容。
微信登录
首先微信开发最开始的部分当然是微信登录
需要注意的地方,一定要把白名单开了,该配置的东西配置好。
微信登录流程:
1、引导用户进入授权页面同意授权,获取code
2、通过code换取网页授权access_token(与基础支持中的access_token不同)
3、如果需要,开发者可以刷新网页授权access_token,避免过期
4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
步骤1为前端处理,前端将获取的code调用后端写好的登录接口,在这个接口中
接口如下
// 获取当前登录人的openid officialAppId和officialAppSecret为项目公众号唯一标识和公众号的appsecret
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+officialAppId+"&secret="+officialAppSecret+
"&code="+code+"&grant_type=authorization_code";
String result = HttpUtils.get(url);
OfficialVO wxCodeResponse = JSONObject.parseObject(result, OfficialVO.class);
// 公众号登录token获取错误信息
if(StringUtils.isNotBlank(wxCodeResponse.getErrmsg())){
return GeneralResult.error(ErrorCode.NOT_TOKEN);
}
String path = "https://api.weixin.qq.com/sns/userinfo?access_token="+wxCodeResponse.getAccess_token()+"&openid="+wxCodeResponse.getOpenid()+"&lang=zh_CN";
String s = HttpUtils.get(path);
// 用户信息
UserinformationVO userinfoVO = JSONObject.parseObject(s, UserinformationVO.class);
// 用户信息获取的错误信息
if(StringUtils.isNotBlank(userinfoVO.getErrmsg())){
return GeneralResult.error(ErrorCode.NOT_USER);
}
// 后面就是项目中用微信表关联用户表的信息详细步骤了
OfficialVO类封装的字段
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class OfficialVO {
private String access_token;
private Integer expires_in;
private String refresh_token;
private String openid;
private String scope;
private Integer errcode;
private String errmsg;
private String subscribe;
private String unionid;
}
UserinformationVO封装的字段
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UserinformationVO {
private String openid;
private String nickname;
private Integer sex;
private String province;
private String city;
private String country;
private String headimgurl;
private String unionid;
private String errmsg;
private Integer errcode;
}
上面就是微信登录整体功能,具体开发文档点击查看
因为这个项目功能是进行投票功能,为了防止恶意的调用该接口,进行浏览器类型判断
/**
* 通过请求头判断是否是微信内置浏览器,是否是在微信内打开
* @param request
* @return
*/
@RequestMapping(value = "/hello")
public String hello(HttpServletRequest request){
String ua = request.getHeader("user-agent")
.toLowerCase();
if (ua.indexOf("micromessenger") <= 0){
return "不是微信内置浏览器";
}
return "微信内置浏览器";
}
这样就可以将不是微信内置浏览器的请求拦截下来。
微信开发批量获取关注的用户列表(缺陷)
该方法上限是10000条数据,并且其中参数next_openid是第一个拉取的OPENID,不填默认从头开始拉取
https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
当看到这个方法,正常人都会觉得这个openid是按创建时间排序的,直接用最后一个openid获取看有没有新的关注用户就可以,其实这个不是按创建时间排序,并且官方文档没有任何的提示。
在拉取关注用户时需要从头拉去关注的用户,如果用户存在则更新,用户不存在则保存。
配合该接口https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN,可以获取更加全的用户信息,方便操作
以下方法是将关注的用户信息,批量保存或更新到本地数据库中
/**
* 批量获取用户数据
* @return
*/
public void userInfos(){
subscriptionDao.deleteUnioniid();
LOGGER.info("开始调用订阅号接口获取用户信息:"+ DateUtils.getCurrentTimeStr());
// 获取普通token成功
String commonToken = null;
try {
commonToken = cacheService.get(SUBSCRIPTION_TOKEN, String.class);
} catch (Exception e) {
LOGGER.error("Redis获取公众号标识异常:{}",e);
}
if(commonToken == null) {
// 调用通用token
String url1 = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + subscriptionAppId + "&secret=" + subscriptionAppSecret;
String result1 = HttpUtils.get(url1);
LoginVO commonLoginVO = JSONObject.parseObject(result1, LoginVO.class);
commonToken = commonLoginVO.getAccess_token();
// 存储token
try {
cacheService.setWithExpireTime(SUBSCRIPTION_TOKEN, commonToken, HOURS, TimeUnit.HOURS);
} catch (Exception e) {
LOGGER.error("Redis存储异常{}",e);
}
}
// 批量拉去关注用户openid,NEXT_OPENID为头openid
String path = "https://api.weixin.qq.com/cgi-bin/user/get?access_token="+commonToken+"&next_openid=";
String s1 = HttpUtils.get(path);
Map map2 = JSONObject.parseObject(s1, Map.class);
Integer count = Integer.valueOf(map2.get("count").toString());
String o = map2.get("data").toString();
Map map1 = JSONObject.parseObject(o, Map.class);
String openidS = map1.get("openid").toString();
List<String> list1 = JSONObject.parseObject(openidS, List.class);
List<UserInfosVO> userinfos = new ArrayList<>();
for(String str:list1){
userinfos.add(UserInfosVO.builder().openid(str).lang("zh_CN").build());
if(userinfos.size()%100==0){
setUser(userinfos);
userinfos.clear();
}
}
if(userinfos.size()!=0){
setUser(userinfos);
}
// 用户数据保存完成
if(count < 10000){
return;
}else{
String nextOpenid = (String) map2.get("next_openid");
circulation(nextOpenid);
}
}
/**
* 循环调用方法获取用户openid 关注用户大于10000时使用
* @param nextOpenid
*/
public void circulation(String nextOpenid){
// 获取普通token
String commonToken = null;
try {
commonToken = cacheService.get(SUBSCRIPTION_TOKEN, String.class);
LOGGER.info("成功:{}",commonToken);
} catch (Exception e) {
LOGGER.error("Redis获取公众号标识异常:{}",e);
}
if(commonToken == null) {
// 获取通用token
String url1 = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + subscriptionAppId + "&secret=" + subscriptionAppSecret;
String result1 = HttpUtils.get(url1);
LoginVO commonLoginVO = JSONObject.parseObject(result1, LoginVO.class);
commonToken = commonLoginVO.getAccess_token();
// 存储token到redis中
try {
cacheService.setWithExpireTime(SUBSCRIPTION_TOKEN, commonToken, HOURS, TimeUnit.HOURS);
} catch (Exception e) {
LOGGER.error("Redis存储异常{}",e);
}
}
// 批量拉去关注用户openid,NEXT_OPENID为头openid
// https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
String path = "https://api.weixin.qq.com/cgi-bin/user/get?access_token="+commonToken+"&next_openid="+nextOpenid;
String s1 = HttpUtils.get(path);
Map map2 = JSONObject.parseObject(s1, Map.class);
Integer count = Integer.valueOf(map2.get("count").toString());
String o = map2.get("data").toString();
Map map1 = JSONObject.parseObject(o, Map.class);
String openidS = map1.get("openid").toString();
List<String> list1 = JSONObject.parseObject(openidS, List.class);
List<UserInfosVO> userinfos = new ArrayList<>();
for(String str:list1){
userinfos.add(UserInfosVO.builder().openid(str).lang("zh_CN").build());
if(userinfos.size()%100==0){
setUser(userinfos);
userinfos.clear();
}
}
if(userinfos.size()!=0){
setUser(userinfos);
}
// 用户数据保存完成
if(count < 10000){
return;
}else{
String Openid = (String) map2.get("next_openid");
circulation(Openid);
}
}
/**
* 保存用户信息
* @param userinfos
*/
public void setUser(List<UserInfosVO> userinfos){
String commonToken = null;
try {
commonToken = cacheService.get(SUBSCRIPTION_TOKEN, String.class);
LOGGER.info("获取普通token成功:{}",commonToken);
} catch (Exception e) {
LOGGER.error("Redis获取公众号标识异常:{}",e);
}
if(commonToken == null) {
// 调用通用token
String url1 = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + subscriptionAppId + "&secret=" + subscriptionAppSecret;
String result1 = HttpUtils.get(url1);
LoginVO commonLoginVO = JSONObject.parseObject(result1, LoginVO.class);
commonToken = commonLoginVO.getAccess_token();
// 存储token
try {
cacheService.setWithExpireTime(SUBSCRIPTION_TOKEN, commonToken, HOURS, TimeUnit.HOURS);
} catch (Exception e) {
LOGGER.error("Redis存储异常{}",e);
}
}
Map<String,Object> map = new HashMap<>();
map.put("user_list",userinfos);
String paramsJson = JSON.toJSONString(map);
// 批量获取用户数据
String url = "https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token="+commonToken;
String post = HttpUtils.post(url, paramsJson);
Map map3 = JSONObject.parseObject(post, Map.class);
String user_info_list = map3.get("user_info_list").toString();
JSONObject.parseObject(post, Map.class);
JSONArray jsonArray= JSONArray.parseArray(user_info_list);
for(int i=0;i<jsonArray.size();i++){
SubscriptionUserPO subscriptionUser = JSONObject.parseObject(jsonArray.get(i).toString(), SubscriptionUserPO.class);
SubscriptionUserPO subscriptionUserPO = subscriptionDao.selectOne(new QueryWrapper<SubscriptionUserPO>().lambda().eq(SubscriptionUserPO::getOpenid, subscriptionUser.getOpenid()));
if(subscriptionUserPO ==null){
subscriptionUser.setCreateTime(LocalDateTime.now());
subscriptionDao.save(subscriptionUser);
}
// // 更新用户数据操作
// else{
// subscriptionUser.setUpdateTime(LocalDateTime.now());
// subscriptionUser.setSubscriptionUserId(subscriptionUser.getSubscriptionUserId());
// subscriptionDao.updateById(subscriptionUser);
// }
}
}
微信页面跳转小程序
需要获取一个公众号标识,一定要开通微信公众号跳转小程序的配置,血的教训
获取公众号标识方法如下
/**
* 获取公众号标识
* @return
* url 请求路径
*/
public GeneralResult jsapi(String url){
String commonToken = null;
try {
commonToken = cacheService.get(OFFICIAL_TOKEN, String.class);
LOGGER.info("获取普通token成功:{}",commonToken);
} catch (Exception e) {
LOGGER.error("Redis获取公众号标识异常:{}",e);
}
if(commonToken == null){
// 获取普通token
String url1 = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + officialAppId + "&secret=" + officialAppSecret;
String result1 = HttpUtils.get(url1);
LOGGER.info("获取公众号标识result1:{}",result1);
LoginVO commonLoginVO = JSONObject.parseObject(result1, LoginVO.class);
commonToken = commonLoginVO.getAccess_token();
try {
cacheService.setWithExpireTime(OFFICIAL_TOKEN, commonToken, HOURS, TimeUnit.HOURS);
} catch (Exception e) {
LOGGER.error("Redis存储公众号标识异常:{}",e);
}
}
// 获取jsapi_ticket
String url2 = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+commonToken+"&type=jsapi";
String result = HttpUtils.get(url2);
LOGGER.info("获取公众号标识result{}",result);
JsapiVO jsapiVO = JSONObject.parseObject(result, JsapiVO.class);
Map<String, String> sign = sign(jsapiVO.getTicket(), url);
return GeneralResult.response(sign);
}
public static Map<String, String> sign(String jsapi_ticket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
System.out.println(string1);
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
ret.put("url", url);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
return ret;
}
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}