Changedomain.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Elton
  5. * Date: 2019/3/4
  6. * Time: 10:56
  7. */
  8. namespace app\api\controller;
  9. use app\common\controller\Api;
  10. use app\common\library\Redis;
  11. use app\main\constants\AdminConstants;
  12. use app\main\service\ChangeMenuService;
  13. use EasyWeChat\Factory;
  14. use Symfony\Component\Cache\Simple\RedisCache;
  15. use think\Config;
  16. use fast\Http;
  17. use think\Db;
  18. use think\Log;
  19. class Changedomain extends Api
  20. {
  21. const CACHE_KEY_ADMIN_INFO = 'ANI:';
  22. const DEAD_DOMAIN_PRE = 'DEADDOMAIN:';
  23. const PARAM_ERROR = 0;
  24. const PARAM_SUCCESS = 1;
  25. const VALID_URL_API = 'http://123.206.179.42/WxListen/api.php?sign=3358471198&url=';
  26. protected $adminExtend_model = null;
  27. protected $adminConfig_model = null;
  28. protected $authGroupAccess_model = null;
  29. protected $ophost_model = null;
  30. protected $ptoken_model = null;
  31. protected $domain_suffix = '';
  32. protected $app_id = '';
  33. protected $pt = '';
  34. protected $redis = null;
  35. public function _initialize()
  36. {
  37. parent::_initialize();
  38. $this->adminExtend_model = model('AdminExtend');
  39. $this->adminConfig_model = model('AdminConfig');
  40. $this->authGroupAccess_model = model('AuthGroupAccess');
  41. $this->ophost_model = model('Ophost');
  42. $this->ptoken_model = model('Ptoken');
  43. $this->redis = Redis::instance();
  44. }
  45. /**
  46. * 1. 接收一个域名{顶级域名 | 二级域名}
  47. * 首先验证顶级域名是否被封了
  48. * { 被封,切换顶级域名 | 顶级没有被封,检查二级域名是否被封,被封,切二级域名 }
  49. * 2. 查找一个可用的域名{ 异步 }
  50. * 3. 将可用域名替换被封掉的域名 { 异步 }
  51. * 4. 发送企业微信 { 异步 | }
  52. */
  53. // 相关表:ophost | admin_config | ptoken | platform
  54. public function index()
  55. {
  56. $param = $this->request->param();
  57. $admin_id = $param['admin_id'] ?? 0;
  58. $domain = $param['domain'] ? urldecode($param['domain']) : '';
  59. $this->pt = $param['pt'] ?? '';
  60. Log::info('[ ChangeDomain ] [doAsynRequest] START');
  61. Log::info('[ ChangeDomain ] [Params] '. json_encode($param));
  62. $adminInfo = $this->_getAdminInfo($admin_id);
  63. Log::info('[ ChangeDomain ] [adminInfo] '. json_encode($adminInfo));
  64. $this->domain_suffix = $this->getDomainSuffix($domain);
  65. if(empty($domain)){
  66. echo json_encode(['msg' => '参数domain不能为空', 'status' => self::PARAM_ERROR]);
  67. exit();
  68. }
  69. if (!$adminInfo) {
  70. // admin_id = 0 && 仅传入了域名的情况
  71. $this->checkTopDomain($domain, $admin_id);
  72. Log::info('[ ChangeDomain ] [doAsynRequest] END');
  73. echo json_encode(['msg' => 'Success', 'status' => self::PARAM_SUCCESS]);
  74. } else {
  75. echo json_encode(['msg' => 'Success', 'status' => self::PARAM_SUCCESS]);
  76. fastcgi_finish_request(); /* 响应完成, 关闭连接 */
  77. // 异步处理
  78. $this->asynHandle($admin_id, urlencode($domain));
  79. Log::info('[ ChangeDomain ] [doAsynRequest] END');
  80. }
  81. }
  82. /**
  83. * 异步任务处理
  84. * @param $admin_id
  85. * @param $domain
  86. */
  87. public function asynHandle($admin_id, $domain)
  88. {
  89. $domain = urldecode($domain);
  90. Log::info('[ ChangeDomain ] [ asynHandle ] start');
  91. Log::info('[ ChangeDomain ] [ asynHandle ] $admin_id:' . $admin_id . '$domain:' . $domain);
  92. $this->domain_suffix = $this->getDomainSuffix($domain);
  93. Log::info('[ ChangeDomain ] [ asynHandle ] domain_suffix:' . $this->domain_suffix);
  94. $adminInfo = $this->_getAdminInfo($admin_id);
  95. $platform_list = $adminInfo['platform_list'] ?? '';
  96. $platform_id = $adminInfo['platform_id'] ?? '';
  97. $ophost_id = $adminInfo['ophost_id'] ?? '';
  98. $menu_platform_id = $adminInfo['menu_platform_id'] ?? '';
  99. $menuophost_id = $adminInfo['menuophost_id'] ?? '';
  100. //获取顶级域名
  101. $top_domain = $this->_getTopDomain($domain);
  102. //判断顶级域名是否被封
  103. if (!$this->_isValidDomain($top_domain)) {
  104. // 企业微信报警,顶级挂了 && 1小时内,同一个顶级不重复报警
  105. $this->checkTopDomain($top_domain, $admin_id);
  106. }else{
  107. $valid_ophost_domain_obj = null;
  108. $valid_menu_domain_obj = null;
  109. Log::info("[ ChangeDomain ] [ asynHandle ] [ platform_list ]" . print_r(json_encode($platform_list), true));
  110. $valid_ophost_domain_obj = $this->getValidHostObj($admin_id, $platform_id, $platform_list);
  111. $valid_menu_domain_obj = $this->getValidHostObj($admin_id, $menu_platform_id, $platform_list);
  112. if (!$valid_ophost_domain_obj) {
  113. $content = "";
  114. $content .= "[Error]".date('Y-m-d H:i:s');
  115. $content .= "\n错误原因:业务域名无域名可切";
  116. $content .= "\n被封域名:{$domain}";
  117. $content .= $this->_packageNotice($admin_id);
  118. Log::info("[ ChangeDomain ] [ asynHandle ] [ 业务域名 ]" . date('Y-m-d H:i:s') . "错误原因:无域名可切,被封域名:{$domain}");
  119. $this->_sendWorkChatMessage($content);
  120. }else{
  121. // 替换
  122. $this->_modifyOphost($ophost_id, $valid_ophost_domain_obj['ophost_id'], $admin_id);
  123. }
  124. if (!$valid_menu_domain_obj) {
  125. $content = "";
  126. $content .= "[Error]".date('Y-m-d H:i:s');
  127. $content .= "\n错误原因:菜单域名无域名可切";
  128. $content .= "\n被封域名:{$domain}";
  129. $content .= $this->_packageNotice($admin_id);
  130. Log::info("[ ChangeDomain ] [ asynHandle ] [ 菜单业务域名 ]" . date('Y-m-d H:i:s') . "错误原因:无域名可切,被封域名:{$domain}");
  131. $this->_sendWorkChatMessage($content);
  132. }else{
  133. $this->_modifyMenuOphost($menuophost_id, $valid_menu_domain_obj['ophost_id'], $admin_id);
  134. }
  135. // 清除缓存
  136. $this->redis->del(self::CACHE_KEY_ADMIN_INFO . $admin_id);
  137. }
  138. return json(['data'=>'ok']);
  139. }
  140. public function getDomainSuffix($domain)
  141. {
  142. $tmp_domain = str_replace('http://', '', $domain);
  143. $tmp_domain = str_replace('https://', '', $tmp_domain);
  144. $tmp_domain_arr = explode('/', $tmp_domain);
  145. unset($tmp_domain_arr[0]);
  146. $str = implode('/',$tmp_domain_arr);
  147. return $str;
  148. }
  149. /**
  150. * 顶级被封则发送企业微信 && 1小时内,同一个顶级不重复报警
  151. * @param $domain
  152. * @param $admin_id
  153. */
  154. public function checkTopDomain($domain, $admin_id)
  155. {
  156. $tmp_domain = $this->filterDomain($domain);
  157. if (!$this->redis->get(self::DEAD_DOMAIN_PRE . $tmp_domain)) {
  158. // 企业微信报警,顶级挂了 && 1小时内,同一个顶级不重复报警
  159. $content = "顶级域名{$tmp_domain}被封,请尽快处理!";
  160. $content .= $this->_packageNotice($admin_id);
  161. $this->_sendWorkChatMessage($content);
  162. $this->redis->setex(self::DEAD_DOMAIN_PRE . $tmp_domain, 3600, $tmp_domain);
  163. }
  164. }
  165. /**
  166. * 过滤url 中的 http:// https://
  167. * @param $domain
  168. * @return mixed
  169. */
  170. public function filterDomain($domain)
  171. {
  172. $tmp_domain = str_replace('http://', '', $domain);
  173. $tmp_domain = str_replace('https://', '', $tmp_domain);
  174. $tmp_domain_arr = explode('/', $tmp_domain);
  175. $tmp_domain = $tmp_domain_arr[0];
  176. return $tmp_domain;
  177. }
  178. /**
  179. * 获取顶级域名
  180. * @param $host
  181. * @return string
  182. */
  183. private function _getTopDomain($host)
  184. {
  185. $host = $this->filterDomain($host);
  186. Log::info('[ ChangeDomain ] [ TopDomain ] '.print_r($host, true));
  187. preg_match("/^wx\w+/i", $host, $matches);
  188. if ($matches) {
  189. $arr = explode('.', $host);
  190. unset($arr[0]);
  191. $top_domain = implode('.', $arr);
  192. Log::info('[ ChangeDomain ] [ TopDomain ] [ Result ]' . $top_domain);
  193. return $top_domain;
  194. }
  195. return $host;
  196. }
  197. /**
  198. * 返回可用的域名
  199. * @param $admin_id
  200. * @param $platform_id
  201. * @param string $platform_list
  202. * @return null
  203. */
  204. public function getValidHostObj($admin_id, $platform_id, $platform_list = '')
  205. {
  206. Log::info("[ ChangeDomain ] [ platform_list ] [ start ] " . print_r(json_encode($platform_list), true));
  207. $valid_domain_obj = null;
  208. $adminInfo = $this->_getAdminInfo($admin_id);
  209. $appid = isset($adminInfo['appid']) ? $adminInfo['appid'] : '';
  210. $platform_id_filter = !empty($platform_list) ? $platform_list : $platform_id;
  211. // 查找一个可用的域名
  212. $valid_domain_obj = $this->_getItemDomain($platform_id_filter, $appid);
  213. Log::info("[ ChangeDomain ] [ getValidHostObj ] [ 查找到的可用域名信息 ] {$admin_id}" . print_r(json_encode($valid_domain_obj), true));
  214. if (!$valid_domain_obj && !$platform_list) {
  215. // 拆解 platform_list
  216. $platform_arr = explode(',', $platform_list);
  217. $platform_arr = array_diff($platform_arr, [$platform_id]);
  218. Log::info("[ ChangeDomain ] [ platform_arr ] " . print_r(json_encode($platform_arr), true));
  219. // 检查平台是否授权,返回授权的平台
  220. foreach ($platform_arr as $key => $value) {
  221. $refresh_token = $this->ptoken_model->getToken($value, $admin_id);
  222. if ($refresh_token) {
  223. // 检查授权平台可用域名
  224. $valid_domain_obj = $this->_getItemDomain($value, $appid);
  225. if ($valid_domain_obj) {
  226. break;
  227. }
  228. }
  229. }
  230. }
  231. // 没有可用域名 ,返回NULL
  232. return $valid_domain_obj;
  233. }
  234. /**
  235. * 查找一个可用域名
  236. * @param $platform_id
  237. * @return null
  238. */
  239. private function _getItemDomain($platform_id, $appid = '')
  240. {
  241. // 查找一个可用的域名
  242. $valid_domain_arr = $this->_getValidDomainList($platform_id);
  243. Log::info("[ ChangeDomain ] [ getItemDomain ] [ 可用域名列表 ] " . print_r(json_encode($valid_domain_arr), true));
  244. $valid_domain_obj = null;
  245. if ($valid_domain_arr) {
  246. foreach ($valid_domain_arr as $k => $item) {
  247. // 请求接口,查看域名是否可用?
  248. // 如果可用,终止循环,返回可用域名
  249. if ($this->_isValidDomain($appid.'.'.$item['ophost_host'] . '/' . $this->domain_suffix)) {
  250. $valid_domain_obj = $item;
  251. break;
  252. }
  253. }
  254. }
  255. return $valid_domain_obj;
  256. }
  257. /**
  258. * 修改业务域名
  259. * @param $ophost_id
  260. * @param $admin_id
  261. * @return mixed
  262. */
  263. private function _modifyOphost($old_ophost_id, $ophost_id, $admin_id)
  264. {
  265. $adminInfo = $this->_getAdminInfo($admin_id);
  266. $appid = isset($adminInfo['appid']) ? $adminInfo['appid'] : '';
  267. $old_ophost = $this->_getOphostById($old_ophost_id);
  268. if (!$this->_isValidDomain($appid . '.' . $old_ophost . '/' . $this->domain_suffix)) {
  269. // 修改数据库
  270. $this->adminConfig_model->save(['ophost_id' => $ophost_id], ['admin_id' => $admin_id]);
  271. $ophost = $this->_getOphostById($ophost_id);
  272. // 发送企业微信 {成功 | 失败 都需要告知}
  273. $content = "";
  274. $content .= "[Success]" . date('Y-m-d H:i:s');
  275. $content .= "\n域名类型:业务域名";
  276. $content .= "\n被封域名:{$old_ophost}";
  277. $content .= "\n切换域名:{$ophost}";
  278. Log::info('[ ChangeDomain ] [ modifyOphost ] ' . "[Success]" . date('Y-m-d H:i:s') . "域名类型:业务域名,被封域名:{$old_ophost},切换域名:{$ophost}");
  279. $content .= $this->_packageNotice($admin_id);
  280. $this->_sendWorkChatMessage($content);
  281. }else{
  282. Log::info('[ ChangeDomain ] [ modifyOphost ] ' . "[Success]" . date('Y-m-d H:i:s') . "业务域名{$old_ophost},未被封无需替换");
  283. }
  284. }
  285. /**
  286. * 修改菜单业务域名
  287. * @param $menuophost_id
  288. * @param $admin_id
  289. * @return mixed
  290. */
  291. private function _modifyMenuOphost($old_ophost_id, $menuophost_id, $admin_id)
  292. {
  293. $adminInfo = $this->_getAdminInfo($admin_id);
  294. $appid = isset($adminInfo['appid']) ? $adminInfo['appid'] : '';
  295. $old_menu_ophost = $this->_getOphostById($old_ophost_id);
  296. if (!$this->_isValidDomain($appid . '.' . $old_menu_ophost . '/' . $this->domain_suffix)) {
  297. // 修改数据库
  298. $data = $this->adminConfig_model->save(['menuophost_id' => $menuophost_id], ['admin_id' => $admin_id]);
  299. if($data){
  300. //刷新菜单业务域名
  301. ChangeMenuService::instance()->setChannelId($admin_id)->setReplaceHostById($menuophost_id)->push();
  302. }
  303. $menu_ophost = $this->_getOphostById($menuophost_id);
  304. $content = "";
  305. $content .= "[Success]".date('Y-m-d H:i:s');
  306. $content .= "\n域名类型:菜单域名";
  307. $content .= "\n被封域名:{$old_menu_ophost}";
  308. $content .= "\n切换域名:{$menu_ophost}";
  309. Log::info('[ ChangeDomain ] [ modifyMenuOphost ] ' . "[Success]" . date('Y-m-d H:i:s') . "域名类型:菜单域名," . "被封域名:{$old_menu_ophost},切换域名:{$menu_ophost}");
  310. $content .= $this->_packageNotice($admin_id);
  311. $this->_sendWorkChatMessage($content);
  312. }else{
  313. Log::info('[ ChangeDomain ] [ modifyMenuOphost ] ' . "[Success]" . date('Y-m-d H:i:s') . "菜单业务域名{$old_menu_ophost},未被封无需替换");
  314. }
  315. }
  316. /**
  317. * 获取可用的域名列表
  318. * @param $platform_id
  319. * @return mixed
  320. */
  321. private function _getValidDomainList($platform_id)
  322. {
  323. $data = $this->ophost_model->getHostListByPlatformId($platform_id);
  324. return $data;
  325. }
  326. /**
  327. * 获取Admin所有信息
  328. * @param $admin_id
  329. * @return mixed
  330. */
  331. private function _getAdminInfo($admin_id)
  332. {
  333. return $this->adminConfig_model->getAdminInfoAll($admin_id);
  334. }
  335. /**
  336. * 获取域名信息
  337. * @param $ophost_id
  338. * @return string
  339. */
  340. private function _getOphostById($ophost_id)
  341. {
  342. $data = $this->ophost_model->get(['id' => $ophost_id]);
  343. return $data ? $data->host : '';
  344. }
  345. //发企业微信
  346. private function _sendWorkChatMessage($content)
  347. {
  348. if (empty($content)) {
  349. return false;
  350. }
  351. try {
  352. $wechat = Config::get('wechat');
  353. $wechat['http']['base_uri'] = $wechat['work']['base_uri'];
  354. $wechat['http']['timeout'] = 20;
  355. $wechat['corp_id'] = $wechat['work']['domain_corp_id'];
  356. $wechat['secret'] = $wechat['work']['domain_secret'];
  357. $app = Factory::work($wechat);
  358. $app['cache'] = new RedisCache(Redis::instanceCache());
  359. $res = $app->message
  360. ->message($content)
  361. ->ofAgent($wechat['work']['domain_agent_id'])
  362. ->toParty($wechat['work']['domain_party_id'])
  363. ->send();
  364. Log::info('[ ChangeDomain ] [ sendWorkChatMessage] '.print_r(json_encode($res), true));
  365. return $res;
  366. } catch (\Exception $exception) {
  367. Log::error('[ ChangeDomain ] [ sendWorkChatMessage] ' . $exception->getMessage());
  368. }
  369. }
  370. /**
  371. * 检测域名是否可以访问,是否被封
  372. * @param $url
  373. * @return bool
  374. */
  375. private function _isValidDomain($url)
  376. {
  377. sleep(1);
  378. Log::info('[ ChangeDomain ] [ checkurl ] ' . print_r($url, true));
  379. $url = self::VALID_URL_API . "{$url}";
  380. Log::info('[ ChangeDomain ] [ checkurl ] ' . $url);
  381. $json_data = Http::get($url);
  382. $request_count = 1;
  383. if (empty($json_data) && $request_count == 1) {
  384. $json_data = Http::get($url);
  385. $request_count++;
  386. echo $request_count;
  387. }
  388. Log::info('[ ChangeDomain ] [ checkurl ] [ Json_data] ' . $json_data);
  389. if(empty($json_data)){
  390. $this->_sendWorkChatMessage('【Error】域名监控接口返回值为空!!!');
  391. exit();
  392. }
  393. $data = json_decode($json_data, true);
  394. $ret = is_array($data) ? $data : false;
  395. Log::info('[ ChangeDomain ] [ checkurl ] [status] ' . $ret['status']);
  396. if (is_array($ret)) {
  397. /**
  398. * -2.请求失败
  399. * -1.cookie失效
  400. * 0.屏蔽
  401. * 1.正常域名
  402. * 2.白名单
  403. * 3.已跳转到微信110页面
  404. */
  405. if (in_array($ret['status'], [-2, -1, 2])) {
  406. $this->_sendWorkChatMessage('【Error】域名监控接口返回值:' . $json_data);
  407. exit();
  408. } elseif ($ret['status'] == 1) {
  409. Log::info('[ ChangeDomain ] [ checkurl ] [ isValidDomain ] TRUE');
  410. return true;
  411. } else {
  412. Log::info('[ ChangeDomain ] [ checkurl ] [ isValidDomain ] FALSE A');
  413. return false;
  414. }
  415. } else {
  416. Log::info('[ ChangeDomain ] [ checkurl ] [ isValidDomain ] FALSE B');
  417. return false;
  418. }
  419. }
  420. /**
  421. * 返回用户关联信息
  422. * @param $admin_id
  423. * @return string
  424. */
  425. private function _packageNotice($admin_id)
  426. {
  427. $html = '';
  428. $group_id = $this->authGroupAccess_model->getGroupId($admin_id);
  429. if($group_id == AdminConstants::ADMIN_GROUP_ID_AGENT){
  430. $sql = "SELECT admin.id,admin.username, admin.nickname, admin_extend.create_by,
  431. qd_admin.username as qd_username,qd_admin.nickname as qd_nickname
  432. ,qd_admin_extend.create_by as qd_create_by
  433. ,sw_admin.username as sw_username, sw_admin.nickname as sw_nickname
  434. FROM admin
  435. LEFT JOIN admin_extend ON admin.id= admin_extend.admin_id
  436. LEFT JOIN admin as qd_admin ON admin_extend.create_by = qd_admin.id
  437. LEFT JOIN admin_extend as qd_admin_extend ON qd_admin.id = qd_admin_extend.admin_id
  438. LEFT JOIN admin as sw_admin on qd_admin_extend.create_by = sw_admin.id
  439. WHERE admin.id ={$admin_id}
  440. LIMIT 1";
  441. $res = Db::query($sql);
  442. if($res){
  443. $data = $res[0];
  444. $html .= "\n代理商ID:{$data['id']} \n代理商账号:{$data['username']} \n代理商昵称:{$data['nickname']}";
  445. $html .= "\n渠道商ID:{$data['create_by']} \n渠道商账号:{$data['qd_username']} \n渠道商昵称:{$data['qd_nickname']}";
  446. $html .= "\n商务ID:{$data['qd_create_by']} \n商务账号:{$data['sw_username']} \n商务昵称:{$data['sw_nickname']}";
  447. }
  448. } elseif ($group_id == AdminConstants::ADMIN_GROUP_ID_CHANNEL){
  449. $sql = "SELECT admin.id,admin.username, admin.nickname, admin_extend.create_by,
  450. sw_admin.username as sw_username,sw_admin.nickname as sw_nickname
  451. FROM admin
  452. LEFT JOIN admin_extend ON admin.id= admin_extend.admin_id
  453. LEFT JOIN admin as sw_admin ON admin_extend.create_by = sw_admin.id
  454. LEFT JOIN admin_extend as sw_admin_extend ON sw_admin.id = sw_admin_extend.admin_id
  455. WHERE admin.id ={$admin_id}
  456. LIMIT 1";
  457. $res = Db::query($sql);
  458. if($res){
  459. $data = $res[0];
  460. $html .= "\n渠道商ID:{$data['id']} \n渠道商账号:{$data['username']} \n渠道商昵称:{$data['nickname']}";
  461. $html .= "\n商务ID:{$data['create_by']} \n商务账号:{$data['sw_username']} \n商务昵称:{$data['sw_nickname']}";
  462. }
  463. }elseif ($group_id == AdminConstants::ADMIN_GROUP_ID_ADMIN){
  464. $sql = "SELECT id,username, nickname FROM admin WHERE id ={$admin_id} LIMIT 1";
  465. $res = Db::query($sql);
  466. if($res){
  467. $data = $res[0];
  468. $html .= "\n商务ID:{$data['id']} \n商务账号:{$data['username']} \n商务昵称:{$data['nickname']}";
  469. }
  470. }
  471. $html .= "\n平台:{$this->pt}";
  472. return $html;
  473. }
  474. }