WeChatAuthorization.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <?php
  2. namespace app\admin\library;
  3. use app\common\library\Rabbitmq;
  4. use app\common\library\Redis;
  5. use app\common\library\WeChatObject;
  6. use app\common\utility\WeChatMenu;
  7. use app\main\service\WeChatAdService;
  8. use GuzzleHttp\Client;
  9. use Symfony\Component\Cache\Simple\RedisCache;
  10. use EasyWeChat\Factory;
  11. use think\Config;
  12. use think\Log;
  13. class WeChatAuthorization{
  14. const SYNC_MQ_QUEUE_NAME = 'Q_SYNC_USER';
  15. const SYNC_MQ_EXCHANGE_NAME = 'E_SYNC_USER';
  16. protected $platform;
  17. protected $adminConfig;
  18. protected $weChat;
  19. public function __construct($platform_id,$channel_id){
  20. if(!$platform_id || !$channel_id){
  21. throw new \Exception('Platform_id OR Channel_id 为空');
  22. }
  23. //获取平台信息
  24. if(!$this->platform = model('Platform')->where(['id'=>$platform_id])->find()){
  25. throw new \Exception('获取平台信息失败');
  26. }
  27. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 取得平台信息',$platform_id,$channel_id));
  28. //获取渠道信息
  29. if(!$this->adminConfig = model('AdminConfig')->where(['admin_id'=>$channel_id])->find()){
  30. throw new \Exception('获取用户信息失败');
  31. }
  32. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 取得渠道信息',$platform_id,$channel_id));
  33. $wechatObj = new WeChatObject($this->adminConfig);
  34. $this->weChat = $wechatObj->getPlatform($platform_id);
  35. }
  36. /**
  37. * 授权处理
  38. * @param bool $isUpdate 是否是更新权限
  39. * @throws \think\db\exception\DataNotFoundException
  40. * @throws \think\db\exception\ModelNotFoundException
  41. * @throws \think\exception\DbException
  42. */
  43. public function authorization($isUpdate = false){
  44. // 使用授权码换取接口调用凭据和授权信息
  45. $auth = $this->weChat->handleAuthorize();
  46. // 检查当前授权的公众号与历史授权公众号是否一致
  47. $existsAppID = model('AdminConfig')->where('appid',$auth['authorization_info']['authorizer_appid'])->where('admin_id','neq',$this->adminConfig->admin_id)->find();
  48. if($existsAppID){
  49. //更新平台用户refreshToken信息
  50. $this->updateUserRefrshToken($this->platform->id,$auth['authorization_info']['authorizer_refresh_token'],$existsAppID['admin_id']);
  51. throw new \Exception('一个服务号只能绑定一个账号!');
  52. }
  53. if ($this->adminConfig->appid && $this->adminConfig->appid != $auth['authorization_info']['authorizer_appid']) {
  54. throw new \Exception('当前授权的公众号与历史授权公众号不一致!');
  55. }
  56. // 获取授权方的帐号基本信息
  57. $authInfo = $this->weChat->getAuthorizer($auth['authorization_info']['authorizer_appid']);
  58. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 微信授权检测通过',$this->platform['id'],$this->adminConfig['admin_id']));
  59. //检测权限
  60. $this->checkWeChatAuth($authInfo);
  61. //更新平台用户refreshToken信息
  62. $this->updateUserRefrshToken($this->platform->id,$auth['authorization_info']['authorizer_refresh_token']);
  63. $data['appid'] = $auth['authorization_info']['authorizer_appid'];
  64. $data['json'] = $authInfo;
  65. if(!$this->adminConfig->save($data)){
  66. throw new \Exception('更新授权信息失败');
  67. }
  68. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 微信基本信息更新完成',$this->platform['id'],$this->adminConfig['admin_id']));
  69. //检查默认平台
  70. if($this->platform['id'] == $this->adminConfig['platform_id'] && !$isUpdate){
  71. try {
  72. //同步微信用户
  73. $this->syncWeChatUsers($this->adminConfig['admin_id'],$auth['authorization_info']['authorizer_appid']);
  74. $menu = [];
  75. //检测是否是单独配置菜单域名
  76. if(isset($this->adminConfig['menuophost_id']) && $this->adminConfig['menuophost_id']){
  77. if($ophost = model('Ophost')->where('id',$this->adminConfig['menuophost_id'])->find()){
  78. $menu = json_decode(str_replace('cpsurl', config('site.scheme'). '://' .$auth['authorization_info']['authorizer_appid'].'.'.$ophost['host'], config('site.wx_menu')), true);
  79. }
  80. }else{
  81. $menu = json_decode(config('site.wx_menu'), true);
  82. foreach($menu as $key => $val){
  83. if(isset($val['url'])){
  84. $menu[$key]['url'] = $this->getWeChatMenuUrl($this->platform->id,$auth['authorization_info']['authorizer_appid'],$val['url']);
  85. }
  86. if(isset($val['sub_button']) && !empty($val['sub_button'])){
  87. foreach($val['sub_button'] as $sub_k => $sub_v){
  88. if(isset($sub_v['url'])){
  89. $menu[$key]['sub_button'][$sub_k]['url'] = $this->getWeChatMenuUrl($this->platform->id,$auth['authorization_info']['authorizer_appid'],$sub_v['url']);
  90. }
  91. }
  92. }
  93. }
  94. }
  95. //默认平台时更新菜单
  96. $data['wx_menu'] = $this->updateUserWeChatMenu($auth['authorization_info']['authorizer_appid'],
  97. $auth['authorization_info']['authorizer_refresh_token'],$menu);
  98. if (!$this->adminConfig->save($data)) {
  99. Log::error('授权时,更新微信菜单失败2');
  100. }
  101. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 微信菜单数据更新完成',$this->platform['id'],$this->adminConfig['admin_id']));
  102. }catch (\Exception $exception){
  103. Log::notice($exception->getMessage());
  104. Log::notice('授权时,更新微信菜单失败1');
  105. }
  106. }
  107. model('AdminConfig')->delAdminInfoAllCache($this->adminConfig->admin_id);
  108. //添加微信AD数据源
  109. WeChatAdService::instance()->createChannelWxAdSourceId($this->adminConfig['admin_id']);
  110. //清除缓存
  111. $redis = Redis::instance();
  112. $redis->del('AA:' . $auth['authorization_info']['authorizer_appid']);
  113. }
  114. /**
  115. * 更新用户微信菜单
  116. * @param $app_id
  117. * @param $refresh_token
  118. * @param $menu
  119. * @return bool|mixed
  120. */
  121. private function updateUserWeChatMenu($app_id,$refresh_token,$menu){
  122. //获取用户公众号
  123. $officialAccount = $this->weChat->officialAccount($app_id, $refresh_token);
  124. $officialAccount->http_client = new Client(Config::get('wechat.http'));
  125. $officialAccount['cache'] = new RedisCache(Redis::instanceCache());
  126. $officialAccount->menu->delete(); //删除全部菜单 主要是掌中云个 性化菜单
  127. $ret = $officialAccount->menu->create($menu);
  128. if (!$ret || $ret['errcode'] != 0) {
  129. return null;
  130. }else{
  131. $menu = WeChatMenu::rechargeMenuFilter($menu);
  132. if(!empty($menu)){
  133. $ret = $officialAccount->menu->create($menu,['client_platform_type' => 1]);
  134. }
  135. }
  136. if (!$ret || (isset($ret['errcode']) && $ret['errcode'] != 0)) {
  137. return null;
  138. }
  139. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 微信菜单更新完成',$this->platform['id'],$this->adminConfig['admin_id']));
  140. return $menu;
  141. }
  142. /**
  143. * 获取菜单URL
  144. * @param $platform_id
  145. * @param $app_id
  146. * @param $redirect
  147. * @return string
  148. */
  149. private function getWeChatMenuUrl($platform_id,$app_id,$redirect){
  150. $redirect = str_replace('cpsurl', '', $redirect);
  151. $webParams = [];
  152. $entryHost = model('Entryhost')->getInfo();
  153. if($entryHost){
  154. $webSite = config('site.scheme'). '://' .$entryHost['host'];
  155. $webParams['channel_id'] = $this->adminConfig->admin_id;
  156. }else{
  157. if($ophost = model('Ophost')->getInfo($platform_id,$this->adminConfig->ophost_id)){
  158. $webSite = \think\Config::get('site.scheme').'://'.$app_id.'.'.$ophost['host'];
  159. }else{
  160. $webSite = '';
  161. }
  162. }
  163. if($redirect){
  164. $pathParams = parse_url($redirect);
  165. //添加路径
  166. if(isset($pathParams['path']) && $pathParams['path']){
  167. $webSite .= $pathParams['path'];
  168. }
  169. //检查路径是否有参数
  170. if(isset($pathParams['query']) && $pathParams['query']){
  171. $pathQuery = explode('&',$pathParams['query']);
  172. if($pathQuery){
  173. foreach($pathQuery as $val){
  174. $temp = explode('=',$val);
  175. $webParams[$temp[0]] = $temp[1] ?? '';
  176. }
  177. }
  178. }
  179. }
  180. if($webParams){
  181. $webSite .= '?'.http_build_query($webParams);
  182. }
  183. return $webSite;
  184. }
  185. /**
  186. * 更新用户RefrshToken
  187. * @param $platform_id
  188. * @param $refresh_token
  189. * @param $admin_id
  190. * @throws \think\db\exception\DataNotFoundException
  191. * @throws \think\db\exception\ModelNotFoundException
  192. * @throws \think\exception\DbException
  193. * @throws \Exception
  194. */
  195. private function updateUserRefrshToken($platform_id,$refresh_token,$admin_id = null){
  196. //更新ptoken信息
  197. $ptoken_map = ['admin_id'=>$this->adminConfig->admin_id,'platform_id'=>$platform_id];
  198. if($admin_id){
  199. $ptoken_map['admin_id'] = $admin_id;
  200. }
  201. if($ptoken = model('Ptoken')->where($ptoken_map)->find()){
  202. if(false === model('Ptoken')->where($ptoken_map)->update(['refresh_token'=>$refresh_token,'updatetime'=>time()])){
  203. throw new \Exception('更新RefrshToken失败');
  204. }
  205. }else{
  206. $insert_data = array_merge($ptoken_map,['refresh_token' => $refresh_token,'createtime'=>time(),'updatetime'=>time()]);
  207. if(!model('Ptoken')->insert($insert_data)){
  208. throw new \Exception('写入RefrshToken失败');
  209. }
  210. }
  211. Log::info(sprintf('微信授权: platform_id:%d channel_id:%d 微信PTOKEN更新完成',$this->platform['id'],$this->adminConfig['admin_id']));
  212. }
  213. /**
  214. * 检测权限
  215. * @param $authUser
  216. * @throws \Exception
  217. */
  218. private function checkWeChatAuth($authUser){
  219. if (!isset($authUser['authorizer_info']['service_type_info']['id'])) {
  220. throw new \Exception('获取授权信息失败,请重新扫码授权');
  221. }
  222. if ($authUser['authorizer_info']['service_type_info']['id'] != 2) {
  223. throw new \Exception('当前授权公众号类型非服务号,请使用服务号进行扫码');
  224. }
  225. if ($authUser['authorizer_info']['verify_type_info']['id'] == -1) {
  226. throw new \Exception('当前授权服务号未认证,请使用认证服务号进行扫码');
  227. }
  228. $authList = array_column(array_column($authUser['authorization_info']['func_info'], 'funcscope_category'), 'id');
  229. if(empty($authList)){
  230. throw new \Exception('获取授权信息失败');
  231. }
  232. if(!in_array(1,$authList)){
  233. throw new \Exception('必须授予-消息管理权限');
  234. }
  235. if(!in_array(15,$authList)){
  236. throw new \Exception('必须授予-自定义菜单权限');
  237. }
  238. if(!in_array(4,$authList)){
  239. throw new \Exception('必须授予-网页服务权限');
  240. }
  241. if(!in_array(2,$authList)){
  242. throw new \Exception('必须授予-用户管理权限');
  243. }
  244. if(!in_array(3,$authList)){
  245. throw new \Exception('必须授予-帐号服务权限');
  246. }
  247. if(!in_array(11,$authList)){
  248. throw new \Exception('必须授予-素材管理权限');
  249. }
  250. if(!in_array(23,$authList)){
  251. throw new \Exception('必须授予-广告管理权限');
  252. }
  253. }
  254. private function syncWeChatUsers($channel_id,$appid){
  255. if(!model('SyncUser')->where(['admin_id'=>$channel_id,'appid'=>$appid])->find()){
  256. $mq = new Rabbitmq();
  257. $mq->send(['channel_id'=>$channel_id,'appid'=>$appid],self::SYNC_MQ_QUEUE_NAME,self::SYNC_MQ_EXCHANGE_NAME);
  258. Log::info('syncUser->Queue: Channel_id:'.$channel_id.' AppID:'.$appid.' 添加微信用户同步任务');
  259. }else{
  260. Log::info('syncUser->Queue: Channel_id:'.$channel_id.' AppID:'.$appid.' 已同步过微信用户');
  261. }
  262. }
  263. }