// +---------------------------------------------------------------------- declare(strict_types=1); namespace addon\haonav\controller; use ywxapp\controller\FrontendBase; use ywxapp\model\MemberUser as UserModel; /** * 前台会员登录 / 注册 / 退出 / 资料 * * 复用框架前台 Auth(Authorization 头或 access_token Cookie)。 * 登录/注册成功后 JwtService 把 token 写入 Result 单例, * 由 Result::applyTokenCookies() 随响应写回非 httpOnly Cookie(path=/), * 前端(整页/iframe)后续请求自动带登录态,无需前端额外存储。 * * 注:框架 Auth::register() 内部依赖不存在的 UserModel::checkExists(), * 故注册在此控制器用 Member 模型直接建号,再用 Auth::login() 建立登录态与 token。 * * @author ywxapp */ class Account extends FrontendBase { protected $noNeedLogin = ['login', 'register']; protected $noNeedVerify = ['*']; /** * 规整要返回给前端的用户资料 */ protected function userInfo(): array { $info = $this->auth->info ?? []; if (is_object($info)) { $info = $info->toArray(); } return [ 'uid' => (int)($info['uid'] ?? 0), 'account' => (string)($info['account'] ?? ''), 'nickname' => (string)($info['nickname'] ?? ''), 'avatar' => (string)($info['avatar'] ?? ''), ]; } /** * 登录 * POST /haonav/account/login {account, password} */ public function login() { $account = trim((string)$this->request->post('account', '')); $password = (string)$this->request->post('password', ''); if ($account === '' || $password === '') { return $this->result->error('请输入账号和密码'); } try { $this->auth->login($account, $password); } catch (\Throwable $e) { // Auth::login 内部已通过 Result 输出错误则不会到这里;此处兜底 return $this->result->error('登录失败:' . $e->getMessage()); } return $this->result->success($this->userInfo(), '登录成功'); } /** * 注册 * POST /haonav/account/register {account, password, nickname?} */ public function register() { $account = trim((string)$this->request->post('account', '')); $password = (string)$this->request->post('password', ''); $nickname = trim((string)$this->request->post('nickname', '')); if ($account === '' || $password === '') { return $this->result->error('请输入账号和密码'); } if (mb_strlen($password) < 6) { return $this->result->error('密码至少 6 位'); } if (!preg_match('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9_@.\-]+$/u', $account)) { return $this->result->error('账号仅支持中英文、数字、_@.- 等字符'); } // 账号查重(框架 UserModel::checkExists 不存在,这里直接查询) if (UserModel::where('account', $account)->findOrEmpty()->isExists()) { return $this->result->error('该账号已被注册'); } $user = new UserModel(); $user->account = $account; $user->nickname = $nickname !== '' ? mb_substr($nickname, 0, 30) : ('用户' . mb_substr($account, 0, 4)); // email 为 NOT NULL + 唯一:用本地占位邮箱保证唯一且不报错(非真实邮箱) $user->email = $account . '@haonav.local'; $user->password = $password; // 触发 Member 模型 setPasswordAttr 自动加盐加密 $user->status = 1; $user->gid = (int)config('fastadmin.user_default_group') ?: 0; $user->create_ip = $this->request->ip(); $user->update_ip = $this->request->ip(); $user->save(); // 建号后建立登录态并写回 token Cookie try { $this->auth->login($account, $password); } catch (\Throwable $e) { return $this->result->error('注册成功但自动登录失败,请手动登录'); } return $this->result->success($this->userInfo(), '注册成功'); } /** * 退出登录 * POST /haonav/account/logout */ public function logout() { $this->auth->logout(); return $this->result->success([], '已退出登录'); } /** * 当前登录资料(用于前端刷新登录态) * GET /haonav/account/profile */ public function profile() { if (! $this->auth || ! $this->auth->isLogin) { return $this->result->error('未登录', 401); } return $this->result->success($this->userInfo()); } }