From fc353899a9410f77316d5b841d56cdf9cff51a94 Mon Sep 17 00:00:00 2001 From: Karlcarlo Date: Mon, 26 Oct 2026 17:08:55 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=85=A5=E5=BA=93=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=EF=BC=882026-10-26=20=E4=B8=8E=20shop.douba.?= =?UTF-8?q?top=20=E4=B8=80=E8=87=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 11 个已跟踪文件的在途改动此前只存在于工作区,生产已在运行 - 补入两个从未入库的文件:InternalController.php(app_03/box_app 内部接口)、 config/internal.php(内部通信 key,值从 .env 读取,不含硬编码密钥) - 逐文件 md5 比对过生产,内容一致 Co-authored-by: Cursor --- app/controller/admin/v1/work/Client.php | 21 ++- app/controller/api/v1/InternalController.php | 134 +++++++++++++++++++ app/controller/api/v2/wechat/Routine.php | 8 +- app/haichao/HaichaoBridge.php | 26 +++- app/jobs/work/WorkClientJob.php | 9 ++ app/services/BaseServices.php | 6 +- app/services/user/LoginServices.php | 13 +- app/services/wechat/RoutineServices.php | 33 +++-- app/services/wechat/WechatServices.php | 20 +-- app/services/work/WorkClientServices.php | 74 +++++++++- config/internal.php | 18 +++ crmeb/utils/JwtAuth.php | 1 + route/api.php | 5 + 13 files changed, 331 insertions(+), 37 deletions(-) create mode 100644 app/controller/api/v1/InternalController.php create mode 100644 config/internal.php diff --git a/app/controller/admin/v1/work/Client.php b/app/controller/admin/v1/work/Client.php index aa2e1c1..d21e8c8 100644 --- a/app/controller/admin/v1/work/Client.php +++ b/app/controller/admin/v1/work/Client.php @@ -20,6 +20,7 @@ use crmeb\services\wechat\config\WorkConfig; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; use think\db\exception\ModelNotFoundException; +use think\facade\Log; /** * 客户管理 @@ -83,11 +84,29 @@ class Client extends AuthController */ public function synch() { - if (!sys_config('wechat_work_corpid', '')) { + $corpId = sys_config('wechat_work_corpid', ''); + if (!$corpId) { return $this->fail('请先配置企微信息'); } + try { + $beforeCount = $this->services->count([]); + Log::info('work client synch start ' . json_encode([ + 'corp_id' => $corpId, + 'before_count' => $beforeCount + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } catch (\Throwable $e) { + Log::error('work client synch precheck error ' . json_encode([ + 'corp_id' => $corpId, + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } $this->services->delete([["id", "<>", 0]]); $this->services->authGetExternalcontact(); + Log::info('work client synch dispatched ' . json_encode([ + 'corp_id' => $corpId + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return $this->success('已加入消息队列,请稍后查看'); } diff --git a/app/controller/api/v1/InternalController.php b/app/controller/api/v1/InternalController.php new file mode 100644 index 0000000..6a51b08 --- /dev/null +++ b/app/controller/api/v1/InternalController.php @@ -0,0 +1,134 @@ + +// +---------------------------------------------------------------------- + +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'); + } +} diff --git a/app/controller/api/v2/wechat/Routine.php b/app/controller/api/v2/wechat/Routine.php index a53c6a0..a46d65d 100644 --- a/app/controller/api/v2/wechat/Routine.php +++ b/app/controller/api/v2/wechat/Routine.php @@ -151,7 +151,13 @@ class Routine { $token = $this->services->silenceAuth($code, (int)$spread_code, (int)$spread_spid, true); if ($token && isset($token['auth_login'])) { - return app('json')->success('授权成功'); + // 透传 openid/unionid 给小程序端,用于向 H5 注入身份参数 + return app('json')->success('授权成功', [ + 'auth_login' => $token['auth_login'], + 'key' => $token['key'], + 'openid' => $token['openid'] ?? '', + 'unionid' => $token['unionid'] ?? '', + ]); } else if ($token) { return app('json')->success('登录成功', ['token' => $token['token'], 'userInfo' => $token['userInfo'], 'expires_time' => $token['params']['exp'], 'store_user_avatar' => $token['store_user_avatar'] ?? 0]); } else diff --git a/app/haichao/HaichaoBridge.php b/app/haichao/HaichaoBridge.php index 7d61d92..1bf3293 100644 --- a/app/haichao/HaichaoBridge.php +++ b/app/haichao/HaichaoBridge.php @@ -85,7 +85,7 @@ abstract class HaichaoBridge $options['http'] = [ 'timeout' => 60, 'method' => 'POST', - 'header' => 'Content-Type: application/json;charset=utf-8', + 'header' => 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8', 'content' => http_build_query($baseBody) ]; $raw = file_get_contents("{$this->api['api']}$api", false, stream_context_create($options)); @@ -174,12 +174,24 @@ abstract class HaichaoBridge private function sign(array $data): string { - $computerStr = ""; - foreach ($data as $k => $v) { - $computerStr .= $k . $v; - } - $computerStr = $this->api['appSecret'] . $computerStr; - return md5($computerStr); + if (!$data) { + return ''; + } + + ksort($data); + $str = ''; + foreach ($data as $k => $v) { + if ($k === null || $k === '' || $k === 'sign' || $v === '' || $v === null) { + continue; + } + if (is_array($v) || is_object($v)) { + $v = json_encode($v, JSON_UNESCAPED_UNICODE); + } + $str .= $k . $v; + } + + $str = $this->api['appSecret'] . $str; + return bin2hex(md5($str, true)); } private function isTokenError(array $rt): bool diff --git a/app/jobs/work/WorkClientJob.php b/app/jobs/work/WorkClientJob.php index b2fb776..db76118 100644 --- a/app/jobs/work/WorkClientJob.php +++ b/app/jobs/work/WorkClientJob.php @@ -18,6 +18,7 @@ use crmeb\traits\QueueTrait; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; use think\db\exception\ModelNotFoundException; +use think\facade\Log; /** * 同步客户 @@ -39,9 +40,17 @@ class WorkClientJob extends BaseJobs */ public function authClient($page, $cursor) { + Log::info('work client job authClient start ' . json_encode([ + 'page' => (int)$page, + 'cursor_len' => strlen((string)$cursor), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); /** @var WorkClientServices $make */ $make = app()->make(WorkClientServices::class); $make->authGetExternalcontact($page, $cursor); + Log::info('work client job authClient done ' . json_encode([ + 'page' => (int)$page, + 'cursor_len' => strlen((string)$cursor), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return true; } diff --git a/app/services/BaseServices.php b/app/services/BaseServices.php index 969dbdb..792e3fd 100644 --- a/app/services/BaseServices.php +++ b/app/services/BaseServices.php @@ -60,13 +60,15 @@ abstract class BaseServices * 创建token * @param int $id * @param $type + * @param string $pwd + * @param array $extras 额外字段,例如 ['phone' => '138...', 'openid' => 'o...'] * @return array */ - public function createToken(int $id, $type, string $pwd = '') + public function createToken(int $id, $type, string $pwd = '', array $extras = []) { /** @var JwtAuth $jwtAuth */ $jwtAuth = app()->make(JwtAuth::class); - return $jwtAuth->createToken($id, $type, ['auth' => md5($pwd)]); + return $jwtAuth->createToken($id, $type, array_merge(['auth' => md5($pwd)], $extras)); } /** diff --git a/app/services/user/LoginServices.php b/app/services/user/LoginServices.php index 7ff0df0..02987be 100644 --- a/app/services/user/LoginServices.php +++ b/app/services/user/LoginServices.php @@ -51,7 +51,7 @@ class LoginServices extends BaseServices */ public function login($account, $password, $spread_uid) { - $user = $this->dao->getOne(['phone' => $account], 'uid,pwd,status'); + $user = $this->dao->getOne(['phone' => $account], 'uid,pwd,status,phone'); if ($user) { if ($user->pwd !== md5((string)$password)) throw new ValidateException('账号或密码错误'); @@ -65,7 +65,7 @@ class LoginServices extends BaseServices $uid = (int)$user['uid']; //更新用户信息 - $token = $this->createToken($uid, 'api', $user->pwd); + $token = $this->createToken($uid, 'api', $user->pwd, ['phone' => $user->phone ?? '']); if ($token) { $this->updateUserInfo(['spread_uid' => $spread_uid], $user); // 用户登录成功事件 @@ -268,7 +268,7 @@ class LoginServices extends BaseServices $this->updateUserInfo(['spread_uid' => $spread_uid], $user); $uid = (int)$user['uid']; - $token = $this->createToken($uid, 'api', $user->pwd); + $token = $this->createToken($uid, 'api', $user->pwd, ['phone' => $user->phone ?? '']); if ($token) { // 用户登录成功事件 event('user.login', [$uid, app('request')->ip()]); @@ -305,7 +305,7 @@ class LoginServices extends BaseServices throw new ValidateException('修改新用户登录类型出错'); } $uid = (int)$switch_user['uid']; - $token = $this->createToken($uid, 'api', $switch_user['pwd']); + $token = $this->createToken($uid, 'api', $switch_user['pwd'], ['phone' => $switch_user->phone ?? '']); if ($token) { // 用户登录成功事件 event('user.login', [$uid, app('request')->ip()]); @@ -336,7 +336,10 @@ class LoginServices extends BaseServices $wechatUser = app()->make(WechatUserServices::class); //更新用户信息 $user = $wechatUser->wechatOauthAfter([$openid, $wechatInfo, $spread_uid, $login_type, $userType]); - $token = $this->createToken((int)$user['uid'], $userType, $user['pwd']); + $token = $this->createToken((int)$user['uid'], $userType, $user['pwd'], [ + 'phone' => $user['phone'] ?? $phone, + 'openid' => $openid, + ]); if ($token) { return [ 'token' => $token['token'], diff --git a/app/services/wechat/RoutineServices.php b/app/services/wechat/RoutineServices.php index bcdff67..ffabbef 100644 --- a/app/services/wechat/RoutineServices.php +++ b/app/services/wechat/RoutineServices.php @@ -98,7 +98,7 @@ class RoutineServices extends BaseServices $wechatUserServices = app()->make(WechatUserServices::class); //写入用户信息 $user = $wechatUserServices->wechatOauthAfter([$openid, $wechatInfo, $spread_uid, $login_type, $userType]); - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'routine', $openid); } /** @@ -133,7 +133,7 @@ class RoutineServices extends BaseServices $wechatUserServices = app()->make(WechatUserServices::class); //写入用户信息 $user = $wechatUserServices->wechatOauthAfter($createData); - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'routine', $openid); } /** @@ -289,15 +289,22 @@ class RoutineServices extends BaseServices * 获取返回信息 * @param $user * @param string $userType + * @param string $openid * @return array */ - public function getReturnInfo($user, string $userType = 'routine') + public function getReturnInfo($user, string $userType = 'routine', string $openid = '') { if (!$user || !isset($user['uid']) || !$user['uid']) { throw new ValidateException('获取用户信息失败'); } $uid = (int)$user['uid']; - $token = $this->createToken($uid, $userType, $user['pwd']); + // 查询 unionid(从 wechat_user 表) + $unionid = $this->dao->value(['uid' => $uid, 'user_type' => $userType, 'is_del' => 0], 'unionid') ?? ''; + $token = $this->createToken($uid, $userType, $user["pwd"] ?? "", [ + "phone" => $user["phone"] ?? "", + "openid" => $openid, + "unionid" => $unionid, + ]); if (!$token) { throw new ValidateException('登录失败!'); } @@ -347,7 +354,7 @@ class RoutineServices extends BaseServices //更新用户信息 $wechatUserServices->wechatUpdata([$user['uid'], $wechatInfo]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, $userType, $openid); } /** @@ -387,7 +394,7 @@ class RoutineServices extends BaseServices //更新用户信息 $wechatUserServices->wechatUpdata([$user['uid'], $wechatInfo]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, $userType, $openid); } /** @@ -416,7 +423,13 @@ class RoutineServices extends BaseServices if ($notLogin || $storeUserMobile) { $userInfoKey = md5($openid . '_' . time() . '_routine'); CacheService::setTokenBucket($userInfoKey, $createData, 7200); - return ['auth_login' => 1, 'key' => $userInfoKey]; + // notLogin=true 时也透传 openid/unionid,让前端(H5/小程序)能拿到稳定身份标识 + return [ + 'auth_login' => 1, + 'key' => $userInfoKey, + 'openid' => $openid, + 'unionid' => $routineInfo['unionid'] ?? '', + ]; } else { //写入用户信息 $user = $wechatUserServices->wechatOauthAfter($createData); @@ -425,7 +438,7 @@ class RoutineServices extends BaseServices //更新用户信息 $wechatUserServices->wechatUpdata([$user['uid'], ['spread_uid' => $spread_uid]]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'routine', $openid); } @@ -452,7 +465,7 @@ class RoutineServices extends BaseServices $wechatUserServices = app()->make(WechatUserServices::class); //写入用户信息 $user = $wechatUserServices->wechatOauthAfter($createData); - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'routine', $openid); } /** @@ -493,7 +506,7 @@ class RoutineServices extends BaseServices $wechatUserServices = app()->make(WechatUserServices::class); //写入用户信息 $user = $wechatUserServices->wechatOauthAfter([$openid, $wechatInfo, $spread_uid, $login_type, $userType]); - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'routine', $openid); } /** diff --git a/app/services/wechat/WechatServices.php b/app/services/wechat/WechatServices.php index f35f417..ea02c43 100644 --- a/app/services/wechat/WechatServices.php +++ b/app/services/wechat/WechatServices.php @@ -116,15 +116,19 @@ class WechatServices extends BaseServices * 获取返回信息 * @param $user * @param string $userType + * @param string $openid * @return array */ - public function getReturnInfo($user, string $userType = 'wechat') + public function getReturnInfo($user, string $userType = 'wechat', string $openid = '') { if (!$user || !isset($user['uid']) || !$user['uid']) { throw new ValidateException('获取用户信息失败'); } $uid = (int)$user['uid']; - $token = $this->createToken($uid, $userType, $user['pwd'] ?? ''); + $token = $this->createToken($uid, $userType, $user['pwd'] ?? '', [ + 'phone' => $user['phone'] ?? '', + 'openid' => $openid, + ]); if (!$token) { throw new ValidateException('登录失败!'); } @@ -179,7 +183,7 @@ class WechatServices extends BaseServices } else { $wechatUserServices->wechatUpdata([$user['uid'], $wechatInfo]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'wechat', $openid); } /** @@ -218,7 +222,7 @@ class WechatServices extends BaseServices //更新用户信息 $wechatUserServices->wechatUpdata([$user['uid'], $wechatInfo]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'wechat', $openid); } /** @@ -260,7 +264,7 @@ class WechatServices extends BaseServices //更新用户信息 $wechatUserServices->wechatUpdata([$user['uid'], $wechatInfo]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'wechat', $openid); } /** @@ -332,7 +336,7 @@ class WechatServices extends BaseServices //更新用户信息 $wechatUserServices->wechatUpdata([$user['uid'], ['spread_uid' => $spread_uid]]); } - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, 'wechat', $openid); } /** @@ -357,7 +361,7 @@ class WechatServices extends BaseServices $wechatUser = app()->make(WechatUserServices::class); //更新用户信息 $user = $wechatUser->wechatOauthAfter([$openid, $wechatInfo, $spread_uid, $login_type, $userType]); - return $this->getReturnInfo($user); + return $this->getReturnInfo($user, $userType, $openid); } /** @@ -410,7 +414,7 @@ class WechatServices extends BaseServices $wechatUser = app()->make(WechatUserServices::class); //更新用户信息 $user = $wechatUser->wechatOauthAfter([$openid, $userInfo, $spread_uid, $login_type, $userType]); - $token = $this->getReturnInfo($user); + $token = $this->getReturnInfo($user, $userType, $openid); $token['isbind'] = false; return $token; } diff --git a/app/services/work/WorkClientServices.php b/app/services/work/WorkClientServices.php index 75430e1..fd31da5 100644 --- a/app/services/work/WorkClientServices.php +++ b/app/services/work/WorkClientServices.php @@ -104,6 +104,10 @@ class WorkClientServices extends BaseServices $config = app()->make(WorkConfig::class); $corpId = $config->corpId; if (!$corpId) { + Log::info('work client authGetExternalcontact skip (empty corp_id) ' . json_encode([ + 'page' => $page, + 'cursor_len' => strlen($cursor), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return true; } /** @var WorkMemberServices $memberService */ @@ -111,6 +115,22 @@ class WorkClientServices extends BaseServices $menmberList = $memberService->getDataList(['corp_id' => $corpId], ['userid'], $page, 1); //没有数据就返回成功 if (!$menmberList) { + $memberCount = 0; + try { + $memberCount = $memberService->count(['corp_id' => $corpId]); + } catch (\Throwable $e) { + Log::error('work client authGetExternalcontact member count error ' . json_encode([ + 'corp_id' => $corpId, + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } + Log::info('work client authGetExternalcontact done (no member) ' . json_encode([ + 'corp_id' => $corpId, + 'page' => $page, + 'member_count' => $memberCount, + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return true; } @@ -123,18 +143,43 @@ class WorkClientServices extends BaseServices } $userids = array_column($menmberList, 'userid'); + Log::info('work client authGetExternalcontact fetch ' . json_encode([ + 'corp_id' => $corpId, + 'page' => $page, + 'cursor_len' => strlen($cursor), + 'userids_count' => count($userids), + 'tag_group_count' => count($tagRes['tag_group'] ?? []), + 'tag_map_count' => count($tagMap), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); try { $response = Work::getBatchClientList($userids, $cursor); $externalContactList = $response['external_contact_list'] ?? []; }catch (\Throwable $e){ + Log::error('work client authGetExternalcontact batchGet exception ' . json_encode([ + 'corp_id' => $corpId, + 'page' => $page, + 'cursor_len' => strlen($cursor), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); WorkClientJob::dispatchDo('authClient', [$page + 1, '']); return true; } + $stats = [ + 'external_contact_count' => count($externalContactList), + 'client_insert' => 0, + 'client_update' => 0, + 'follow_insert' => 0, + 'follow_update' => 0, + 'tag_save' => 0, + 'missing_client_map' => 0, + ]; $external = []; - $followUser = [];//内部人员跟踪 - $externalUserids = [];//客户信息 - $this->transaction(function () use ($externalContactList, $corpId, $externalUserids, $followUser, $external, $tagMap) { + $followUser = []; + $externalUserids = []; + $this->transaction(function () use ($externalContactList, $corpId, $tagMap, &$stats, &$externalUserids, &$followUser, &$external) { /** @var WechatUserServices $wechatUserService */ $wechatUserService = app()->make(WechatUserServices::class); foreach ($externalContactList as $item) { @@ -196,10 +241,12 @@ class WorkClientServices extends BaseServices if ($this->dao->count(['external_userid' => $externalUserid, 'corp_id' => $corpId])) { unset($externalContact['external_userid']); $this->dao->update(['external_userid' => $externalUserid], $externalContact); + $stats['client_update']++; } else { $externalContact['create_time'] = $item['follow_info']['createtime'] ??time(); $externalContact['update_time'] = time(); $external[] = $externalContact; + $stats['client_insert']++; } } if ($external) { @@ -214,6 +261,7 @@ class WorkClientServices extends BaseServices $tagService = app()->make(WorkClientFollowTagsServices::class); foreach ($followUser as $userid => $items) { if(!isset($clientList[$userid])){ + $stats['missing_client_map']++; continue; } $items['client_id'] = $clientList[$userid]; @@ -227,9 +275,11 @@ class WorkClientServices extends BaseServices 'add_way' => $items['add_way'], 'oper_userid' => $items['oper_userid'], ]); + $stats['follow_update']++; } else { $res = $followService->save($items); $id = $res->id; + $stats['follow_insert']++; } if (!empty($items['tags'])) { $tagService->delete(['follow_id' => $id]); @@ -237,14 +287,32 @@ class WorkClientServices extends BaseServices $tag['follow_id'] = $id; } $tagService->saveAll($items['tags']); + $stats['tag_save'] += count($items['tags']); } } } }); + Log::info('work client authGetExternalcontact processed ' . json_encode([ + 'corp_id' => $corpId, + 'page' => $page, + 'cursor_len' => strlen($cursor), + 'next_cursor_len' => strlen((string)($response['next_cursor'] ?? '')), + 'stats' => $stats, + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + if (isset($response['next_cursor']) && $response['next_cursor']) { + Log::info('work client authGetExternalcontact dispatch next_cursor ' . json_encode([ + 'corp_id' => $corpId, + 'page' => $page, + 'next_cursor_len' => strlen((string)$response['next_cursor']), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); WorkClientJob::dispatchDo('authClient', [$page, $response['next_cursor']]); } else if (empty($response['next_cursor'])) { + Log::info('work client authGetExternalcontact dispatch next_page ' . json_encode([ + 'corp_id' => $corpId, + 'page' => $page + 1, + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); WorkClientJob::dispatchDo('authClient', [$page + 1, '']); } diff --git a/config/internal.php b/config/internal.php new file mode 100644 index 0000000..8364338 --- /dev/null +++ b/config/internal.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- + +// 内部服务通信密钥(app_03 等调用 /api/internal/* 时使用) +// 优先读 .env 的 INTERNAL_API_KEY,没有则用占位符(生产必须改) +// 注意:.env 用 CRLF 换行时,env() 读到的值末尾会带 \r +// 用 trim() 避免请求头和 config 值的字节级比较失败 +return [ + 'api_key' => trim((string)env('INTERNAL_API_KEY', '')), +]; diff --git a/crmeb/utils/JwtAuth.php b/crmeb/utils/JwtAuth.php index 9b4a870..785063a 100644 --- a/crmeb/utils/JwtAuth.php +++ b/crmeb/utils/JwtAuth.php @@ -61,6 +61,7 @@ class JwtAuth 'iat' => $time, 'nbf' => $time, 'exp' => $exp_time, + 'uid' => $id, ]; $params['jti'] = compact('id', 'type'); $token = JWT::encode($params, Env::get('app.app_key', $this->app_key) ?: $this->app_key, 'HS256'); diff --git a/route/api.php b/route/api.php index 2d9681d..8144f58 100644 --- a/route/api.php +++ b/route/api.php @@ -17,6 +17,11 @@ use think\Response; */ Route::group('api', function () { + // 内部接口(无需用户认证) + Route::get('internal/user-info', 'v1.InternalController/userInfo')->name('internalUserInfo'); + // 通过微信 code 换 openid(绕过 store_user_mobile 限制) + Route::any('internal/openid-by-code', 'v1.InternalController/getOpenidByCode')->name('internalOpenidByCode'); + Route::any('order_call_back', 'v1.order.StoreOrder/callBack');//商家寄件回调 Route::any('jst/callback', 'v1.PublicController/jstCallback')->name('jstCallback'); Route::any('wechat/serve', 'v1.wechat.Wechat/serve');//公众号服务