find($uid); if (!$user) { return json(['code' => 500, 'msg' => '用户不存在', 'data' => []]); } if ((int) ($user['status'] ?? 1) !== 1) { return json(['code' => 403, 'msg' => '账号已被禁用', 'data' => []]); } $token = bin2hex(random_bytes(32)); // 业务上长期有效(库字段仍为 datetime,写远期时间便于排查;鉴权不再依赖该字段) $expire = '2099-12-31 23:59:59'; Db::name('mp_user')->where('id', $uid)->update([ 'api_token' => $token, 'token_expire' => $expire, 'update_time' => $now, ]); $user = Db::name('mp_user')->find($uid); return json([ 'code' => 0, 'msg' => 'ok', 'data' => [ 'token' => $token, 'client_id' => $user['client_id'], 'user' => [ 'id' => (int) $user['id'], 'nickname' => $user['nickname'], 'avatar' => $user['avatar'], 'bio' => $user['bio'], 'gender' => (int) $user['gender'], 'phone' => trim((string) ($user['phone'] ?? '')), ], ], ]); } public function bootstrap() { try { $clientId = trim((string) $this->request->post('client_id', '')); $wxCode = trim((string) $this->request->post('wx_code', '')); $wxNickname = trim((string) $this->request->post('wx_nickname', '')); $wxAvatar = trim((string) $this->request->post('wx_avatar', '')); $wxGender = (int) $this->request->post('wx_gender', 0); $now = date('Y-m-d H:i:s'); $uid = 0; if ($wxCode !== '') { if (mb_strlen($wxNickname) > 64) { $wxNickname = mb_substr($wxNickname, 0, 64); } $sess = WechatMini::code2Session($wxCode); if (!$sess['ok']) { return json(['code' => 400, 'msg' => $sess['msg'], 'data' => []]); } $openid = $sess['openid']; $unionid = (string) ($sess['unionid'] ?? ''); $sessionKey = (string) ($sess['session_key'] ?? ''); $exist = Db::name('mp_user')->where('openid', $openid)->find(); if ($exist) { $uid = (int) $exist['id']; $up = ['update_time' => $now]; if ($sessionKey !== '') { $up['session_key'] = $sessionKey; } if ($unionid !== '' && empty($exist['unionid'])) { $up['unionid'] = $unionid; } // 有真实微信资料时更新;无资料不阻断登录 if ($wxNickname !== '' && !in_array($wxNickname, ['微信用户', '游客'], true)) { $up['nickname'] = $wxNickname; } if ($wxAvatar !== '') { $up['avatar'] = $wxAvatar; } $up['gender'] = $wxGender; if (count($up) > 1) { Db::name('mp_user')->where('id', $uid)->update($up); } } else { $bound = false; if ($clientId !== '') { $guest = Db::name('mp_user')->where('client_id', $clientId)->find(); if ($guest && empty($guest['openid'])) { $uid = (int) $guest['id']; Db::name('mp_user')->where('id', $uid)->update([ 'openid' => $openid, 'unionid' => $unionid !== '' ? $unionid : (string) ($guest['unionid'] ?? ''), 'session_key' => $sessionKey, 'nickname' => ($wxNickname !== '' && !in_array($wxNickname, ['微信用户', '游客'], true)) ? $wxNickname : (string) ($guest['nickname'] ?? '微信用户'), 'avatar' => $wxAvatar !== '' ? $wxAvatar : (string) ($guest['avatar'] ?? ''), 'gender' => $wxGender, 'update_time' => $now, ]); $bound = true; } } if (!$bound) { if ($clientId === '') { $clientId = 'cid_' . bin2hex(random_bytes(10)); } elseif (Db::name('mp_user')->where('client_id', $clientId)->count() > 0) { $clientId = 'cid_' . bin2hex(random_bytes(10)); } $uid = Db::name('mp_user')->insertGetId([ 'client_id' => $clientId, 'openid' => $openid, 'unionid' => $unionid, 'session_key' => $sessionKey, 'nickname' => ($wxNickname !== '' && !in_array($wxNickname, ['微信用户', '游客'], true)) ? $wxNickname : '微信用户', 'avatar' => $wxAvatar, 'gender' => $wxGender, 'create_time' => $now, 'update_time' => $now, ]); } } } else { if ($clientId === '') { $clientId = 'cid_' . bin2hex(random_bytes(12)); $uid = Db::name('mp_user')->insertGetId([ 'client_id' => $clientId, 'nickname' => '游客', 'create_time' => $now, 'update_time' => $now, ]); } else { $row = Db::name('mp_user')->where('client_id', $clientId)->find(); if (!$row) { $uid = Db::name('mp_user')->insertGetId([ 'client_id' => $clientId, 'nickname' => '游客', 'create_time' => $now, 'update_time' => $now, ]); } else { $uid = (int) $row['id']; } } } if ($uid <= 0) { return json(['code' => 500, 'msg' => '用户创建失败', 'data' => []]); } return $this->issueTokenJson($uid); } catch (Throwable $e) { $msg = '微信登录失败'; if ((bool) env('APP_DEBUG', false)) { $msg .= ':' . $e->getMessage(); } return json(['code' => 500, 'msg' => $msg, 'data' => []]); } } /** 微信手机号授权绑定(需已登录) */ public function bindPhone(): Json { $u = AuthContext::mpUser(); if (!$u) { return json(['code' => 401, 'msg' => '未授权', 'data' => []]); } $phoneCode = trim((string) $this->request->post('phone_code', '')); if ($phoneCode === '') { return json(['code' => 400, 'msg' => '缺少手机号授权参数', 'data' => []]); } $res = WechatMini::getPhoneByCode($phoneCode); if (!$res['ok']) { return json(['code' => 400, 'msg' => $res['msg'], 'data' => []]); } $phone = trim((string) ($res['phone'] ?? '')); if ($phone === '') { return json(['code' => 400, 'msg' => '未获取到手机号', 'data' => []]); } $uid = (int) $u['id']; $exist = Db::name('mp_user')->where('phone', $phone)->where('id', '<>', $uid)->find(); if ($exist) { return json(['code' => 400, 'msg' => '该手机号已绑定其他账号', 'data' => []]); } Db::name('mp_user')->where('id', $uid)->update([ 'phone' => $phone, 'update_time' => date('Y-m-d H:i:s'), ]); return json(['code' => 0, 'msg' => 'ok', 'data' => ['phone' => $phone]]); } /** 手机号注册:需 password 与 password_confirm 一致 */ public function register(): Json { $phone = trim((string) $this->request->post('phone', '')); $password = (string) $this->request->post('password', ''); $confirm = (string) $this->request->post('password_confirm', ''); if (!preg_match('/^1\d{10}$/', $phone)) { return json(['code' => 400, 'msg' => '请输入正确的11位手机号', 'data' => []]); } if (strlen($password) < 6) { return json(['code' => 400, 'msg' => '密码至少6位', 'data' => []]); } if ($password !== $confirm) { return json(['code' => 400, 'msg' => '两次密码不一致', 'data' => []]); } if (Db::name('mp_user')->where('phone', $phone)->count() > 0) { return json(['code' => 400, 'msg' => '该手机号已注册', 'data' => []]); } $now = date('Y-m-d H:i:s'); $clientId = 'cid_' . bin2hex(random_bytes(12)); $suffix = substr($phone, -4); $uid = Db::name('mp_user')->insertGetId([ 'client_id' => $clientId, 'phone' => $phone, 'pwd_hash' => password_hash($password, PASSWORD_DEFAULT), 'nickname' => '用户' . $suffix, 'create_time' => $now, 'update_time' => $now, ]); return $this->issueTokenJson((int) $uid); } /** 手机号 + 密码登录 */ public function loginPhone(): Json { $phone = trim((string) $this->request->post('phone', '')); $password = (string) $this->request->post('password', ''); if (!preg_match('/^1\d{10}$/', $phone)) { return json(['code' => 400, 'msg' => '请输入正确的11位手机号', 'data' => []]); } if ($password === '') { return json(['code' => 400, 'msg' => '请输入密码', 'data' => []]); } $user = Db::name('mp_user')->where('phone', $phone)->find(); $hash = (string) ($user['pwd_hash'] ?? ''); if (!$user || $hash === '' || !password_verify($password, $hash)) { return json(['code' => 400, 'msg' => '账号或密码错误', 'data' => []]); } return $this->issueTokenJson((int) $user['id']); } }