企微短剧业务系统

wechatWorkService.php 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: shensong
  5. * Date: 2022/3/16
  6. * Time: 16:50
  7. */
  8. namespace App\Service;
  9. # 爬取企业微信数据
  10. use App\Log;
  11. use App\Models\AuthorizeCorp;
  12. use App\Models\ChatGroup;
  13. use App\Models\Customer;
  14. use App\Models\CustomerDetails;
  15. use App\Models\CustomerTag;
  16. use App\Models\DjDepartment;
  17. use App\Models\DjUser;
  18. use App\Models\Tag;
  19. use App\Models\TagGroup;
  20. use App\RedisModel;
  21. use App\Support\EmailQueue;
  22. use App\Support\qyApi\QyCommon;
  23. class wechatWorkService
  24. {
  25. CONST SYNC_CORP_CUSTOMER = 'Playlet::SyncCorpCustomer';
  26. CONST SYNC_FAIL_USER_RDS = 'Playlet::SyncCorpCustomerFailUser';
  27. public static function getCorCustomerList($corpid)
  28. {
  29. $requestData = [
  30. 'corpid' => $corpid
  31. ];
  32. if(empty($corpid)) {
  33. Log::logInfo('getCorCustomerList 企业id为空 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  34. return [['error' => '企业id为空'], 2004];
  35. }
  36. // 判断键是否存在
  37. $redisKey = 'Playlet::getCorCustomerList-'.$corpid;
  38. $value = RedisModel::get($redisKey);
  39. if(!empty($value)) {
  40. Log::logInfo('getCorCustomerList 操作太快 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  41. return [['error' => '操作太快'], 2007];
  42. }
  43. // 根据企业id获取accessToken
  44. $accessToken = AuthorizeCorp::getAccessToken($corpid, '全量获取企业部门、标签和员工信息');
  45. if(empty($accessToken)) {
  46. Log::logInfo('getCorCustomerList 获取企业access_token失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  47. return [['error' => '获取企业access_token失败'], 2000];
  48. }
  49. // 根据企业获取部门列表
  50. $departmentList = self::getCorDepartmentList($corpid, $accessToken);
  51. if(empty($departmentList)) {
  52. Log::logInfo('getCorCustomerList 部门权限获取失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  53. return [['error' => '部门权限获取失败'], 2001];
  54. }
  55. // 将部门信息保存到本地
  56. foreach($departmentList as $department) {
  57. $departmentId = $department['id'];
  58. $res = wechatWorkService::getDepartmentInfo($departmentId, $corpid, $accessToken);
  59. if(!empty($res)) {
  60. DjDepartment::departmentInfoSave($corpid, $res);
  61. }
  62. $res = wechatWorkService::departmentUserList($corpid, $departmentId, $accessToken);
  63. if(isset($res['errcode']) && $res['errcode'] == 0) {
  64. if(!empty($res['userlist'])) {
  65. // 将员工信息保存入库
  66. foreach($res['userlist'] as $user) {
  67. $userId = DjUser::departmentUserSave($corpid, $user);
  68. }
  69. } else {
  70. Log::logInfo('getCorCustomerList 获取内部员工信息为空 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  71. }
  72. } else {
  73. Log::logInfo('getCorCustomerList 获取内部员工信息失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  74. return [['error' => '获取内部员工信息失败'], 2002];
  75. }
  76. }
  77. # 获取所有企业标签
  78. $data1 = wechatWorkService::getCorTagList($corpid);
  79. if(!empty($data1)) {
  80. // Log::logInfo('getCorCustomerList 获取企业标签失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  81. // return [['error' => '获取企业标签失败'], 2005];
  82. // } else {
  83. $re1 = wechatWorkService::tagDataSave($corpid, 1, $data1);
  84. if(!$re1) {
  85. Log::logInfo('getCorCustomerList 保存企业标签失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  86. return [['error' => '保存企业标签失败'], 2006];
  87. }
  88. }
  89. # 获取所有客户群
  90. RedisModel::lPush(ChatGroup::CHAT_GROUP_BASIC_INFO_RDS, json_encode(['corpid' => $corpid], 256));
  91. // 获取所有已激活客服列表
  92. $followUserList = DjUser::getUserIdList($corpid);
  93. $followUserList = json_decode(json_encode($followUserList), 1);
  94. // 循环处理配置了客户联系功能的员工,拉取对应的客户信息
  95. // 如果使用没有配置联系我的内部员工id来请求数据的话,接口会返回user_id异常的错误信息
  96. $followUserListArr = array_chunk($followUserList, 100);
  97. foreach ($followUserListArr as $userArr) {
  98. $value['corpid'] = $corpid;
  99. $value['user_list'] = $userArr;
  100. RedisModel::lPush(AuthorizeCorp::SYNC_CORP_CUSTOMER, json_encode($value));
  101. }
  102. RedisModel::set($redisKey, time());
  103. RedisModel::expire($redisKey, 600);
  104. return [['info' => '获取成功'], 0];
  105. }
  106. public static function syncCorpData($corpid)
  107. {
  108. $requestData = [
  109. 'corpid' => $corpid
  110. ];
  111. $subject = config('app.name') . '于' . date('d日H:i:s') . '响应异常';
  112. try{
  113. if(empty($corpid)) {
  114. Log::logError('syncCorpData',[
  115. 'params' => $requestData,
  116. 'errmsg' => '企微id为空',
  117. ], 'interface');
  118. return [['error' => '企业id为空'], 2004];
  119. }
  120. // 判断键是否存在
  121. $redisKey = 'Playlet::getCorCustomerList-'.$corpid;
  122. $value = RedisModel::get($redisKey);
  123. if(!empty($value)) {
  124. Log::logError('syncCorpData', [
  125. 'params' => $requestData,
  126. 'errmsg' => '请求频率超限制',
  127. ], 'interface');
  128. return [['error' => '操作太快'], 2007];
  129. }
  130. // 根据企业id获取accessToken
  131. $accessToken = AuthorizeCorp::getAccessToken($corpid, '全量获取企业部门、标签和员工信息');
  132. if(empty($accessToken)) {
  133. Log::logError('syncCorpData', [
  134. 'params' => $requestData,
  135. 'errmsg' => '获取企业access_token失败',
  136. ], 'interface');
  137. return [['error' => '获取企业access_token失败'], 2000];
  138. }
  139. // 根据企业获取部门列表
  140. $departmentResponse = QyCommon::simplelist($corpid);
  141. if(isset($departmentResponse['errcode']) && $departmentResponse['errcode'] == 0) {
  142. $departmentList = $departmentResponse['department_id'];
  143. } else {
  144. Log::logError('syncCorpData', [
  145. 'params' => $requestData,
  146. 'errmsg' => '部门权限获取失败',
  147. 'response' => $departmentResponse,
  148. ], 'interface');
  149. $content = '同步企微信息--获取授权部门列表失败';
  150. EmailQueue::rPush($subject, nl2br($content), ['song.shen@kuxuan-inc.com'], config('app.name'));
  151. return [['error' => '部门权限获取失败'], 2001];
  152. }
  153. if(empty($departmentList)) {
  154. Log::logError('syncCorpData', [
  155. 'params' => $requestData,
  156. 'errmsg' => '获取部门列表为空',
  157. 'response' => $departmentResponse,
  158. ], 'interface');
  159. return [['error' => '获取部门列表为空'], 2009];
  160. }
  161. // 将部门信息保存到本地
  162. foreach($departmentList as $department) {
  163. $departmentId = $department['id'];
  164. $departmentInfoResponse = QyCommon::get_department_info($corpid, $departmentId);
  165. if(isset($departmentInfoResponse['errcode']) && $departmentInfoResponse['errcode'] == 0) {
  166. $departmentInfo = $departmentInfoResponse['department'];
  167. if(!empty($departmentInfo)) {
  168. DjDepartment::departmentInfoSave($corpid, $departmentInfo);
  169. }
  170. }
  171. $res = wechatWorkService::departmentUserList($corpid, $departmentId, $accessToken);
  172. if(isset($res['errcode']) && $res['errcode'] == 0) {
  173. if(!empty($res['userlist'])) {
  174. // 将员工信息保存入库
  175. foreach($res['userlist'] as $user) {
  176. $userId = DjUser::departmentUserSave($corpid, $user);
  177. }
  178. } else {
  179. Log::logInfo('syncCorpData 获取内部员工信息为空 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  180. }
  181. } else {
  182. Log::logInfo('syncCorpData 获取内部员工信息失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  183. return [['error' => '获取内部员工信息失败'], 2002];
  184. }
  185. }
  186. # 获取所有企业标签
  187. $data1 = wechatWorkService::getCorTagList($corpid);
  188. if(empty($data1)) {
  189. Log::logInfo('syncCorpData 获取企业标签失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  190. return [['error' => '获取企业标签失败'], 2005];
  191. } else {
  192. $re1 = wechatWorkService::tagDataSave($corpid, 1, $data1);
  193. if(!$re1) {
  194. Log::logInfo('syncCorpData 保存企业标签失败 request:'.json_encode($requestData, JSON_UNESCAPED_UNICODE), [], 'interface');
  195. return [['error' => '保存企业标签失败'], 2006];
  196. }
  197. }
  198. $followUserList = DjUser::getUserIdList($corpid);
  199. $followUserList = json_decode(json_encode($followUserList), 1);
  200. // 循环处理配置了客户联系功能的员工,拉取对应的客户信息
  201. // 如果使用没有配置联系我的内部员工id来请求数据的话,接口会返回user_id异常的错误信息
  202. $followUserListArr = array_chunk($followUserList, 100);
  203. foreach ($followUserListArr as $userArr) {
  204. $value['corpid'] = $corpid;
  205. $value['user_list'] = $userArr;
  206. RedisModel::lPush(AuthorizeCorp::SYNC_CORP_CUSTOMER, json_encode($value));
  207. }
  208. RedisModel::set($redisKey, time());
  209. RedisModel::expire($redisKey, 600);
  210. return [['info' => '获取成功'], 0];
  211. } catch (\Exception $exception) {
  212. Log::logError('syncCorpData',[
  213. 'params' => $requestData,
  214. 'exception' => $exception->getMessage(),
  215. 'file' => $exception->getFile(),
  216. 'line' => $exception->getLine(),
  217. 'trace' => $exception->getTraceAsString(),
  218. ], 'interface');
  219. $subject = config('app.name') . '于' . date('d日H:i:s') . '抛出异常';
  220. $content = $exception->getMessage() . "\r\n\r\n" . $exception->getTraceAsString();
  221. EmailQueue::rPush($subject, nl2br($content), ['song.shen@kuxuan-inc.com'], config('app.name'));
  222. return [['errcode' => '系统异常'], 400];
  223. }
  224. }
  225. // 根据企业id获取企业部门列表
  226. public static function getCorDepartmentList($corpid, $accessToken = null)
  227. {
  228. if(empty($corpid) && empty($accessToken)) {
  229. return [];
  230. }
  231. if(empty($accessToken)) {
  232. //根据企业id获取对应的access_token
  233. $accessToken = AuthorizeCorp::getAccessToken($corpid, '根据企业id获取企业部门列表');
  234. }
  235. // 部门id。获取指定部门及其下的子部门(以及子部门的子部门等等,递归)。 如果不填,默认获取全量组织架构
  236. $url = 'https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token='.$accessToken.'&id=';
  237. $result = HttpService::httpGet($url);
  238. $result = json_decode($result, 1);
  239. Log::logInfo('获取企业部门列表,URL'.$url, (array)$result, 'department');
  240. if(isset($result['errcode']) && $result['errcode'] == 0) {
  241. // 请求成功
  242. $departmentList = $result['department_id'];
  243. return $departmentList;
  244. } else {
  245. // 请求失败,将响应数据写入日志
  246. EmailQueue::rPush('获取企业部门列表失败', json_encode([
  247. 'corpid' => $corpid, 'result' => (array)$result
  248. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  249. return [];
  250. }
  251. }
  252. // 根据部门id获取部门详情
  253. public static function getDepartmentInfo($departmentId, $corpid, $accessToken = null)
  254. {
  255. if((empty($corpid) && empty($accessToken)) || empty($departmentId)) {
  256. return [];
  257. }
  258. if(empty($accessToken)) {
  259. //根据企业id获取对应的access_token
  260. $accessToken = AuthorizeCorp::getAccessToken($corpid, '根据部门id获取部门详情');
  261. }
  262. $url = 'https://qyapi.weixin.qq.com/cgi-bin/department/get?access_token='.$accessToken.'&id='.$departmentId;
  263. $result = HttpService::httpGet($url);
  264. $result = json_decode($result, 1);
  265. if(isset($result['errcode']) && $result['errcode'] == 0) {
  266. return $result['department'];
  267. } else {
  268. // 请求失败,将响应数据写入日志
  269. Log::logError('获取部门详情失败,URL:'.$url, (array)$result, 'department');
  270. EmailQueue::rPush('获取部门详情失败', json_encode([
  271. 'corpid' => $corpid, 'department_id' => $departmentId, 'result' => (array)$result
  272. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  273. return [];
  274. }
  275. }
  276. // 获取部门下的员工列表
  277. public static function departmentUserList($corpid, $departmentId, $accessToken = null)
  278. {
  279. if((empty($corpid) && empty($accessToken)) || empty($departmentId)) {
  280. return [];
  281. }
  282. if(empty($accessToken)) {
  283. //根据企业id获取对应的access_token
  284. $accessToken = AuthorizeCorp::getAccessToken($corpid, '获取部门下的员工列表');
  285. }
  286. $fetchChild = 1; // 是否递归获取子部门下面的成员:1-递归获取,0-只获取本部门
  287. $url = 'https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token='.$accessToken.'&department_id='.$departmentId.'&fetch_child='.$fetchChild;
  288. $result = HttpService::httpGet($url);
  289. $result = json_decode($result, 1);
  290. if(isset($result['errcode']) && $result['errcode'] == 0) {
  291. return $result;
  292. } else {
  293. // 请求失败,将响应数据写入日志
  294. Log::logError('获取部门下的成员列表失败,URL:'.$url, (array)$result, 'get_department_user_list');
  295. EmailQueue::rPush('获取部门下的成员列表失败', json_encode([
  296. 'corpid' => $corpid, 'department_id' => $departmentId, 'result' => (array)$result
  297. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  298. return [];
  299. }
  300. }
  301. // 根据内容员工获取外部客户外层方法(有分页,需要循环获取)
  302. public static function getCustomerListByUserId($corpid, $userList, $accessToken = null)
  303. {
  304. if(empty($corpid) || empty($userList)) {
  305. return [];
  306. }
  307. $params = [
  308. 'userid_list' => $userList,
  309. 'cursor' => '',
  310. 'limit' => 100
  311. ];
  312. $flag = true;
  313. $result = null;
  314. $count = 0;
  315. while ($flag) {
  316. $accessToken = AuthorizeCorp::getAccessToken($corpid, '根据员工获取外部客户');
  317. try{
  318. $result = wechatWorkService::getCustomerListMethod($params, $accessToken, $corpid);
  319. // if($corpid == 'wwdfec0bcee287d5c5') {
  320. // Log::logInfo('同步客户', ['corpid' => $corpid, 'user_list' => $userList, 'res' =>$result], 'get_corp_customer_list');
  321. // }
  322. if(!$result) {
  323. $flag = false;
  324. }
  325. if(!empty($result['external_contact_list'])) {
  326. // 将数据保存入库,并进行翻页查询
  327. self::userCustomerDataSave($result['external_contact_list'], $corpid);
  328. $count += count($result['external_contact_list']);
  329. }
  330. # 全部都没有许可时直接返回错误码
  331. if(isset($result['errcode']) && $result['errcode'] == 701008) {
  332. foreach($userList as $userId) {
  333. RedisModel::lPush(self::SYNC_FAIL_USER_RDS, json_encode([
  334. 'corpid' => $corpid, 'user_id' => $userId
  335. ]));
  336. }
  337. $flag = false;
  338. }
  339. if(isset($result['errcode']) && $result['errcode'] == 60111) {
  340. # 部分客服userid不存在(在放入队列之后客服离职了)
  341. // 提取客服userid,将异常userid从数据中排除,然后将处理好的数据重新赛回待处理队列
  342. $errmsg = $result['errmsg'] ?? '';
  343. preg_match("/`(.*)`/i", $errmsg, $userArr);
  344. if(isset($userArr[1]) && !empty($userArr[1])) {
  345. $userId = $userArr[1];
  346. $key = array_search($userId, $userList);
  347. unset($userList[$key]);
  348. RedisModel::lPush(AuthorizeCorp::DAILY_SYNC_CORP_CUSTOMER, json_encode([
  349. 'corpid' => $corpid,
  350. 'user_list' => array_values($userList),
  351. ]));
  352. } else {
  353. EmailQueue::rPush('同步客服对应客户列表中处理userid异常', json_encode([
  354. 'corpid' => $corpid,
  355. 'user_list' => $userList,
  356. 'result' => $result
  357. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  358. }
  359. $flag = false;
  360. }
  361. # 部分客服没有许可时会返回无许可客服列表
  362. if(!empty($result['fail_info']['unlicensed_userid_list']) && empty($params['cursor'])) {
  363. foreach($result['fail_info']['unlicensed_userid_list'] as $userId) {
  364. RedisModel::lPush(self::SYNC_FAIL_USER_RDS, json_encode([
  365. 'corpid' => $corpid, 'user_id' => $userId
  366. ]));
  367. }
  368. }
  369. if(empty($result['next_cursor'])) {
  370. $flag = false;
  371. } else {
  372. $params['cursor'] = $result['next_cursor'];
  373. }
  374. } catch (\Exception $exception) {
  375. Log::logError('获取企业微信客户数据异常',[
  376. 'exception' => $exception->getMessage(),
  377. 'file' => $exception->getFile(),
  378. 'line' => $exception->getLine(),
  379. ], 'sync_corp_customer_exception');
  380. $subject = config('app.name') . '于' . date('d日H:i:s') . '抛出异常';
  381. $content = $exception->getMessage() . "\r\n\r\n" . $exception->getTraceAsString();
  382. EmailQueue::rPush($subject, nl2br($content), ['song.shen@kuxuan-inc.com'], config('app.name'));
  383. }
  384. }
  385. self::checkoutResponse($corpid, $userList, $count);
  386. Log::logInfo('获取客户列表结果记录', [
  387. 'corpid' => $corpid, 'user_list' => $userList, 'count' => $count
  388. ], 'get_corp_customer_list');
  389. }
  390. // 根据内部员工获取外部联系客户具体方法
  391. public static function getCustomerListMethod($params, $accessToken, $corpid, $retry = 0)
  392. {
  393. $url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user?access_token='.$accessToken;
  394. $result = HttpService::httpPost($url, json_encode($params), true);
  395. $result = mb_convert_encoding($result, "UTF-8");
  396. $result = json_decode($result, 1);
  397. if((empty($result) || (isset($result['errcode']) && in_array($result['errcode'], [-1]))) && $retry < 5) {
  398. sleep(2);
  399. $retry++;
  400. return self::getCustomerListMethod($params, $accessToken, $corpid, $retry);
  401. }
  402. if(isset($result['errcode']) && in_array($result['errcode'], [0, 701008, 60111, 610023])) {
  403. return $result;
  404. } else {
  405. // 请求失败,将响应数据写入日志
  406. Log::logError('获取内部员工对应的外部联系人列表失败:URL'.$url.', 请求参数:'.json_encode(array_merge($params, ['corpid' => $corpid])),
  407. (array)$result, 'get_user_customer_list');
  408. EmailQueue::rPush('获取内部员工对应的外部联系人列表失败', json_encode([
  409. 'url' => $url, 'params' => array_merge($params, ['corpid' => $corpid]), 'result' => (array)$result
  410. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  411. return false;
  412. }
  413. }
  414. public static function checkoutResponse($corpid, $userList, $count) {
  415. # 当同步单个客服时,对比同步下来的客户数与客户总数,查看是否完全同步
  416. if(count($userList) == 1) {
  417. $response = QyCommon::getExternalContactList($corpid, current($userList));
  418. if(isset($response['errcode'])) {
  419. if(0 == $response['errcode']) {
  420. $customerCount = count($response['external_userid']);
  421. } else if (in_array($response['errcode'], [701008, 84061])) {
  422. $customerCount = 0;
  423. } else {
  424. $customerCount = 0;
  425. EmailQueue::rPush('获取客户列表结果异常', json_encode([
  426. 'corpid' => $corpid, 'user_id' => current($userList), 'response' => $response
  427. ]), ['song.shen@kuxuan-inc.com'], '猎羽系统');
  428. }
  429. if($customerCount > $count) {
  430. EmailQueue::rPush('批量获取客户数据数少于实际客户数', json_encode([
  431. 'corpid' => $corpid, 'user_id' => current($userList), 'real_count' => $customerCount, 'get_count' => $count
  432. ]), ['song.shen@kuxuan-inc.com'], '猎羽系统');
  433. }
  434. }
  435. }
  436. }
  437. // 内部员工以及外部客户联系等数据保存入库
  438. public static function userCustomerDataSave($data, $corpid)
  439. {
  440. try{
  441. foreach($data as $item) {
  442. // 企业成员客户跟进信息
  443. $followInfo = $item['follow_info'];
  444. // 客户基本信息
  445. $externalContact = $item['external_contact'];
  446. \DB::beginTransaction();
  447. // 保存客户信息,并返回客户id
  448. $customerId = Customer::externalContactDataSave($corpid, $externalContact);
  449. // 保存员工客户跟进信息
  450. if($customerId > 0) {
  451. // $followInfoId = CustomerRelation::followInfoDataSave($corpid, $followInfo, $customerId,
  452. // $externalContact['external_userid']);
  453. $followInfoId = CustomerDetails::followInfoDataSave($corpid, $followInfo, $customerId,
  454. $externalContact);
  455. // 自动打日期标签
  456. // TagService::autoMarkCustomerAddDateTag($corpid, $customerId, $followInfo);
  457. if($followInfoId > 0) {
  458. // // 如果标签列表不为空,则将标签信息也一并记录下来
  459. // if(isset($followInfo['tag_id']) && !empty($followInfo['tag_id'])) {
  460. // $customerRes = CustomerTag::customerTagSave($corpid, $followInfo, $externalContact,
  461. // $customerId);
  462. // if($customerRes > 0) {
  463. // \DB::commit();
  464. // } else {
  465. // \DB::rollBack();
  466. // }
  467. // } else {
  468. \DB::commit();
  469. // }
  470. } else {
  471. \DB::rollBack();
  472. }
  473. } else {
  474. \DB::rollBack();
  475. $params = $item;
  476. $params['corpid'] = $corpid;
  477. // 保存用户信息异常
  478. Log::logError('保存客户信息失败, 参数'.json_encode($params), [],
  479. 'external_contract_data_save');
  480. }
  481. }
  482. } catch (\Exception $exception) {
  483. EmailQueue::rPush('保存客户信息失败', $exception->getTraceAsString(), ['xiaohua.hou@kuxuan-inc.com'], '保存客户信息失败');
  484. \DB::rollBack();
  485. // 保存用户信息异常
  486. Log::logError('保存客户信息失败', [
  487. 'exception' => $exception->getMessage(),
  488. 'file' => $exception->getFile(),
  489. 'line' => $exception->getLine(),
  490. ], 'external_contract_data_save');
  491. }
  492. }
  493. // 获取配置了客户联系功能的成员列表
  494. public static function followUserList($corpid, $accessToken = null)
  495. {
  496. if(empty($corpid)) {
  497. return [];
  498. }
  499. if(empty($accessToken)) {
  500. $accessToken = AuthorizeCorp::getAccessToken($corpid, '获取配置了客户联系功能的成员列表');
  501. }
  502. $url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list?access_token='.$accessToken;
  503. $result = HttpService::httpGet($url);
  504. $result = json_decode($result, 1);
  505. if(isset($result['errcode']) && $result['errcode'] == 0) {
  506. return [$result['follow_user'], 0];
  507. } else {
  508. $errcode = $result['errcode'] ?? null;
  509. // 请求失败,将响应数据写入日志
  510. Log::logError('获取配置了客户联系功能的成员列表失败,url:'.$url,
  511. (array)$result,
  512. 'follow_user_list');
  513. EmailQueue::rPush('获取配置了客户联系功能的成员列表失败', json_encode([
  514. 'corpid' => $corpid, 'result' => (array)$result
  515. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  516. return [[], $errcode];
  517. }
  518. }
  519. // 获取企业标签
  520. public static function getCorTagList($corpid, Array $tagId = [], Array $groupId = [], $accessToken = null)
  521. {
  522. if(empty($corpid) && empty($accessToken)) {
  523. return [];
  524. }
  525. if(empty($accessToken)) {
  526. $accessToken = AuthorizeCorp::getAccessToken($corpid, '获取企业标签');
  527. }
  528. $url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_corp_tag_list?access_token='.$accessToken;
  529. $params = [];
  530. if(!empty($tagId)) {
  531. $params['tag_id'] = $tagId;
  532. }
  533. if(!empty($groupId)) {
  534. $params['group_id'] = $groupId;
  535. }
  536. $result = HttpService::httpPost($url, json_encode($params), true);
  537. $result = json_decode($result, 1);
  538. if(isset($result['errcode']) && $result['errcode'] == 0) {
  539. return $result['tag_group'];
  540. } else {
  541. // 请求失败,将响应数据写入日志
  542. Log::logError('获取企业下所有标签失败,url:'.$url,
  543. (array)$result,
  544. 'get_cor_tag_list');
  545. EmailQueue::rPush('获取企业下标签失败', json_encode([
  546. 'corpid' => $corpid, 'url' => $url, 'request' => (array)$result
  547. ]), ['song.shen@kuxuan-inc.com'], '猎羽');
  548. return [];
  549. }
  550. }
  551. // 获取所有企业规则组标签
  552. public static function getStrategyTagList($corpid, $strategyId = null, Array $tagId = [], Array $groupId = [], $accessToken = null)
  553. {
  554. if(empty($corpid) && empty($accessToken)) {
  555. return [];
  556. }
  557. if(empty($accessToken)) {
  558. $accessToken = AuthorizeCorp::getAccessToken($corpid, '获取所有企业规则组标签');
  559. }
  560. $url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_strategy_tag_list?access_token='.$accessToken;
  561. $params = [];
  562. if(!empty($strategyId)) {
  563. $params['strategy_id'] = $strategyId;
  564. }
  565. if(!empty($tagId)) {
  566. $params['tag_id'] = $tagId;
  567. }
  568. if(!empty($groupId)) {
  569. $params['group_id'] = $groupId;
  570. }
  571. $result = HttpService::httpPost($url, json_encode($params), true);
  572. $result = json_decode($result, 1);
  573. if(isset($result['errcode']) && $result['errcode'] == 0) {
  574. return $result['tag_group'];
  575. } else {
  576. // 请求失败,将响应数据写入日志
  577. Log::logError('获取企业下所有规则组标签失败,URL:'.$url.',参数:'.json_encode($params),
  578. (array)$result,
  579. 'get_strategy_tag_list');
  580. return [];
  581. }
  582. }
  583. // 标签信息保存入库
  584. public static function tagDataSave($corpid, $type, $data)
  585. {
  586. if(empty($corpid) || empty($data)) {
  587. return false;
  588. }
  589. try{
  590. // 添加编辑标签
  591. if(isset($data['group_id'])) {
  592. $groupRes = TagGroup::dataSave($corpid, $type, $data);
  593. if(!empty($data['tag'])) {
  594. foreach($data['tag'] as $tag) {
  595. $tagRes = Tag::dataSave($corpid, $data['group_id'], $tag);
  596. if(0 == $tagRes) {
  597. return false;
  598. }
  599. }
  600. }
  601. if(0 == $groupRes) {
  602. return false;
  603. }
  604. } else {
  605. // 获取企业下所有标签
  606. foreach($data as $group) {
  607. $groupRes = TagGroup::dataSave($corpid, $type, $group);
  608. if(!empty($group['tag'])) {
  609. foreach($group['tag'] as $tag) {
  610. $tagRes = Tag::dataSave($corpid, $group['group_id'], $tag);
  611. if(0 == $tagRes) {
  612. return false;
  613. }
  614. }
  615. }
  616. if(0 == $groupRes) {
  617. return false;
  618. }
  619. }
  620. }
  621. } catch (\Exception $exception) {
  622. Log::logError('标签信息保存入库失败', [
  623. 'exception' => $exception->getMessage(),
  624. 'line' => $exception->getLine(),
  625. 'file' => $exception->getFile()
  626. ], 'tag_data_save');
  627. }
  628. return true;
  629. }
  630. /**
  631. * 根据客户跟进关系表中标签信息
  632. * @param $corpid -企业id
  633. * @param $type -标签类型 1企业标签 2用户自定义标签 3规则组标签
  634. * @param $data -标签数据
  635. * @return bool
  636. */
  637. public static function updateCustomerTag($corpid, $type, $data)
  638. {
  639. if(empty($corpid) || empty($data)) {
  640. return false;
  641. }
  642. \DB::begintransaction();
  643. foreach($data as $group) {
  644. $item['group_name'] = $group['group_name'];
  645. $item['type'] = $type;
  646. if(!empty($group['tag'])) {
  647. foreach($group['tag'] as $tag) {
  648. $item['tag_name'] = $tag['name'];
  649. $res = CustomerTag::updateCustomerTag($corpid, $tag['id'], $item);
  650. if($res == 0) {
  651. \DB::rollBack();
  652. return false;
  653. }
  654. }
  655. }
  656. }
  657. \DB::commit();
  658. return true;
  659. }
  660. /*
  661. * 创建企业标签
  662. * $corpid 企业id 获取access_token用
  663. * $groupId 标签组id 可为空
  664. * $groupName 标签组名称,最长为30个字符 可为空,$groupId和$groupName不能同时为空
  665. * $order 标签组次序值,order值大的排序靠前。有效的值范围是[0, 2^32) 可为空
  666. * $tag 标签数组
  667. * $name 标签名称,最长30个字符,必传
  668. * $order 标签次序值 可为空
  669. */
  670. public static function addCorpTag($corpid, $groupId, $groupName, $order, $tag, $accessToken = null)
  671. {
  672. if(empty($corpid) || empty($tag) || (empty($groupName) && empty($groupId))) {
  673. return false;
  674. }
  675. if(empty($accessToken)) {
  676. $accessToken = AuthorizeCorp::getAccessToken($corpid, '创建企业标签');
  677. }
  678. $url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag?access_token='.$accessToken;
  679. $params['tag'] = $tag;
  680. if(!empty($groupId)) {
  681. $params['group_id'] = $groupId;
  682. }
  683. if(!empty($groupName)) {
  684. $params['group_name'] = $groupName;
  685. }
  686. if(!empty($order)) {
  687. $params['order'] = $order;
  688. }
  689. $result = HttpService::httpPost($url, json_encode($params), true);
  690. $result = json_decode($result, 1);
  691. if(isset($result['errcode']) && $result['errcode'] == 0) {
  692. // 将数据保存入库
  693. $res = self::tagDataSave($corpid, 1, $result['tag_group']);
  694. if($res) {
  695. return true;
  696. } else {
  697. return false;
  698. }
  699. } else {
  700. Log::logError('新增企业标签失败, URL:'.$url.', 请求参数:'.json_encode($params), (array)$result,
  701. 'add_corp_tag');
  702. return false;
  703. }
  704. }
  705. /*
  706. * 编辑客户企业标签
  707. * @param $corpid string 企业id
  708. * @param $userId string 添加外部联系人的userid 必传
  709. * @param $externalUserId string 外部联系人userid 必传
  710. * @param $addTag array 要标记的标签id列表 可为空
  711. * @param $removeTag array 要移除的标签id列表 可为空 add_tag和remove_tag不可同时为空。
  712. * @param null $accessToken
  713. * @return bool
  714. */
  715. public static function markTag($corpid, $userId, $externalUserId, $addTag, $removeTag, $accessToken = null, $retry = 0)
  716. {
  717. if((empty($corpid) && empty($accessToken)) || empty($userId) || empty($externalUserId)) {
  718. return false;
  719. }
  720. if(empty($accessToken)) {
  721. $accessToken = AuthorizeCorp::getAccessToken($corpid, '编辑客户企业标签');
  722. }
  723. $url = 'https://qyapi.weixin.qq.com/cgi-bin/externalcontact/mark_tag?access_token='.$accessToken;
  724. $params['userid'] = $userId;
  725. $params['external_userid'] = $externalUserId;
  726. if(!empty($addTag)) {
  727. $params['add_tag'] = $addTag;
  728. }
  729. if(!empty($removeTag)) {
  730. $params['remove_tag'] = $removeTag;
  731. }
  732. $result = HttpService::httpPost($url, json_encode($params), true);
  733. $result = json_decode($result, 1);
  734. Log::logInfo('客户打标签接口', [
  735. 'corpid' => $corpid,
  736. 'params' => $params,
  737. 'response' => $result
  738. ], 'markTag');
  739. $returnFlag = false;
  740. if(isset($result['errcode']) && $result['errcode'] == 0) {
  741. //编辑成功了,还需要处理本地的数据
  742. TagService::updateLocalCustomerTagSecond($corpid, $userId, $externalUserId, $addTag, $removeTag);
  743. $returnFlag = true;
  744. } else {
  745. // 员工不在企微 跳过不处理
  746. if('60111' == $result['errcode']) {
  747. $returnFlag = true;
  748. }
  749. // 不存在的客户联系关系 跳过不处理
  750. if('84061' == $result['errcode']) {
  751. RedisModel::lPush(CustomerDetails::CUSTOMER_LOSS_INFO_RDS, json_encode([
  752. 'corpid' => $corpid,
  753. 'user_id' => $userId,
  754. 'external_userid' => $externalUserId
  755. ], 256));
  756. $returnFlag = true;
  757. }
  758. if(in_array($result['errcode'], [45033, 45035, -1])) {
  759. if($retry < 5){
  760. $retry++;
  761. $seconds = rand(1,5);
  762. sleep($seconds);
  763. return self::markTag($corpid, $userId, $externalUserId, $addTag, $removeTag, null, $retry);
  764. }
  765. }
  766. EmailQueue::rPush('【'.$corpid.'】客户打标签失败', json_encode([
  767. 'url' => $url, 'params' => $params, 'result' => $result
  768. ], 256), ['song.shen@kuxuan-inc.com'], '猎羽系统');
  769. }
  770. return $returnFlag;
  771. }
  772. public static function convertUseridToOpenid($corpid, $userId)
  773. {
  774. if(empty($corpid) || empty($userId)) {
  775. return false;
  776. }
  777. $accessToken = AuthorizeCorp::getAccessToken($corpid, 'UserId转换OpenId');
  778. $url = 'https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid?access_token='.$accessToken;
  779. $params['userid'] = $userId;
  780. $result = HttpService::httpPost($url, json_encode($params), true);
  781. $result = json_decode($result, 1);
  782. if(isset($result['errcode']) && $result['errcode'] == 0) {
  783. // 请求成功了
  784. return $result['openid'];
  785. } else {
  786. return false;
  787. }
  788. }
  789. }