` 采集), * 新用户直接落库、老用户填了就更新,保证后台「会员列表」能看到真实微信昵称与头像。 */ public function wxLogin() { $code = trim((string)$this->input('code', '')); if (!$code) { $this->fail('缺少微信授权 code'); } // 频率限制:同一 IP 每分钟最多 30 次授权登录 $this->rateLimit('wxlogin:' . $this->request->ip(), 30, 60); $nickname = trim((string)$this->input('nickname', '')); $avatar = trim((string)$this->input('avatar', '')); if (mb_strlen($nickname) > 32) { $nickname = mb_substr($nickname, 0, 32); } // 解析手机号(支持微信快捷获取的 phoneCode 或手动传入的 phone) $phoneCode = trim((string)$this->input('phoneCode', '')); $manualPhone = trim((string)$this->input('phone', '')); $phone = ''; if ($phoneCode !== '') { try { $phone = WxMini::phoneNumber($phoneCode); } catch (\Throwable $e) { // 手机号属于可选增强:拿不到不阻断登录,前端可回落到手动绑定(A2 bindPhone) $phone = ''; } } elseif ($manualPhone !== '' && preg_match('/^1\d{10}$/', $manualPhone)) { $phone = $manualPhone; } $openid = $this->resolveOpenid($code); $inviterId = (int)$this->input('inviterId', $this->input('inviter_id', 0)); $inviteCode = trim((string)$this->input('inviteCode', $this->input('invite_code', ''))); // 同一 openid 串行建号:避免并发首次登录建出两个账号(usage: MySQL GET_LOCK) $lockName = 'fc_wxlogin_' . md5($openid); Db::query('SELECT GET_LOCK(?, 5)', [$lockName]); try { Db::startTrans(); $ext = Db::name('fc_user_ext')->where('openid', $openid)->find(); $user = $ext ? Db::name('user')->where('id', $ext['user_id'])->find() : null; // 跨端归一:若按 openid 未查到,但带有手机号,且系统中已存在该手机号用户,则直接归一绑定微信,避免重复建号 if (!$user && $phone !== '') { $existUser = Db::name('user')->where('mobile', $phone)->find(); if ($existUser) { $user = $existUser; $existExt = Db::name('fc_user_ext')->where('user_id', $user['id'])->find(); if ($existExt) { Db::name('fc_user_ext')->where('user_id', $user['id'])->update([ 'openid' => $openid, 'updatetime' => time(), ]); } else { Db::name('fc_user_ext')->insert([ 'user_id' => $user['id'], 'openid' => $openid, 'user_type' => 1, 'realname_status' => 0, 'is_member' => 0, 'member_level' => '', 'member_expiretime' => null, 'invite_code' => strtoupper(substr(md5('fc' . $user['id']), 0, 6)), 'inviter_id' => 0, 'free_view_date' => date('Y-m-d'), 'free_view_used' => 0, 'createtime' => time(), 'updatetime' => time(), ]); } } } if (!$user) { // 首次登录:自动建号(有微信昵称/头像就落库,否则用占位昵称 + 字母头像) $uid = Db::name('user')->insertGetId([ 'group_id' => 1, 'username' => 'wx' . substr(md5($openid), 0, 10), 'nickname' => $nickname !== '' ? $nickname : '同城用户' . mt_rand(1000, 9999), 'password' => md5(mt_rand()), 'salt' => 'wx', 'email' => substr(md5($openid), 0, 12) . '@wx.local', 'mobile' => $phone, 'avatar' => $avatar, 'level' => 1, 'money' => 0, 'score' => 0, 'status' => 'normal', 'jointime' => time(), 'joinip' => $this->request->ip(), 'createtime' => time(), 'updatetime' => time(), ]); Db::name('fc_user_ext')->insert([ 'user_id' => $uid, 'openid' => $openid, 'user_type' => 1, 'realname_status' => 0, 'is_member' => 0, 'member_level' => '', 'member_expiretime' => null, 'invite_code' => strtoupper(substr(md5('fc' . $uid), 0, 6)), 'inviter_id' => 0, 'free_view_date' => date('Y-m-d'), 'free_view_used' => 0, 'createtime' => time(), 'updatetime' => time(), ]); // 首次登录:绑定分销推荐关系 DistributionService::bindRelation($uid, $inviterId, $inviteCode); $user = Db::name('user')->where('id', $uid)->find(); } else { // 老用户:不再自动补绑推荐人(老用户须在分销中心手动填写邀请码补绑) // 老用户:仅在本次带上了新值且确实不同时更新(不覆盖运营在后台改过的资料) $update = []; if ($nickname !== '' && $nickname !== (string)$user['nickname']) { $update['nickname'] = $nickname; } if ($avatar !== '' && $avatar !== (string)$user['avatar']) { $update['avatar'] = $avatar; } if ($phone !== '' && (string)$user['mobile'] === '') { $update['mobile'] = $phone; } if ($update) { $update['updatetime'] = time(); Db::name('user')->where('id', $user['id'])->update($update); $user = Db::name('user')->where('id', $user['id'])->find(); } } if ($user['status'] != 'normal') { throw new \RuntimeException('账号已被禁用,请联系客服'); } Db::commit(); } catch (\Throwable $e) { Db::rollback(); Db::query('SELECT RELEASE_LOCK(?)', [$lockName]); $this->fail($e->getMessage() ?: '微信登录失败,请重试'); } Db::query('SELECT RELEASE_LOCK(?)', [$lockName]); $token = $this->issueToken($user['id']); $this->ok([ 'token' => $token, 'refreshToken' => $this->issueRefreshToken($user['id']), 'userInfo' => $this->userInfo($user), ]); } /** * A2 手机号授权绑定 * 入参 { phone } → 出参 { phone } * 规则 BR-02:必须匹配 ^1\d{10}$ */ public function bindPhone() { $user = $this->auth(); $phone = trim((string)$this->input('phone', '')); if (!preg_match('/^1\d{10}$/', $phone)) { $this->fail('手机号格式不正确'); } $conflict = Db::name('user')->where('mobile', $phone)->where('id', '<>', $user['id'])->find(); if ($conflict) { $this->fail('该手机号已被其他账号绑定,请勿重复使用'); } Db::name('user')->where('id', $user['id'])->update([ 'mobile' => $phone, 'updatetime' => time(), ]); $this->ok(['phone' => $phone]); } /** * A5 微信手机号快捷获取 * 入参 { code }(`