企业微信开发总结-获取通讯录

news/2024/12/15 0:36:43/

企业微信开发总结-获取通讯录
最近遇到个项目需求,需要能够获取到用户企业的通讯录,同步到我们系统中,这样就不用重复输入一批企业人员了。一开始想的很简单,实际研究下来发现企业微信比个人微信对接起来复杂多了, 也许是微信平台考虑到企业数据的安全性,特意设计的很复杂吧。话不多说,直奔主题。
PS: 本项目代码是基于java语言springboot框架开发的,使用工具是IDEA, jdk1.8。如果看官您采用的是其他语言,那只能参考下本文的思路了。
一、 前期企业微信准备

  1. 先注册个企业微信,这个按照官方指引来就行了。进入服务商平台-服务商信息-基本信息,查看corpId, 保存备用。 同时需要设置IP白名单。不然后面的API接口调用会失败哦。
    在这里插入图片描述

  2. 进入应用管理,选择应用类型。 我这里做的是H5网页,所以选择网页应用。创建一个。注意应用类型,有普通应用和通讯录应用两种。 这里有个坑,普通应用的说明里也可以访问通讯录,但是他没有说无法获取到人员手机号码和姓名,只有人员的id,部门什么的。 这个和我的需求显然是不匹配的。 自己也是踏过这个坑,后面发现调用获取人员详情接口时,没有手机号码返回,一查看接口说明才发现这个问题。 所以新手就不用再犯同样的错误了,节约时间。创建完查看应用详情。
    在这里插入图片描述

SuiteID和Secret需要记录下来,这是第三方应用获取授权企业信息时需要的参数。
可信域名要填写我们应用的域名,如果没有需要去申请。建议配置https。还有安装完成回调域名,业务设置URL都写好。
接下来还有回调配置
在这里插入图片描述

数据回调URL是用于企业微信的通信消息的,比如谁发信息给谁了,会推送到这个地址。我们这里虽然不需要,但还是写个接口响应一下吧,不然会无法进行安装测试。
指令回调URL是非业务的交互指令通知,比如企业对我们的应用完成授权的时候,而这个正是我们需要的。我们需要在这里取到企业的临时授权码,用来换取永久授权码,进而取到企业的AccessToken, 获取通讯录数据。其实拿到永久授权码的数据返回中也有AccessToken,可以直接用。 存储永久授权码是为了以后再调取企业接口,直接使用永久授权去换取AccessToken。
Token自己填写或者自动生成,EncodingAESKey也是。这两个是用于验证和微信平台的数据交互的安全性。
接口得先写好,调通后这里的回调填写保存才能成功。所以下面开始撸代码。
二、 业务代码
application.properties中配置相关参数

suitId=这里是应用的suiteID
suitSecret=这里是应用的Secret
#平台作为服务商的参数
corpId=服务商corpId
token=应用回调token
encodingAesKey=应用回调encodingAESKey

创建表wx_corp_info,用于存储授权企业的信息。用的是mysql数据库。

然后mybatis-plus逆向生成相关的代码
编写主类
WeiXinCorpController

package xxx;import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.util.StringUtil;
import com.hssx.cloudmodel.constant.Constant;
import com.hssx.cloudmodel.entity.SysParam;
import com.hssx.cloudmodel.entity.User;
import com.hssx.cloudmodel.entity.WxCorpInfo;
import com.hssx.cloudmodel.mapper.SysParamMapper;
import com.hssx.cloudmodel.mapper.UserMapper;
import com.hssx.cloudmodel.mapper.WxCorpInfoMapper;
import com.hssx.cloudmodel.util.HttpRespMsg;
import com.hssx.cloudmodel.util.JsapiTicketUtil;
import com.hssx.cloudmodel.util.Sha1Util;
import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.json.XML;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.SortedMap;
import java.util.TreeMap;@RestController
@RequestMapping("/wxcorp")
@Slf4j
public class WeiXinCorpController {public static final String GET_SUITE_ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token";public static final String GET_PREAUTH_CODE_URL = "https://qyapi.weixin.qq.com/cgi-bin/service/get_pre_auth_code?suite_access_token=SUITE_ACCESS_TOKEN";//获取企业永久授权码public static final String GET_CORP_PERMANENT_CODE_URL = "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKEN";//获取成员详情public static final String GET_USER_INFO_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID";//获取部门列表public static final String GET_DEPARTMENT_URL = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=ACCESS_TOKEN&id=";//获取部门成员详情public static final String GET_DEPARTMENT_USER_DETAIL_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=1";public static final String AUTH_CALLBACK_URL = "http://ymhh.yunsu.cn/wxcorp/authcallback";@Value("${suitId}")private String suitId;@Value("${suitSecret}")private String suitSecret;@Value("${token}")private String token;@Value("${encodingAesKey}")private String encodingAesKey;@Value("${corpId}")private String corpId;@AutowiredRestTemplate restTemplate;public static String SUITE_ACCESS_TOKEN = null;public static long suiteTokenExpireTime = 0L;public static String PRE_AUTH_CODE = null;public static long expireTime = 0L;@ResourceSysParamMapper sysParamMapper;@ResourceWxCorpInfoMapper wxCorpInfoMapper;@ResourceUserMapper userMapper;@ApiOperation(value = "企业微信数据回调", notes = "企业微信数据回调")@RequestMapping(value = "/dataCallback/{corpId}", method = RequestMethod.GET)@ResponseBodypublic String dataCallback(@PathVariable String corpId, String msg_signature, String timestamp, String nonce, String echostr) {System.out.println("========dataCallback========="+corpId);try {String sVerifyMsgSig = msg_signature;String sVerifyTimeStamp = timestamp;String sVerifyNonce = nonce;String sVerifyEchoStr = echostr;WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAesKey, corpId);String sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce, sVerifyEchoStr);//需要返回的明文System.out.println("verifyurl echostr: " + sEchoStr);return sEchoStr;// 验证URL成功,将sEchoStr返回// HttpUtils.SetResponse(sEchoStr);}  catch (AesException e) {e.printStackTrace();}return "success";}@ApiOperation(value = "企业微信数据回调", notes = "企业微信数据回调")@RequestMapping(value = "/dataCallback/{corpId}", method = RequestMethod.POST)@ResponseBodypublic String dataCallbackPost(@PathVariable String corpId, @RequestBody String requestBody, String msg_signature, String timestamp, String nonce) {log.info("========dataCallback========="+corpId);// String sReqMsgSig = HttpUtils.ParseUrl("msg_signature");String sReqMsgSig = msg_signature;// String sReqTimeStamp = HttpUtils.ParseUrl("timestamp");String sReqTimeStamp = timestamp;// String sReqNonce = HttpUtils.ParseUrl("nonce");String sReqNonce = nonce;// post请求的密文数据// sReqData = HttpUtils.PostData();
//        String sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>";log.info("===========dataCallback POST=============" + msg_signature);log.info("===========corpId=====suitId========" + suitId);try {WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAesKey, suitId);String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, requestBody);log.info("解密后===msg: " + sMsg);} catch (Exception e) {// TODO// 解密失败,失败原因请查看异常e.printStackTrace();}return "success";}@ApiOperation(value = "企业微信指令回调", notes = "企业微信指令回调, 处理回调验证")@RequestMapping(value = "/cmdCallback", method = RequestMethod.GET)@ResponseBodypublic String cmdCallbackGet(String msg_signature, String timestamp, String nonce, String echostr) {try {log.info("===========cmdCallback GET=============" + msg_signature);String sVerifyMsgSig = msg_signature;String sVerifyTimeStamp = timestamp;String sVerifyNonce = nonce;String sVerifyEchoStr = echostr;WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAesKey, corpId);String sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce, sVerifyEchoStr);//需要返回的明文log.info("verifyurl echostr: " + sEchoStr);return sEchoStr;// 验证URL成功,将sEchoStr返回// HttpUtils.SetResponse(sEchoStr);}  catch (AesException e) {e.printStackTrace();}return "success";}@ApiOperation(value = "企业微信指令回调", notes = "企业微信指令回调, 处理消息体")@RequestMapping(value = "/cmdCallback", method = RequestMethod.POST)@ResponseBodypublic String cmdCallbackPost(@RequestBody(required = false) String requestBody, String msg_signature, String timestamp, String nonce) {// String sReqMsgSig = HttpUtils.ParseUrl("msg_signature");String sReqMsgSig = msg_signature;// String sReqTimeStamp = HttpUtils.ParseUrl("timestamp");String sReqTimeStamp = timestamp;// String sReqNonce = HttpUtils.ParseUrl("nonce");String sReqNonce = nonce;// post请求的密文数据// sReqData = HttpUtils.PostData();
//        String sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>";log.info("===========企业微信指令回调 POST=============" + msg_signature);log.info("===========corpId=====suitId========" + suitId);try {WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAesKey, suitId);String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, requestBody);log.info("解密后===msg: " + sMsg);// TODO: 解析出明文xml标签的内容进行处理org.json.JSONObject jsonObject = XML.toJSONObject(sMsg);log.info("json=="+jsonObject.toString());jsonObject = jsonObject.getJSONObject("xml");if (jsonObject.has("AuthCode")) {//企业授权通知String authCode = jsonObject.getString("AuthCode");handleCorpAuth(authCode);} else if (jsonObject.has("SuiteTicket")) {//ticket推送String ticket = jsonObject.getString("SuiteTicket");String timeStamp = jsonObject.getLong("TimeStamp")+"";log.info("ticket=="+ticket);//存储SysParam param = sysParamMapper.selectOne(new QueryWrapper<SysParam>().eq("param_code", "suite_ticket"));if (param == null) {param = new SysParam();param.setParamCode("suite_ticket");param.setParamName(ticket);param.setRemark(timeStamp);sysParamMapper.insert(param);} else {param.setParamName(ticket);param.setRemark(timeStamp);sysParamMapper.updateById(param);}}} catch (Exception e) {// TODO// 解密失败,失败原因请查看异常e.printStackTrace();}return "success";}@ApiOperation(value = "企业授权微信应用", notes = "企业授权微信应用")@RequestMapping("/getAuthPage")@ResponseBodypublic HttpRespMsg getAuthPage() {HttpRespMsg msg = new HttpRespMsg();String preAuthCode = getAuthCode();String url = "https://open.work.weixin.qq.com/3rdapp/install?suite_id=SUITE_ID&pre_auth_code=PRE_AUTH_CODE&redirect_uri=REDIRECT_URI&state=STATE";url = url.replace("SUITE_ID", suitId).replace("PRE_AUTH_CODE", preAuthCode).replace("REDIRECT_URI", URLEncoder.encode(AUTH_CALLBACK_URL));msg.data = url;return msg;}@ApiOperation(value = "企业授权后回调地址")@RequestMapping("/authcallback")@ResponseBodypublic HttpRespMsg authcallback(String auth_code, Integer expires_in, String state) {HttpRespMsg msg = new HttpRespMsg();log.info("authcallback收到: auth_code="+auth_code+", expires_in="+ expires_in+",state= "+state);handleCorpAuth(auth_code);return msg;}private void handleCorpAuth(String authCode) {String suitAccessToken = getSuiteAccessToken();String url = GET_CORP_PERMANENT_CODE_URL.replace("SUITE_ACCESS_TOKEN", suitAccessToken);//失效了,需要重新获取HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);JSONObject reqParam = new JSONObject();reqParam.put("auth_code",  authCode);HttpEntity<String> requestEntity = new HttpEntity<String>(reqParam.toJSONString(), headers);ResponseEntity<String> responseEntity = this.restTemplate.exchange(url,HttpMethod.POST, requestEntity, String.class);if (responseEntity.getStatusCode() == HttpStatus.OK) {String resp = responseEntity.getBody();log.info(resp);JSONObject obj = JSONObject.parseObject(resp);if (obj.getIntValue("errcode") == 0) {JSONObject corpInfo = obj.getJSONObject("auth_corp_info");String corpId = corpInfo.getString("corpid");WxCorpInfo data = wxCorpInfoMapper.selectById(corpId);if (data == null) {//不存在的情况下,需要生成企业信息data = new WxCorpInfo();log.info("================create corp=========");data.setCorpid(corpId);String permanentCode = obj.getString("permanent_code");String curCorpAccessToken = obj.getString("access_token");LocalDateTime time = LocalDateTime.now();time = time.plusSeconds(obj.getLong("expires_in"));data.setAccessToken(curCorpAccessToken);data.setCorpFullName(corpInfo.getString("corp_full_name"));data.setCorpIndustry(corpInfo.getString("corp_industry"));data.setCorpName(corpInfo.getString("corp_name"));data.setCorpScale(corpInfo.getString("corp_scale"));data.setCorpSubIndustry(corpInfo.getString("corp_sub_industry"));data.setExpireTime(time);data.setLocation(corpInfo.getString("location"));data.setPermanentCode(permanentCode);//取到当前授权人,按照授权人与企业进行匹配JSONObject authUserInfo = obj.getJSONObject("auth_user_info");data.setAuthUsername(authUserInfo.getString("name"));String userId = authUserInfo.getString("userid");//授权人的useridJSONObject userDetail = getUserInfo(curCorpAccessToken, userId);//按姓名和手机号进行匹配log.info("===userDetail==" + userDetail.toJSONString());User user = userMapper.selectOne(new QueryWrapper<User>().eq("account", userDetail.getString("mobile")).eq("username", userDetail.getString("name")));if (user != null) {//找到了匹配的企业data.setCompanyId(user.getCompanyId());}wxCorpInfoMapper.insert(data);//获取部门JSONObject deptObj = getDepartments(curCorpAccessToken);JSONArray deptObjJSONArray = deptObj.getJSONArray("department");for (int i=0;i<deptObjJSONArray.size(); i++) {int deptId = deptObjJSONArray.getJSONObject(i).getIntValue("id");JSONArray userList = getDeptUserDetail(curCorpAccessToken, deptId);for (int m=0;m<userList.size(); m++) {JSONObject item = userList.getJSONObject(m);log.info("userid="+item.getString("userid")+", name=" + item.getString("name")+", mobile="+item.getString("mobile"));//不存在的人员, 进行插入User employee = new User();employee.setUsername(item.getString("name"));employee.setAccount(item.getString("mobile"));int count = userMapper.selectCount(new QueryWrapper<User>().eq("account", employee.getAccount()).eq("company_id", data.getCompanyId()));if (count == 0) {//手机号不存在的,添加if (data.getCompanyId() != null) {employee.setCompanyId(data.getCompanyId());userMapper.insert(employee);}}}}}}}}private JSONArray getDeptUserDetail(String accessToken, int deptId) {String url = GET_DEPARTMENT_USER_DETAIL_URL.replace("ACCESS_TOKEN", accessToken).replace("DEPARTMENT_ID", ""+deptId);String result = restTemplate.getForObject(url, String.class);JSONObject obj = JSONObject.parseObject(result);JSONArray userlist = obj.getJSONArray("userlist");return userlist;}private JSONObject getDepartments(String accessToken) {String url = GET_DEPARTMENT_URL.replace("ACCESS_TOKEN", accessToken);String result = restTemplate.getForObject(url, String.class);JSONObject obj = JSONObject.parseObject(result);return obj;}private JSONObject getUserInfo(String accessToken, String userId) {String url = GET_USER_INFO_URL.replace("ACCESS_TOKEN", accessToken).replace("USERID", userId);String result = restTemplate.getForObject(url, String.class);JSONObject obj = JSONObject.parseObject(result);return obj;}//获取预授权码private String getAuthCode() {if (PRE_AUTH_CODE == null || expireTime < System.currentTimeMillis()) {//失效了,需要重新获取String resp = restTemplate.getForObject(GET_PREAUTH_CODE_URL.replaceAll("SUITE_ACCESS_TOKEN", getSuiteAccessToken()), String.class);log.info(resp);JSONObject obj = JSONObject.parseObject(resp);if (obj.getIntValue("errcode") == 0) {PRE_AUTH_CODE = obj.getString("pre_auth_code");expireTime = System.currentTimeMillis() + obj.getIntValue("expires_in")*1000;}}return PRE_AUTH_CODE;}//获取第三方应用临时凭证private String getSuiteAccessToken() {if (SUITE_ACCESS_TOKEN == null || suiteTokenExpireTime < System.currentTimeMillis()) {//失效了,需要重新获取HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);JSONObject reqParam = new JSONObject();reqParam.put("suite_id",  suitId);reqParam.put("suite_secret", suitSecret);SysParam param = sysParamMapper.selectOne(new QueryWrapper<SysParam>().eq("param_code", "suite_ticket"));if (param != null) {reqParam.put("suite_ticket",param.getParamName());}HttpEntity<String> requestEntity = new HttpEntity<String>(reqParam.toJSONString(), headers);ResponseEntity<String> responseEntity = this.restTemplate.exchange(GET_SUITE_ACCESS_TOKEN_URL,HttpMethod.POST, requestEntity, String.class);if (responseEntity.getStatusCode() == HttpStatus.OK) {String resp = responseEntity.getBody();log.info(resp);JSONObject obj = JSONObject.parseObject(resp);if (obj.getIntValue("errcode") == 0) {SUITE_ACCESS_TOKEN = obj.getString("suite_access_token");suiteTokenExpireTime = System.currentTimeMillis() + obj.getIntValue("expires_in")*1000;}}}return SUITE_ACCESS_TOKEN;}
}

需要指导和代码的话,请联系qq:373132975


http://www.ppmy.cn/news/748389.html

相关文章

Android 高仿微信通讯录

模仿了一下微信的联系人界面UI&#xff0c;由于是新手&#xff0c;所以看起来很简单的界面&#xff0c;结果被搞得半死&#xff0c;弄到凌晨5点&#xff0c;实在吃不消了&#xff0c;就睡了&#xff0c;早上9点又有小伙伴过来找我&#xff0c;约好了下午出去爬山的&#xff0c;…

android 微信 备份通讯录,用微信导入手机通讯录?安卓手机的备份方法介绍

因为科技的发展&#xff0c;只有手机上也在持续的更新换代&#xff0c;一个知名品牌每一年都是会发布好几种型号&#xff0c;因此 客户在使用这种高科技产品的情况下更新换代的速率也是较为快的&#xff0c;在升级的全过程中难免会遗失一些数据信息&#xff0c;因此 大家针对手…

学习使用php获取企业微信通讯录管理接口代码

学习企业微信接口开发之通讯录管理接口 登陆企业微信具体代码 登陆企业微信 点击管理工具 如下图&#xff1a; 选择通讯录同步 点击开启api接口同步&#xff0c;进入配置页面 获取到对应的Secret&#xff0c;点击查看 点击发送&#xff0c;就会收到对应的密钥了 具体代码 …

wx微信小程序实现通讯录

第一步&#xff0c;保证后端返回数据是固定格式&#xff0c;带有abcd这样的标识 第二步,分三块&#xff0c;左侧&#xff08;scroll-view &#xff09;&#xff0c;上&#xff0c;右侧 第三步&#xff0c;左侧滚动&#xff0c;判断滚动的距离&#xff0c;借助第三方变量&…

pywinauto爬取微信通讯录 2023年1月有效

使用pywinauto爬取微信通讯录 1.前期准备 将微信窗口固定到这个位置&#xff0c;即通讯录第一个人的显示 2.运行如下代码并将鼠标焦点到微信窗口 import psutil import pywinauto from pywinauto.application import Application import os import sysdef getWinxin():PID0f…

Android Studio 微信通讯录页面设计(RecycleView)

目录 功能说明 设计流程 核心代码详解 总结 源码开源地址&#xff08;gitee&#xff09; 一、功能说明 1.在微信通讯录页面添加列表项&#xff0c;使用RecycleView实现二级列表 2.在上一列表页面的基础上进行点击跳转设计 二、设计流程 1.item设计 为RecyclerView内的元素…

【uniapp前端组件】仿微信通讯录列表组件

仿微信通讯录列表组件 示例图 前言 仿微信通讯录列表组件&#xff0c;可实现通讯列表以及选择多个联系人功能。 组件介绍 本组件有三个自定义组件构成&#xff0c;都已经集成在bugking7-contact-list中&#xff0c;该组件具有两种模式&#xff0c;不带选择框和带选择框&…

微信通讯录备份android,《微信》通讯录备份功能介绍说明

在如今的智能手机时代&#xff0c;备份通讯录变得相当方便&#xff0c;我们如果是手机丢了或者更换手机的时候&#xff0c;就能省去很多不必要的麻烦&#xff0c;因此备份通讯录也相当重要。个人觉得&#xff0c;用微信来备份通讯是比较快捷的&#xff0c;而且可以在相关联的QQ…