diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/config/properties/WxConfig.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/config/properties/WxConfig.java index 119a7d0..0c78530 100644 --- a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/config/properties/WxConfig.java +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/config/properties/WxConfig.java @@ -27,6 +27,11 @@ public class WxConfig { private String accessTokenUrl; + private String obtainWxOfficialAccountUserUrl; + + private String ObtainUserInfoByOpenidUrl; + + public String getLoginUrl() { return loginUrl; @@ -51,4 +56,20 @@ public class WxConfig { public void setAccessTokenUrl(String accessTokenUrl) { this.accessTokenUrl = accessTokenUrl; } + + public String getObtainWxOfficialAccountUserUrl() { + return obtainWxOfficialAccountUserUrl; + } + + public void setObtainWxOfficialAccountUserUrl(String obtainWxOfficialAccountUserUrl) { + this.obtainWxOfficialAccountUserUrl = obtainWxOfficialAccountUserUrl; + } + + public String getObtainUserInfoByOpenidUrl() { + return ObtainUserInfoByOpenidUrl; + } + + public void setObtainUserInfoByOpenidUrl(String obtainUserInfoByOpenidUrl) { + ObtainUserInfoByOpenidUrl = obtainUserInfoByOpenidUrl; + } } diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/controller/WxNoRemindController.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/controller/WxNoRemindController.java index cdf36ad..7ca861e 100644 --- a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/controller/WxNoRemindController.java +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/controller/WxNoRemindController.java @@ -40,4 +40,14 @@ public class WxNoRemindController extends BaseController { return AjaxResult.success(); } + /** + * 是否关注微信公众号 + * + * @return + */ + @GetMapping("/isAttentionOfficialAccount") + public R isAttentionOfficialAccount() throws Exception { + return R.ok(wxNoRemindService.isAttentionOfficialAccount()); + } + } diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserDetailRet.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserDetailRet.java new file mode 100644 index 0000000..160f1b7 --- /dev/null +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserDetailRet.java @@ -0,0 +1,167 @@ +package com.flossom.miniProgram.domain.vo; + +import java.util.List; + +/** + * 分页获取 微信公众号 的关注者用户详情 + */ +public class OfficialAccountUserDetailRet { + + private List user_info_list; + + private Integer errcode; + + private String errmsg; + + public OfficialAccountUserDetailRet() { + } + + public OfficialAccountUserDetailRet(List user_info_list, Integer errcode, String errmsg) { + this.user_info_list = user_info_list; + this.errcode = errcode; + this.errmsg = errmsg; + } + + public List getUser_info_list() { + return user_info_list; + } + + public void setUser_info_list(List user_info_list) { + this.user_info_list = user_info_list; + } + + public Integer getErrcode() { + return errcode; + } + + public void setErrcode(Integer errcode) { + this.errcode = errcode; + } + + public String getErrmsg() { + return errmsg; + } + + public void setErrmsg(String errmsg) { + this.errmsg = errmsg; + } + + class userInfoList { + private Integer subscribe; + private String openid; + private String language; + private String subscribe_time; + private String unionid; + private String remark; + private Integer groupid; + private List tagid_list; + private String subscribe_scene; + private String qr_scene; + private String qr_scene_str; + + public userInfoList() { + } + + public userInfoList(Integer subscribe, String openid, String language, String subscribe_time, String unionid, String remark, Integer groupid, List tagid_list, String subscribe_scene, String qr_scene, String qr_scene_str) { + this.subscribe = subscribe; + this.openid = openid; + this.language = language; + this.subscribe_time = subscribe_time; + this.unionid = unionid; + this.remark = remark; + this.groupid = groupid; + this.tagid_list = tagid_list; + this.subscribe_scene = subscribe_scene; + this.qr_scene = qr_scene; + this.qr_scene_str = qr_scene_str; + } + + public Integer getSubscribe() { + return subscribe; + } + + public void setSubscribe(Integer subscribe) { + this.subscribe = subscribe; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getSubscribe_time() { + return subscribe_time; + } + + public void setSubscribe_time(String subscribe_time) { + this.subscribe_time = subscribe_time; + } + + public String getUnionid() { + return unionid; + } + + public void setUnionid(String unionid) { + this.unionid = unionid; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public Integer getGroupid() { + return groupid; + } + + public void setGroupid(Integer groupid) { + this.groupid = groupid; + } + + public List getTagid_list() { + return tagid_list; + } + + public void setTagid_list(List tagid_list) { + this.tagid_list = tagid_list; + } + + public String getSubscribe_scene() { + return subscribe_scene; + } + + public void setSubscribe_scene(String subscribe_scene) { + this.subscribe_scene = subscribe_scene; + } + + public String getQr_scene() { + return qr_scene; + } + + public void setQr_scene(String qr_scene) { + this.qr_scene = qr_scene; + } + + public String getQr_scene_str() { + return qr_scene_str; + } + + public void setQr_scene_str(String qr_scene_str) { + this.qr_scene_str = qr_scene_str; + } + } +} diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserReq.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserReq.java new file mode 100644 index 0000000..af567af --- /dev/null +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserReq.java @@ -0,0 +1,32 @@ +package com.flossom.miniProgram.domain.vo; + +/** + * 获取 微信公众号关注者详情 请求对象 + */ +public class OfficialAccountUserReq { + private String openid; + private String lang = "zh_CN"; + + public OfficialAccountUserReq() { + } + + public OfficialAccountUserReq(String openid) { + this.openid = openid; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } +} diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserRet.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserRet.java new file mode 100644 index 0000000..c3c4905 --- /dev/null +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/domain/vo/OfficialAccountUserRet.java @@ -0,0 +1,100 @@ +package com.flossom.miniProgram.domain.vo; + +import java.util.List; + +/** + * 分页获取 微信公众号 的关注者 + */ +public class OfficialAccountUserRet { + + private Integer total; + private Integer count; + private data data; + private String next_openid; + private Integer errcode; + private String errmsg; + + public OfficialAccountUserRet() { + } + + public OfficialAccountUserRet(Integer total, Integer count, OfficialAccountUserRet.data data, String next_openid, Integer errcode, String errmsg) { + this.total = total; + this.count = count; + this.data = data; + this.next_openid = next_openid; + this.errcode = errcode; + this.errmsg = errmsg; + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + public OfficialAccountUserRet.data getData() { + return data; + } + + public void setData(OfficialAccountUserRet.data data) { + this.data = data; + } + + public String getNext_openid() { + return next_openid; + } + + public void setNext_openid(String next_openid) { + this.next_openid = next_openid; + } + + public Integer getErrcode() { + return errcode; + } + + public void setErrcode(Integer errcode) { + this.errcode = errcode; + } + + public String getErrmsg() { + return errmsg; + } + + public void setErrmsg(String errmsg) { + this.errmsg = errmsg; + } + + public List getOpenidList() { + return getData().getOpenid(); + } + + class data { + private List openid; + + public data() { + } + + public data(List openid) { + this.openid = openid; + } + + public List getOpenid() { + return openid; + } + + public void setOpenid(List openid) { + this.openid = openid; + } + } + +} diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/IWxNoRemindService.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/IWxNoRemindService.java index 27679af..7c540b7 100644 --- a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/IWxNoRemindService.java +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/IWxNoRemindService.java @@ -1,6 +1,7 @@ package com.flossom.miniProgram.service; import com.flossom.common.core.domain.entity.WxNoRemindRecord; +import com.sun.org.apache.xpath.internal.operations.Bool; public interface IWxNoRemindService { @@ -8,4 +9,6 @@ public interface IWxNoRemindService { void closeOfficialAccount(); + Boolean isAttentionOfficialAccount() throws Exception; + } diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxNoRemindServiceImpl.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxNoRemindServiceImpl.java index ecd24fd..21585cb 100644 --- a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxNoRemindServiceImpl.java +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxNoRemindServiceImpl.java @@ -7,6 +7,7 @@ import com.flossom.common.core.utils.DateUtils; import com.flossom.common.core.web.domain.AjaxResult; import com.flossom.common.security.utils.SecurityUtils; import com.flossom.miniProgram.service.IWxNoRemindService; +import com.flossom.miniProgram.utils.MiniProgramUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -28,4 +29,9 @@ public class WxNoRemindServiceImpl implements IWxNoRemindService { wxNoRemindRecord.setCreateTime(DateUtils.getNowDate()); wxNoRemindRecordMapper.insertWxNoRemindRecord(wxNoRemindRecord); } + + @Override + public Boolean isAttentionOfficialAccount() throws Exception { + return MiniProgramUtils.isAttentionOfficialAccount(SecurityUtils.getLoginUser().getWxUserMember().getUnionid()); + } } diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxUserMemberServiceImpl.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxUserMemberServiceImpl.java index 1ee250c..df96b8f 100644 --- a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxUserMemberServiceImpl.java +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/service/impl/WxUserMemberServiceImpl.java @@ -100,13 +100,8 @@ public class WxUserMemberServiceImpl implements IWxUserMemberService { @Override public String upgradeMember(String code) throws Exception { - String accessToken = MiniProgramUtils.getAccessToken(SecurityUtils.getLoginUser().getWxUserMember().getOpenid()); - if (StringUtils.isBlank(accessToken)) { - throw new ServiceException("获取用户手机号码失败"); - } - // 获取手机号 - String result = MiniProgramUtils.getPhone(code, accessToken); + String result = MiniProgramUtils.getPhone(code); logger.info("请求微信服务器获取手机号码返回结果:{}", result); WxCode2PhoneRet wxCode2PhoneRet = JSON.parseObject(result, WxCode2PhoneRet.class); if (wxCode2PhoneRet.getErrcode() != 0) { diff --git a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/utils/MiniProgramUtils.java b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/utils/MiniProgramUtils.java index c8f70ad..b78f7c7 100644 --- a/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/utils/MiniProgramUtils.java +++ b/flossom-modules/flossom-mini-program/src/main/java/com/flossom/miniProgram/utils/MiniProgramUtils.java @@ -3,19 +3,26 @@ package com.flossom.miniProgram.utils; import com.alibaba.fastjson.JSON; import com.flossom.common.core.constant.CacheConstants; import com.flossom.common.core.domain.entity.WxParameterSetting; +import com.flossom.common.core.exception.ServiceException; import com.flossom.common.core.mapper.WxParameterSettingMapper; import com.flossom.common.core.utils.StringUtils; import com.flossom.common.redis.service.RedisService; import com.flossom.miniProgram.config.properties.WxConfig; +import com.flossom.miniProgram.domain.vo.OfficialAccountUserDetailRet; +import com.flossom.miniProgram.domain.vo.OfficialAccountUserReq; +import com.flossom.miniProgram.domain.vo.OfficialAccountUserRet; import com.flossom.miniProgram.domain.vo.WxAccessTokenRet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * 小程序工具类 @@ -94,29 +101,18 @@ public class MiniProgramUtils { } /** - * @param code - * @return

- * errcode number 错误码 - * errmsg string 错误信息 - * phone_info object 用户手机号信息 - * 属性 类型 说明 - * phoneNumber string 用户绑定的手机号(国外手机号会有区号) - * purePhoneNumber string 没有区号的手机号 - * countryCode string 区号 + * 获取 acceess_token + * + * @return + * @throws Exception */ - public static String getPhone(String code, String accessToken) throws Exception { - Map params = new HashMap(); - params.put("code", code); - return HttpClientUtils.postParameters(wxConfig.getObtainPhoneUrl() + "?access_token=" + accessToken, JSON.toJSONString(params)); - } - - public static String getAccessToken(String openid) throws Exception { + public static String getAccessToken() throws Exception { /** * TODO: 将 access_token 保存在 redis,官方定义说 access_token 的有效期暂定为 2个小时,使用注意事项: * 1、从 redis 获取 access_token,存在则未过期,不存在则过期了,重新获取 access_token */ - String accessToken = redisService.getCacheObject(CacheConstants.WX_ACCESS_TOKEN_CACHE + openid); + String accessToken = redisService.getCacheObject(CacheConstants.WX_ACCESS_TOKEN_CACHE); if (StringUtils.isBlank(accessToken)) { // 获取 access_token Map params = new HashMap(); @@ -132,10 +128,138 @@ public class MiniProgramUtils { return null; } // 保存缓存 - redisService.setCacheObject(CacheConstants.WX_ACCESS_TOKEN_CACHE + openid, + redisService.setCacheObject(CacheConstants.WX_ACCESS_TOKEN_CACHE, wxAccessTokenRet.getAccess_token(), CacheConstants.WX_ACCESS_TOKEN_EXPIRATION, TimeUnit.MINUTES); return wxAccessTokenRet.getAccess_token(); } return accessToken; } + + /** + * @param code + * @return

+ * errcode number 错误码 + * errmsg string 错误信息 + * phone_info object 用户手机号信息 + * 属性 类型 说明 + * phoneNumber string 用户绑定的手机号(国外手机号会有区号) + * purePhoneNumber string 没有区号的手机号 + * countryCode string 区号 + */ + public static String getPhone(String code) throws Exception { + String accessToken = getAccessToken(); + if (StringUtils.isBlank(accessToken)) { + throw new ServiceException("获取用户手机号码失败"); + } + + Map params = new HashMap(); + params.put("code", code); + return HttpClientUtils.postParameters(wxConfig.getObtainPhoneUrl() + "?access_token=" + accessToken, JSON.toJSONString(params)); + } + + /** + * 分页 获取关注微信公众号的用户列表 + * TODO: 一次拉取调用最多拉取10000个关注者的OpenID + * + * @param nextOpenid 下一个用户openid + * @return + * @throws Exception + */ + public static OfficialAccountUserRet obtainWxOfficialAccountUser(String nextOpenid) throws Exception { + String accessToken = getAccessToken(); + if (StringUtils.isBlank(accessToken)) { + logger.error("获取关注微信公众号用户列表失败,获取accessToken失败!"); + throw new ServiceException("操作失败"); + } + + Map params = new HashMap(); + params.put("access_token", accessToken); + if (StringUtils.isNotBlank(nextOpenid)) { + params.put("next_openid", nextOpenid); + } + String result = HttpClientUtils.getParameters(wxConfig.getObtainWxOfficialAccountUserUrl(), params); + logger.info("请求微信服务器获取关注微信公众号的用户列表:{}", result); + OfficialAccountUserRet officialAccountUserRet = JSON.parseObject(result, OfficialAccountUserRet.class); + if (officialAccountUserRet.getErrcode() != 0) { + logger.error("获取 关注微信公众号的用户列表 失败: {}", officialAccountUserRet.getErrmsg()); + throw new ServiceException("操作失败"); + } + return officialAccountUserRet; + } + + /** + * 获取 关注微信公众号用户的 unionid + * TODO: officialAccountUserReqList 最多支持 100 条 + */ + public static OfficialAccountUserDetailRet ObtainUserInfoByOpenid(List officialAccountUserReqList) throws Exception { + String accessToken = getAccessToken(); + if (StringUtils.isBlank(accessToken)) { + logger.error("获取关注微信公众号用户的unionid,获取accessToken失败!"); + throw new ServiceException("操作失败"); + } + + Map params = new HashMap(); + params.put("user_list", JSON.toJSONString(officialAccountUserReqList)); + String result = HttpClientUtils.postParameters(wxConfig.getObtainUserInfoByOpenidUrl() + "access_token=" + accessToken, params); + logger.info("请求微信服务器获获取 关注微信公众号用户的 unionid列表:{}", result); + OfficialAccountUserDetailRet officialAccountUserDetailRet = JSON.parseObject(result, OfficialAccountUserDetailRet.class); + if (officialAccountUserDetailRet.getErrcode() != 0) { + logger.error("获取 关注微信公众号的用户列表 失败: {}", officialAccountUserDetailRet.getErrmsg()); + throw new ServiceException("操作失败"); + } + return officialAccountUserDetailRet; + } + + /** + * 是否关注微信公众号 + * + * @return + */ + public static Boolean isAttentionOfficialAccount(String unionId) throws Exception { + String nextOpenid = null; + Integer currentTotal = 0; + while (true) { + // 1、获取关注微信公众号的用户列表 + OfficialAccountUserRet officialAccountUserRet = obtainWxOfficialAccountUser(null); + if (officialAccountUserRet.getErrcode() != 0) { + logger.error("获取微信用户列表失败: {}", officialAccountUserRet.getErrmsg()); + throw new ServiceException("获取微信用户列表失败"); + } + if (officialAccountUserRet.getCount() == null || officialAccountUserRet.getCount() == 0) { + return false; + } + currentTotal += officialAccountUserRet.getCount(); + nextOpenid = officialAccountUserRet.getNext_openid(); + List openidList = officialAccountUserRet.getOpenidList(); + logger.info("微信公众号关注列表:{}", openidList); + + // 2、获取关注微信公众号的unionid + int pageSize = 100; + int page = (int) Math.ceil(officialAccountUserRet.getCount() / pageSize); + if (page > 0) { + List pageOpenidList = officialAccountUserRet.getOpenidList(); + for (int pageNo = 1; pageNo <= page; pageNo++) { + // 构建分页集合 + List pageList = pageOpenidList.stream().skip((pageNo - 1) * 100).limit(pageSize).collect(Collectors.toList()); + // 获取 unionid 集合 + List officialAccountUserReqList = new ArrayList<>(); + for (String openid : pageList) { + OfficialAccountUserReq officialAccountUserReq = new OfficialAccountUserReq(openid); + officialAccountUserReqList.add(officialAccountUserReq); + } + OfficialAccountUserDetailRet officialAccountUserDetailRet = ObtainUserInfoByOpenid(officialAccountUserReqList); + + // 3、判断当前unionid是否在关注者列表 + return officialAccountUserDetailRet.getUser_info_list().contains(unionId); + } + } + // 4、 查完全部,未找到退出 + if (StringUtils.isBlank(nextOpenid) || officialAccountUserRet.getTotal() == currentTotal) { + break; + } + } + return false; + } + + }