需求
前天公司来了个需求有关微信的对话能力(原 导购助手),为关注公众号的粉丝绑定顾问并且分批打上标签,方便顾问进行群发消息,不然想选几万个粉丝,顾问在手机端得选中很久,从微信公众号后台界面手动为顾问绑定客户并配置标签也很麻烦且久不实际,所以使用微信提供的API进行便利的绑定与设置。
1 项目建立
2 建立接受微信返回的结果类
/**
* 错误父类
*/
@Getter
@Setter
@ToString
public class Error {
private int errcode;
private String errmsg;
}
/**
* 请求或返回结果类
*/
@Getter
@Setter
@AllArgsConstructor
//加了callSuper = true 才会打印父类属性
@ToString(callSuper = true)
//equals(Object other) 和 hashCode()方法使用父类属性,防止父类相同,子类不同计算后结果却相同
@EqualsAndHashCode(callSuper = true)
public class Buyer extends Error{
private String openid;
private String buyerNickname;
private String createTime;
public Buyer(String openid) {
this.openid = openid;
}
}
/**
* 微信返回值
*/
@Getter
@Setter
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Result extends Error{
private int totalNum;
/**
* 对应不同请求返回的名称不同
*/
List<Buyer> list;
List<Buyer> buyerResp;
List<String> openidList;
}
3.对话能力API封装类
下面方法都有的参数
accessToken
公众号accessTokenguideAccount
顾问微信号
3.1 为顾问批量分配客户
/**
* 为顾问批量分配客户(不超过200)
*
* @param openidList 客户列表的openid列表
*/
private static void addGuideBuyerRelation(String accessToken, String guideAccount, List<String> openidList) throws WeixinException {
JSONObject paramsJo = new JSONObject();
List<Buyer> buyerList = new ArrayList<>();
for (String openid : openidList) {
buyerList.add(new Buyer(openid));
}
paramsJo.put("guide_account", guideAccount);
paramsJo.put("buyer_list", buyerList);
HttpsClient httpsClient = new HttpsClient();
Response res = httpsClient.post("https://api.weixin.qq.com/cgi-bin/guide/addguidebuyerrelation?access_token=" + accessToken, paramsJo);
JSONObject jsonObject = res.asJSONObject();
Result result = JSON.parseObject(jsonObject.toJSONString(), Result.class);
int errcode = result.getErrcode();
if (errcode == 0) {
log.info("分配客户成功");
} else {
log.info("分配客户失败:{}", result);
}
}
3.2 为客户设置标签
/**
* 为客户设置标签,每次最多设置200
*
* @param openidList 客户列表的openid列表
* @param tagValue 标签值
*/
public static void addGuideBuyerTag(String accessToken, String guideAccount, List<String> openidList, String tagValue) throws WeixinException {
JSONObject paramsJo = new JSONObject();
paramsJo.put("guide_account", guideAccount);
paramsJo.put("openid_list", openidList);
paramsJo.put("tag_value", tagValue);
HttpsClient httpsClient = new HttpsClient();
Response res = httpsClient.post("https://api.weixin.qq.com/cgi-bin/guide/addguidebuyertag?access_token=" + accessToken, paramsJo);
JSONObject jsonObject = res.asJSONObject();
Result result = JSON.parseObject(jsonObject.toJSONString(), Result.class);
int errcode = result.getErrcode();
if (errcode == 0) {
int failCount = 0;
int size = openidList.size();
//输出为每个客户设置标签失败结果
List<Buyer> buyerList = result.getBuyerResp();
for (Buyer buyer : buyerList) {
if (buyer.getErrcode() != 0) {
failCount++;
//todo 失败结果没显示
log.info("设置标签失败,错误码:{},错误信息:{},openid:{}", buyer.getErrcode(), buyer.getErrmsg(), buyer.getOpenid());
}
}
log.info("需设置标签客户数:{},成功数:{},失败数:{}", size, size - failCount, failCount);
} else {
log.info("全部设置标签失败:{}", result);
}
}
3.3 获取顾问的客户列表
/**
* 获取顾问的客户列表
*
* @param page 分页页数,从0开始
* @param num 每页数量
*/
public static List<String> getGuideBuyerRelationList(String accessToken, String guideAccount, int page, int num) throws WeixinException {
JSONObject paramsJo = new JSONObject();
paramsJo.put("guide_account", guideAccount);
paramsJo.put("page", page);
paramsJo.put("num", num);
HttpsClient httpsClient = new HttpsClient();
Response res = httpsClient.post("https://api.weixin.qq.com/cgi-bin/guide/getguidebuyerrelationlist?access_token=" + accessToken, paramsJo);
JSONObject jsonObject = res.asJSONObject();
Result result = JSON.parseObject(jsonObject.toJSONString(), Result.class);
int errcode = result.getErrcode();
if (errcode == 0) {
log.info("顾问绑定客户的总数:{}", result.getTotalNum());
List<Buyer> buyerRespList = result.getList();
List<String> openidList = new ArrayList<>();
buyerRespList.forEach(buyerResp -> {
openidList.add(buyerResp.getOpenid());
});
return openidList;
}
log.info("获取顾问的客户列表失败:{}", result);
return null;
}
3.4 根据标签值获取客户列表
/**
* 根据标签值获取客户列表
*
* @param tagValues 标签值集合
*/
public static List<String> queryGuideBuyerByTag(String accessToken, String guideAccount, List<String> tagValues) throws WeixinException {
JSONObject paramsJo = new JSONObject();
paramsJo.put("guide_account", guideAccount);
paramsJo.put("tag_values", tagValues);
HttpsClient httpsClient = new HttpsClient();
Response res = httpsClient.post("https://api.weixin.qq.com/cgi-bin/guide/queryguidebuyerbytag?access_token=" + accessToken, paramsJo);
JSONObject jsonObject = res.asJSONObject();
Result result = JSON.parseObject(jsonObject.toJSONString(), Result.class);
int errcode = result.getErrcode();
if (errcode == 0) {
List<String> openidList = result.getOpenidList();
log.info("根据标签值获取客户列表成功");
return openidList;
}
log.info("根据标签值获取客户列表失败:{}", result);
return null;
}
3.5 获取关注列表
/**
* 获取关注列表,一次拉取调用最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。
*
* @param accessToken
* @param nextOpenid 第一个拉取的OPENID,不填默认从头开始拉取
*/
public JSONObject getFollowerList(String accessToken, String nextOpenid) throws Exception {
HttpsClient http = new HttpsClient();
String url = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + accessToken;
if (StringUtils.isNotBlank(nextOpenid)) {
url += "&next_openid=" + nextOpenid;
}
//log.info("getFollowerList请求:{}", url);
Response res = http.get(url);
JSONObject jsonObj = res.asJSONObject();
if (jsonObj != null) {
//log.info("getFollowerList返回next:{}", jsonObj.getString("next_openid"));
Object errcode = jsonObj.get("errcode");
if (errcode != null) {
return null;
}
}
return jsonObj;
}
4 使用案例
4.1 需求
顾问已经绑定了3000个客户且设置了标签,这时候又需要新绑定3000个客户在设置标签。
4.2 实现
public static ExecutorService executorService() {
return ExecutorBuilder.create().setCorePoolSize(8).build();
}
public static void main(String[] args) throws Exception {
//开始计算方法时间
TimeInterval timer = DateUtil.timer();
log.info("===========开始计算方法执行时间===========");
String guideAccount = "顾问微信号";
Weixin weixin = new Weixin();
String accessToken = weixin.getToken().getAccess_token();
//获取已绑定的客户集合
List<String> tagOpenidList = getGuideBuyerRelationList(accessToken, guideAccount, 0, 3000);
//获取粉丝列表
Snippet snippet = new Snippet();
JSONObject followerListJo = snippet.getFollowerList(accessToken, null);
JSONArray jsonArray = followerListJo.getJSONObject("data").getJSONArray("openid");
List<String> followerList = JSONObject.parseArray(jsonArray.toJSONString(), String.class);
//从所有粉丝中筛选没有绑定的粉丝集合
if(CollUtil.isNotEmpty(tagOpenidList)){
followerList.removeAll(tagOpenidList);
}
//循环请求次数
int loopCount = 15;
//构造线程池
ExecutorService executorService = Task.executorService();
//生成绑定计数器
CountDownLatch bindTaskCountDownLatch = new CountDownLatch(loopCount);
//根据粉丝列表绑定客户,默认顾问每次绑定200个,绑定loopCount次,那么此次绑定的客户200*loopCount,其他请求类似的也限制200个最大
for (int i = 0; i < loopCount; i++) {
List<String> tempList = followerList.subList(BUYER_MAX * i, BUYER_MAX * (i + 1));
executorService.execute(()->{
try {
addGuideBuyerRelation(accessToken, guideAccount, tempList);
//执行完计数器减1
bindTaskCountDownLatch.countDown();
} catch (WeixinException e) {
e.printStackTrace();
}
});
}
//等待绑定完成
bindTaskCountDownLatch.await();
//生成设置标签计数器
CountDownLatch setTagCountDownLatch = new CountDownLatch(loopCount);
//为顾问已绑定的指定客户集合设置标签
for (int i = 0; i < loopCount; i++) {
List<String> tempList = followerList.subList(BUYER_MAX * i, BUYER_MAX * (i + 1));
executorService.execute(()->{
try {
addGuideBuyerTag(accessToken, guideAccount, tempList, "4月1日");
//执行完计数器减1
setTagCountDownLatch.countDown();
} catch (WeixinException e) {
e.printStackTrace();
}
});
}
//等待设置标签完成
setTagCountDownLatch.await();
//结束计算方法时间
long executeTime = timer.interval();
log.info("===========结束计算方法执行时间,耗时 {} 毫秒===========", executeTime);
}
4.3 分析代码
- 代码第一行使用,hutool提供的计时方法,计算方法运行时间,结尾输出时间
- 使用多线程进行并发请求加速方法执行,我这边看输出结果耗时1分多钟比原来快很多。
- 开启多线程,主线程不会阻塞,但是我需要等顾问绑定完粉丝后才能设置标签,这时候使用了计数器
CountDownLatch
,只有当绑定完粉丝的线程都执行完毕才减1,当计数器为0是进行下一步,当然这里得在后面加上countDownLatch.await()
方法,才能生效。 - 最后要注意的是,计数器减1操作
countDown()
一定要放在finally {}
里执行,不然有一个线程执行中报错了,直接GG,不执行countDown()
永远阻塞,我代码里忘记写了,你们一定要加上。