众所周知,微信订阅号是没有网页授权接口权限的,因此不可以直接判断用户的关注状态,但并不意味没有其他方法可以实现,下面就是一种还不错的方法。
首先你需要一个服务号,并通过微信认证,然后在微信开放平台http://open.weixin.qq.com注册一个帐号,将你的服务号和订阅号都绑定到开放平台,绑定后如下图:
绑定后,订阅号和服务号将通过unionid关联用户信息,也就是说同个openid调用服务号拉取用户信息接口和订阅号获取用户信息接口都将返回相同的unionid
具体实现步骤和原理:
1.同步订阅号用户列表和用户信息(如果您的订阅号已经在运营),保存到数据库表(包括unionid、关注状态等)
2.用服务号做网页授权,拉取用户信息,获取unionid
3.根据unionid在数据表查询用户关注状态
4.开发订阅号事件接口,通过取消关注事件更新数据表用户关注状态
同步订阅号用户列表和用户信息代码
@RequestMapping(value="/syncOpenid")
@ResponseBody
public String syncWxOpenid(final HttpServletRequest request){
if(WeixinUtil.syncOpenidStatus){
return JSON.toJSONString(new DwzAjaxResult("300", "同步已在进行", "", "", ""));
}
final WxToken token = wxTokenService.getWxTokenByKey(WX_TOKEN_KEY.NORMAL_ACCESS_TOKEN.getKey());
if(token == null || !token.isActive() || token.getToken_value() == null || "".equals(token.getToken_value())){
return JSON.toJSONString(new DwzAjaxResult("300", "获取AccessToken失败", "", "", ""));
}
//标识Openid已经在同步状态
WeixinUtil.syncOpenidStatus = true;
//获取最后一个Openid,从最后一个开始
//final WxOpenid lastOpenid = wxOpenidService.getLatestOpenid();
Runnable orun = new Runnable() {
private String nextOpenId;
private WxToken tk;
private boolean flag = true;
@Override
public void run() {
if(tk == null){
//初始化token
tk = token;
}
// if(nextOpenId == null && lastOpenid != null && lastOpenid.getOpen_id() != null){
// nextOpenId = lastOpenid.getOpen_id();
// }
System.out.println("= Sync openid start =");
while(flag){
//调用微信用户列表接口
try {
JSONObject jo = getUserList(nextOpenId);
if(jo != null && !jo.containsKey("errcode")){
System.out.println("GET OPENID LIST,total="+jo.getLongValue("total")+",count="+jo.getIntValue("count")+",next_openid="+jo.getString("next_openid"));
//设置下一个查询起始Openid
nextOpenId = jo.getString("next_openid");
JSONObject data = jo.getJSONObject("data");
if(data != null){
//获取open id列表
JSONArray openidArray = data.getJSONArray("openid");
wxOpenidService.addWxOpenidList(openidArray);
}
if(jo.getIntValue("count") == 0 || nextOpenId == null || "".equals(nextOpenId)){
shutdown();
}
}
} catch (Exception e) {
e.printStackTrace();
request.getServletContext().setAttribute("openid_sync", "0");
}
}
}
public void shutdown(){
WeixinUtil.syncOpenidStatus = false;
System.out.println("= Sync openid complete =");
flag = false;
}
};
Thread th = new Thread(orun);
th.start();
DwzAjaxResult dwzResult = new DwzAjaxResult("200", "同步已开始至后台进行,请耐心等待!", "wxOpenidList", "", "");
return JSON.toJSONString(dwzResult);
}
private JSONObject getUserList(String nextOpenid){
JSONObject json = WeixinUtil.getUserList(nextOpenid, getWxAccessToken());
if(json.containsKey("errcode")){
if(json.getString("errmsg").indexOf("access_token is invalid")!=-1){
//access token过期,则需要刷新access token
JSONObject jo = WeixinUtil.getAccessToken();
if(!jo.containsKey("errcode")){
WxToken token = new WxToken();
token.setToken_key(WX_TOKEN_KEY.NORMAL_ACCESS_TOKEN.getKey());
token.setCreate_time(System.currentTimeMillis());
token.setToken_value(jo.getString("access_token"));
token.setExpire_seconds(jo.getInteger("expires_in"));
wxTokenService.addNewWxTokenOrUpdate(token);
json = WeixinUtil.getUserList(nextOpenid, token.getToken_value());
}else{
System.out.println("REGET ACESS TOKEN ERROR:" + JSON.toJSONString(jo));
}
}
}
return json;
}
@RequestMapping(value="/syncUserInfo")
@ResponseBody
public String syncUserInfo(final HttpServletRequest request){
if(WeixinUtil.syncUserInfoStatus){
return JSON.toJSONString(new DwzAjaxResult("300", "同步已在进行", "", "", ""));
}
final WxToken token = wxTokenService.getWxTokenByKey(WX_TOKEN_KEY.NORMAL_ACCESS_TOKEN.getKey());
if(token == null || !token.isActive() || token.getToken_value() == null || "".equals(token.getToken_value())){
return JSON.toJSONString(new DwzAjaxResult("300", "获取AccessToken失败", "", "", ""));
}
//标识Openid已经在同步状态
WeixinUtil.syncUserInfoStatus = true;
Runnable orun = new Runnable() {
private WxToken tk;
private boolean flag = true;
private long page = 1;
private int psize = 100;
@Override
public void run() {
if(tk == null){
//初始化token
tk = token;
}
System.out.println("= Sync userinfo start =");
while(flag){
try {
List list = wxOpenidService.getWxOpenidsWithoutUnionid(page, psize);
if(list.size() == 0 || list.size() < psize){
shutdown();
}
for(WxOpenid wo : list){
//调用微信用户基本信息接口
WxOpenid userInfo = getUserInfo(wo.getOpenid());
wxOpenidService.addNewWxOpenidOrUpdate(userInfo);
}
page += 1;
} catch (Exception e) {
e.printStackTrace();
request.getServletContext().setAttribute("userinfo_sync", "0");
}
}
}
public void shutdown(){
flag = false;
WeixinUtil.syncUserInfoStatus = false;
System.out.println("= Sync userinfo complete =");
}
};
Thread th = new Thread(orun);
th.start();
DwzAjaxResult dwzResult = new DwzAjaxResult("200", "同步已开始至后台进行,请耐心等待!", "wxOpenidList", "", "");
return JSON.toJSONString(dwzResult);
}
private WxOpenid getUserInfo(String openid){
JSONObject json = WeixinUtil.getUserInfo(openid, getWxAccessToken());
if(json.containsKey("errcode")){
if(json.getString("errmsg").indexOf("access_token is invalid")!=-1){
//access token过期,则需要刷新access token
JSONObject jo = WeixinUtil.getAccessToken();
if(!jo.containsKey("errcode")){
WxToken token = new WxToken();
token.setToken_key(WX_TOKEN_KEY.NORMAL_ACCESS_TOKEN.getKey());
token.setCreate_time(System.currentTimeMillis());
token.setToken_value(jo.getString("access_token"));
token.setExpire_seconds(jo.getInteger("expires_in"));
wxTokenService.addNewWxTokenOrUpdate(token);
json = WeixinUtil.getUserInfo(openid, token.getToken_value());
}else{
System.out.println("REGET ACESS TOKEN ERROR:" + JSON.toJSONString(jo));
}
}
}
return JSONObject.parseObject(json.toJSONString(), WxOpenid.class);
}
private String getWxAccessToken(){
WxToken token = wxTokenService.getWxTokenByKey(WX_TOKEN_KEY.NORMAL_ACCESS_TOKEN.getKey());
if(token != null){
return token.getToken_value();
}
return "";
}
获取用户列表
public void addWxOpenidList(JSONArray openidArray) {
WxOpenid wxo = new WxOpenid();
wxo.setSubscribe(1); //只添加openid列表,默认为关注状态为:已关注
for(int i=0; i list = wxOpenidMapper.selectByCondition(wxo);
if(list.size() > 0){
//union id 已经存在, 若果Open id不存在,则添加open id
wxo = list.get(0);
if(wxo.getSubscribe() != 1){
wxo.setSubscribe(1);
wxOpenidMapper.update(wxo);
}
}else{
wxOpenidMapper.save(wxo);
}
}
}
public static JSONObject getUserInfo(String openid, String accessToken){
String resp = CommonUtil.httpsRequest("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openid, "GET", null);
JSONObject jo = JSONObject.parseObject(resp);
if(jo.containsKey("errcode")){
log.error("[WAP] GET USER INFO ERROR: " + jo.getString("errcode") + ":" + jo.getString("errmsg"));
}
return jo;
}
保存或更新token
public WxToken addNewWxTokenOrUpdate(WxToken wxToken) {
WxToken token = wxTokenMapper.selectByKey(wxToken.getToken_key());
if(token != null){
token.setCreate_time(System.currentTimeMillis());
//token已存在,则更新token
wxToken.setId(token.getId());
wxTokenMapper.update(wxToken);
//如果更新的是noral_access_token,则需要删除失效的js ticket
if(WX_TOKEN_KEY.NORMAL_ACCESS_TOKEN.getKey().equals(wxToken.getToken_key())){
WxToken ticket = wxTokenMapper.selectByKey(WX_TOKEN_KEY.JSSDK_TICKET.getKey());
if(ticket != null){
wxTokenMapper.delete(ticket.getId());
}
}
}else{
token = new WxToken();
token.setExpire_seconds(wxToken.getExpire_seconds());
token.setToken_key(wxToken.getToken_key());
token.setToken_value(wxToken.getToken_value());
token.setCreate_time(System.currentTimeMillis());
wxTokenMapper.save(wxToken);
}
return wxToken;
}
public List getWxOpenidsWithoutUnionid(long page, int psize) {
QueryPager pager = new QueryPager();
pager.setPageNum(page);
pager.setNumPerPage((long)psize);
WxOpenid wxo = new WxOpenid();
wxo.setPager(pager);
return wxOpenidMapper.selectWithoutUnionid(wxo);
}
public WxToken getWxTokenByKey(String key) {
WxToken token = wxTokenMapper.selectByKey(key);
boolean isSave = false;
if(token == null || !token.isActive()){
JSONObject tokenJo = WeixinUtil.getAccessToken();
if(tokenJo != null && tokenJo.containsKey("access_token")){
if(token == null){
//保存新token
token = new WxToken();
isSave = true;
}
token.setExpire_seconds(tokenJo.getInteger("expires_in"));
token.setToken_value(tokenJo.getString("access_token"));
token.setToken_key(key);
token.setCreate_time(System.currentTimeMillis());
if(isSave){
wxTokenMapper.save(token);
}else{
wxTokenMapper.update(token);
}
}
}
return token;
}
@RequestMapping("/weixinConnector" + GlobalConstant.STATIC_SUFFIX)
public void weixinConnector(HttpServletRequest request, HttpServletResponse response){
if(request.getMethod().equalsIgnoreCase(RequestMethod.GET.toString())){
//GET处理签名验证
String signature = request.getParameter("signature");
String nonce = request.getParameter("nonce");
String timestamp = request.getParameter("timestamp");
String echostr = request.getParameter("echostr");
WxSignature sign = new WxSignature(signature, nonce, timestamp);
if(WeixinUtil.verifySignature(sign)){
printJson(echostr, response);
return;
}
}else{
//POST处理菜单创建、消息回复等
WxNotify notify = WeixinUtil.parseNotify(request);
String msgType = notify.getMsgType();
String fromOpenid = notify.getFromUserName();
//处理事件推送
if("event".equals(msgType)){
if("subscribe".equals(notify.getEvent())){
//关注自动回复消息(暂无实现回复图片、视频、语音等)
WxConfig config = wxConfigService.getWxConfigByKey(WX_CONFIG_KEY.SUBSCRIBE_REPLY_KEY.getKey());
if(config != null && config.getValue() != null){
String keyValue = config.getValue();
if(WX_CONFIG_KEY.SUBSCRIBE_REPLY_TEXT.getKey().equals(keyValue)){
//回复文本信息
doReplyText(notify.getToUserName(), fromOpenid, WX_CONFIG_KEY.SUBSCRIBE_REPLY_TEXT, response);
}
else if(WX_CONFIG_KEY.SUBSCRIBE_REPLY_ARTICLE.getKey().equals(keyValue)){
//回复图文信息
doReplyArticles(notify.getToUserName(), fromOpenid, response);
}
}
}
if("subscribe".equals(notify.getEvent()) || "unsubscribe".equals(notify.getEvent())){
//关注和取消关注事件,需要更新数据库Open id状态
//获取用户信息(UionID)机制
try {
WxOpenid userInfo = getUserInfo(fromOpenid);
//保存open id 和 union id信息
wxOpenidService.addNewWxOpenidOrUpdate(userInfo);
} catch (Exception e) {
e.printStackTrace();
//获取AccessToken失败,则按接口文档说明返回空字符串
printJson("", response);
return;
}
}
if("TEMPLATESENDJOBFINISH".equals(notify.getEvent())) {
//模版消息发送任务完成事件
printJson("", response);
return;
}
}else if("text".equals(msgType) || "image".equals(msgType) || "voice".equals(msgType) || "video".equals(msgType) || "shortvideo".equals(msgType) || "location".equals(msgType) || "link".equals(msgType)){
fromOpenid = notify.getFromUserName();
//处理消息回复(暂无实现回复图片、视频、语音等)
WxConfig config = wxConfigService.getWxConfigByKey(WX_CONFIG_KEY.COMMON_REPLY_KEY.getKey());
if(config != null && config.getValue() != null){
String keyValue = config.getValue();
if(WX_CONFIG_KEY.COMMON_REPLY_TEXT.getKey().equals(keyValue)){
//回复文本信息
doReplyText(notify.getToUserName(), fromOpenid, WX_CONFIG_KEY.COMMON_REPLY_TEXT, response);
}
else if(WX_CONFIG_KEY.COMMON_REPLY_ARTICLE.getKey().equals(keyValue)){
//回复图文信息
doReplyArticles(notify.getToUserName(), fromOpenid, response);
}
}
}
}
}