// +---------------------------------------------------------------------- namespace app\controller\api\v1; use app\Request; use app\services\user\UserServices; use app\services\wechat\RoutineServices; use app\services\wechat\WechatUserServices; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; use think\db\exception\ModelNotFoundException; use think\exception\ValidateException; use think\Response; /** * 内部服务接口(不经过用户认证) * 用于 H5 / app_03 后端通过 openid 自动补齐用户信息 */ class InternalController { /** * 验证内部 API Key * @param Request $request * @return bool true=通过,false=拒绝 */ private function checkInternalKey(Request $request): bool { $apiKey = $request->header('X-Internal-Key'); // 注意:在 Swoole 沙箱里 env()/getenv() 经常读不到 .env // 用 config() 兜底,确保两种部署(PHP-FPM / Swoole)都生效 $configKey = (string)(config('internal.api_key') ?: env('INTERNAL_API_KEY', '')); return $apiKey && $configKey && $apiKey === $configKey; } /** * 根据 openid 获取用户信息 * @param Request $request * @param WechatUserServices $wechatUserServices * @return Response */ public function userInfo(Request $request, WechatUserServices $wechatUserServices): Response { if (!$this->checkInternalKey($request)) { return Response::create(['code' => 403, 'msg' => 'Forbidden'], 'json'); } $openid = $request->param('openid', ''); if (!$openid) { return Response::create(['code' => 400, 'msg' => 'openid is required'], 'json'); } try { $wechatUser = $wechatUserServices->getWechatUserInfo(['openid' => $openid]); } catch (DataNotFoundException | ModelNotFoundException | DbException $e) { return Response::create(['code' => 500, 'msg' => '查询失败'], 'json'); } if (!$wechatUser) { return Response::create(['code' => 404, 'msg' => 'user not found'], 'json'); } $uid = (int)$wechatUser['uid']; $userServices = app()->make(UserServices::class); try { $user = $userServices->getUserInfo($uid); } catch (DataNotFoundException | ModelNotFoundException | DbException $e) { $user = null; } return Response::create([ 'code' => 200, 'data' => [ 'uid' => $uid, 'nickname' => $user['nickname'] ?? $wechatUser['nickname'] ?? '', 'phone' => $user['phone'] ?? $wechatUser['phone'] ?? '', 'avatar' => $user['avatar'] ?? $wechatUser['headimgurl'] ?? '', ], ], 'json'); } /** * 通过微信 code 换 openid(绕过 store_user_mobile 限制) * * 设计目的:H5/app_03 在新用户场景下(CRMEB 强制绑手机号=true), * 原 silenceAuth 返回 {auth_login:1, key:...},前端拿不到 openid 会被 * 引导到授权页。本端点只调微信 code2session 拿 openid/unionid,不查 * store_user_mobile、不建用户、不发 token,前端拿到 openid 后走自己的 * openid-login 即可自助登录。 * * @param Request $request * @param RoutineServices $routineServices * @return Response */ public function getOpenidByCode(Request $request, RoutineServices $routineServices): Response { if (!$this->checkInternalKey($request)) { return Response::create(['code' => 403, 'msg' => 'Forbidden'], 'json'); } $code = $request->param('code', ''); if (!$code) { return Response::create(['code' => 400, 'msg' => 'code is required'], 'json'); } try { $wechatInfo = $routineServices->getUserInfoByCode((string)$code); } catch (ValidateException $e) { return Response::create(['code' => 400, 'msg' => $e->getMessage()], 'json'); } catch (\Throwable $e) { return Response::create(['code' => 500, 'msg' => 'code2session 失败: ' . $e->getMessage()], 'json'); } $openid = $wechatInfo['openid'] ?? ''; if (!$openid) { return Response::create(['code' => 500, 'msg' => 'openid 缺失'], 'json'); } return Response::create([ 'code' => 200, 'data' => [ 'openid' => $openid, 'unionid' => $wechatInfo['unionid'] ?? '', ], ], 'json'); } }