From 1ceaeb921404d73f04085fb12afb4559199d39dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Tue, 19 Aug 2025 14:45:17 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=8A=9F=E8=83=BD=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/controller/AdminController.php | 106 +++++++ app/api/controller/BaseController.php | 90 ++++++ app/api/controller/UserController.php | 402 +++++++++++++++++++++++++ frontend/.env.development | 2 +- frontend/src/api/user.ts | 31 +- frontend/src/config/env.ts | 2 +- frontend/src/store/modules/user.ts | 44 ++- frontend/src/views/login/index.vue | 4 +- 8 files changed, 635 insertions(+), 46 deletions(-) create mode 100644 app/api/controller/AdminController.php create mode 100644 app/api/controller/BaseController.php create mode 100644 app/api/controller/UserController.php diff --git a/app/api/controller/AdminController.php b/app/api/controller/AdminController.php new file mode 100644 index 0000000..767da33 --- /dev/null +++ b/app/api/controller/AdminController.php @@ -0,0 +1,106 @@ + $userId, + 'timestamp' => time(), + 'random' => mt_rand(100000, 999999) + ]; + + // 使用base64编码,实际项目中建议使用JWT + return base64_encode(json_encode($data)); + } + + /** + * 用户登录接口 + * + * @return \think\Response + */ + public function login() + { + if (!$this->request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法错误']); + } + + $data = $this->request->post(); + + try { + // 验证数据 + $validate = validate([ + 'account' => 'require|email', + 'password' => 'require' + ], [ + 'account.require' => '账户不能为空', + 'account.email' => '邮箱格式不正确', + 'password.require' => '密码不能为空' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 查询用户 + $user = AdminUser::where('account', $data['account'])->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + // 验证密码 + if ($user->password !== md5($data['password'])) { + return json(['code' => 1, 'msg' => '密码错误']); + } + + // 生成JWT token(这里使用简单的token,实际项目中建议使用JWT) + $token = $this->generateToken($user->id); + + // 将token存储到缓存中,设置过期时间 + Cache::set('user_token_' . $user->id, $token, 7 * 24 * 3600); + + // 记录登录日志 + Log::record('用户登录成功:' . $user->account, 'info'); + + // 返回用户信息和token + return json([ + 'code' => 0, + 'msg' => '登录成功', + 'data' => [ + 'token' => $token, + 'user_info' => [ + 'id' => $user->id, + 'account' => $user->account, + 'name' => $user->name, + 'avatar' => $user->avatar ?? '/static/images/avatar.png', + 'phone' => $user->phone ?? '', + 'sex' => $user->sex ?? 0, + 'qq' => $user->qq ?? '', + 'wechat' => $user->wechat ?? '', + 'create_time' => $user->create_time + ] + ] + ]); + + } catch (\Exception $e) { + Log::record('登录失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '登录失败:' . $e->getMessage()]); + } + } +} diff --git a/app/api/controller/BaseController.php b/app/api/controller/BaseController.php new file mode 100644 index 0000000..236897a --- /dev/null +++ b/app/api/controller/BaseController.php @@ -0,0 +1,90 @@ +app = $app; + $this->request = $this->app->request; + } + + /** + * 返回JSON响应 + * @param mixed $data 数据 + * @param int $code 状态码 + * @param string $msg 消息 + * @return \think\Response + */ + protected function json($data = [], $code = 0, $msg = 'success') + { + return json([ + 'code' => $code, + 'msg' => $msg, + 'data' => $data + ]); + } + + /** + * 返回成功响应 + * @param mixed $data 数据 + * @param string $msg 消息 + * @return \think\Response + */ + protected function success($data = [], $msg = 'success') + { + return $this->json($data, 0, $msg); + } + + /** + * 返回错误响应 + * @param string $msg 错误消息 + * @param int $code 错误码 + * @param mixed $data 数据 + * @return \think\Response + */ + protected function error($msg = 'error', $code = 1, $data = []) + { + return $this->json($data, $code, $msg); + } +} \ No newline at end of file diff --git a/app/api/controller/UserController.php b/app/api/controller/UserController.php new file mode 100644 index 0000000..bf55f3d --- /dev/null +++ b/app/api/controller/UserController.php @@ -0,0 +1,402 @@ +request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法错误']); + } + + $data = $this->request->post(); + + try { + // 验证数据 + $validate = validate([ + 'account' => 'require|email', + 'password' => 'require' + ], [ + 'account.require' => '账户不能为空', + 'account.email' => '邮箱格式不正确', + 'password.require' => '密码不能为空' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 查询用户 + $user = Users::where('account', $data['account'])->find(); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + // 验证密码 + if ($user->password !== md5($data['password'])) { + return json(['code' => 1, 'msg' => '密码错误']); + } + + // 生成JWT token(这里使用简单的token,实际项目中建议使用JWT) + $token = $this->generateToken($user->id); + + // 将token存储到缓存中,设置过期时间 + Cache::set('user_token_' . $user->id, $token, 7 * 24 * 3600); + + // 记录登录日志 + Log::record('用户登录成功:' . $user->account, 'info'); + + // 返回用户信息和token + return json([ + 'code' => 0, + 'msg' => '登录成功', + 'data' => [ + 'token' => $token, + 'user_info' => [ + 'id' => $user->id, + 'account' => $user->account, + 'name' => $user->name, + 'avatar' => $user->avatar ?? '/static/images/avatar.png', + 'phone' => $user->phone ?? '', + 'sex' => $user->sex ?? 0, + 'qq' => $user->qq ?? '', + 'wechat' => $user->wechat ?? '', + 'create_time' => $user->create_time + ] + ] + ]); + + } catch (\Exception $e) { + Log::record('登录失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '登录失败:' . $e->getMessage()]); + } + } + + /** + * 用户注册接口 + * + * @return \think\Response + */ + public function register() + { + if (!$this->request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法错误']); + } + + $data = $this->request->post(); + + try { + // 验证数据 + $validate = validate([ + 'account' => 'require|email|unique:users', + 'code' => 'require|number|length:6', + 'password' => 'require|min:6|max:20', + 'repassword' => 'require|confirm:password' + ], [ + 'account.require' => '账户不能为空', + 'account.email' => '邮箱格式不正确', + 'account.unique' => '该邮箱已注册', + 'code.require' => '验证码不能为空', + 'code.number' => '验证码必须为数字', + 'code.length' => '验证码长度必须为6位', + 'password.require' => '密码不能为空', + 'password.min' => '密码长度不能小于6个字符', + 'password.max' => '密码长度不能超过20个字符', + 'repassword.require' => '确认密码不能为空', + 'repassword.confirm' => '两次输入的密码不一致' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 验证邮箱验证码 + $emailCode = Cache::get('email_code_' . $data['account']); + if (!$emailCode || $emailCode != $data['code']) { + return json(['code' => 1, 'msg' => '验证码错误或已过期']); + } + + // 创建用户 + $user = new Users; + $user->account = $data['account']; + $user->password = md5($data['password']); + $user->name = $this->generateRandomName(); + $user->create_time = time(); + $user->save(); + + // 清除验证码缓存 + Cache::delete('email_code_' . $data['account']); + + return json(['code' => 0, 'msg' => '注册成功']); + + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '注册失败:' . $e->getMessage()]); + } + } + + /** + * 退出登录接口 + * + * @return \think\Response + */ + public function logout() + { + try { + $token = $this->request->header('Authorization'); + if ($token) { + // 从token中获取用户ID + $userId = $this->getUserIdFromToken($token); + if ($userId) { + // 清除token缓存 + Cache::delete('user_token_' . $userId); + } + } + + Log::record('用户退出登录', 'info'); + return json(['code' => 0, 'msg' => '退出成功']); + + } catch (\Exception $e) { + Log::record('退出登录失败:' . $e->getMessage(), 'error'); + return json(['code' => 1, 'msg' => '退出失败:' . $e->getMessage()]); + } + } + + /** + * 获取用户信息接口 + * + * @return \think\Response + */ + public function getUserInfo() + { + try { + $token = $this->request->header('Authorization'); + if (!$token) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + $userId = $this->getUserIdFromToken($token); + if (!$userId) { + return json(['code' => 1, 'msg' => 'token无效']); + } + + // 验证token是否在缓存中 + $cachedToken = Cache::get('user_token_' . $userId); + if (!$cachedToken || $cachedToken !== $token) { + return json(['code' => 1, 'msg' => 'token已过期']); + } + + // 获取用户信息 + $user = Users::find($userId); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + return json([ + 'code' => 0, + 'msg' => '获取成功', + 'data' => [ + 'id' => $user->id, + 'account' => $user->account, + 'name' => $user->name, + 'avatar' => $user->avatar ?? '/static/images/avatar.png', + 'phone' => $user->phone ?? '', + 'sex' => $user->sex ?? 0, + 'qq' => $user->qq ?? '', + 'wechat' => $user->wechat ?? '', + 'create_time' => $user->create_time + ] + ]); + + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '获取用户信息失败:' . $e->getMessage()]); + } + } + + /** + * 发送邮箱验证码接口 + * + * @return \think\Response + */ + public function sendEmailCode() + { + if (!$this->request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法错误']); + } + + $email = $this->request->post('account'); + if (empty($email)) { + return json(['code' => 1, 'msg' => '邮箱不能为空']); + } + + // 验证邮箱格式 + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return json(['code' => 1, 'msg' => '邮箱格式不正确']); + } + + // 检查邮箱是否已注册 + $exists = Users::where('account', $email)->find(); + if ($exists) { + return json(['code' => 1, 'msg' => '该邮箱已注册']); + } + + // 生成6位随机验证码 + $code = mt_rand(100000, 999999); + + // 这里应该调用邮件发送服务 + // 为了演示,我们直接返回成功 + // 实际项目中需要实现邮件发送逻辑 + + // 将验证码存入缓存,有效期5分钟 + Cache::set('email_code_' . $email, $code, 300); + + return json(['code' => 0, 'msg' => '验证码已发送']); + } + + /** + * 修改密码接口 + * + * @return \think\Response + */ + public function updatePassword() + { + if (!$this->request->isPost()) { + return json(['code' => 1, 'msg' => '请求方法错误']); + } + + try { + $token = $this->request->header('Authorization'); + if (!$token) { + return json(['code' => 1, 'msg' => '请先登录']); + } + + $userId = $this->getUserIdFromToken($token); + if (!$userId) { + return json(['code' => 1, 'msg' => 'token无效']); + } + + $data = $this->request->post(); + + // 验证数据 + $validate = validate([ + 'old_password' => 'require', + 'new_password' => 'require|min:6|max:20', + 'confirm_password' => 'require|confirm:new_password' + ], [ + 'old_password.require' => '旧密码不能为空', + 'new_password.require' => '新密码不能为空', + 'new_password.min' => '新密码长度不能小于6个字符', + 'new_password.max' => '新密码长度不能超过20个字符', + 'confirm_password.require' => '确认密码不能为空', + 'confirm_password.confirm' => '两次输入的密码不一致' + ]); + + if (!$validate->check($data)) { + return json(['code' => 1, 'msg' => $validate->getError()]); + } + + // 获取用户信息 + $user = Users::find($userId); + if (!$user) { + return json(['code' => 1, 'msg' => '用户不存在']); + } + + // 验证旧密码 + if ($user->password !== md5($data['old_password'])) { + return json(['code' => 1, 'msg' => '旧密码错误']); + } + + // 更新密码 + $user->password = md5($data['new_password']); + $user->update_time = time(); + + if ($user->save()) { + // 清除token,要求重新登录 + Cache::delete('user_token_' . $userId); + return json(['code' => 0, 'msg' => '密码修改成功,请重新登录']); + } else { + return json(['code' => 1, 'msg' => '密码修改失败']); + } + + } catch (\Exception $e) { + return json(['code' => 1, 'msg' => '密码修改失败:' . $e->getMessage()]); + } + } + + /** + * 生成简单的token + * + * @param int $userId + * @return string + */ + private function generateToken($userId) + { + $data = [ + 'user_id' => $userId, + 'timestamp' => time(), + 'random' => mt_rand(100000, 999999) + ]; + + return base64_encode(json_encode($data)); + } + + /** + * 从token中获取用户ID + * + * @param string $token + * @return int|null + */ + private function getUserIdFromToken($token) + { + try { + $data = json_decode(base64_decode($token), true); + if ($data && isset($data['user_id'])) { + return $data['user_id']; + } + } catch (\Exception $e) { + return null; + } + + return null; + } + + /** + * 生成随机用户名 + * + * @return string + */ + private function generateRandomName() + { + return '云朵_' . mt_rand(100000, 999999); + } +} \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development index eff5c72..6fda80d 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,4 +1,4 @@ VITE_APP_ENV=development VITE_APP_DEBUG_MODE=true -VITE_APP_TITLE=Ŀϵͳ +VITE_APP_TITLE=项目管理系统 VITE_APP_API_BASE_URL=https://www.yunzer.cn/api diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts index e59750a..c2339c3 100644 --- a/frontend/src/api/user.ts +++ b/frontend/src/api/user.ts @@ -10,24 +10,9 @@ const api = axios.create({ } }) -// 调试信息 -console.log('API配置:', { - baseURL: ENV_CONFIG.API_BASE_URL, - timeout: ENV_CONFIG.REQUEST_TIMEOUT, - '环境变量VITE_APP_API_BASE_URL': import.meta.env.VITE_APP_API_BASE_URL -}) - // 请求拦截器 - 添加token api.interceptors.request.use( (config) => { - // 调试信息 - console.log('API请求:', { - method: config.method, - url: config.url, - fullURL: (config.baseURL || '') + (config.url || ''), - baseURL: config.baseURL - }) - const token = localStorage.getItem(ENV_CONFIG.TOKEN_KEY) if (token) { config.headers.Authorization = `Bearer ${token}` @@ -42,8 +27,16 @@ api.interceptors.request.use( // 响应拦截器 - 处理错误 api.interceptors.response.use( (response) => { - // 直接返回响应数据,而不是整个response对象 - return response.data + const data = response.data + + // 检查后端返回的状态码 + if (data.code === 0) { + // 成功,返回data字段的内容 + return data.data + } else { + // 失败,抛出错误 + return Promise.reject(new Error(data.msg || '请求失败')) + } }, (error) => { if (error.response?.status === 401) { @@ -57,8 +50,8 @@ api.interceptors.response.use( ) // 用户登录接口 -export const login = (data: { username: string; password: string }) => { - return api.post('/user/login', data) +export const login = (data: { account: string; password: string }) => { + return api.post('/admin/login', data) } // 获取用户信息接口 diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index be8f7a0..7d94785 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -2,7 +2,7 @@ const ENV_CONFIG = { // API配置 - API_BASE_URL: import.meta.env.VITE_APP_API_BASE_URL || 'https://www.yunzer.cn/api', + API_BASE_URL: import.meta.env.VITE_APP_API_BASE_URL, REQUEST_TIMEOUT: 10000, // 应用配置 diff --git a/frontend/src/store/modules/user.ts b/frontend/src/store/modules/user.ts index 018b07d..458db2a 100644 --- a/frontend/src/store/modules/user.ts +++ b/frontend/src/store/modules/user.ts @@ -3,27 +3,25 @@ import { login as loginApi, getUserInfo as getUserInfoApi, logout as logoutApi } import ENV_CONFIG from '@/config/env' interface LoginForm { - username: string + account: string password: string } interface UserInfo { id: number - username: string + account: string name: string avatar?: string - role: string -} - -interface ApiResponse { - code: number - message: string - data: T + phone?: string + sex?: number + qq?: string + wechat?: string + create_time?: number } interface LoginResponse { token: string - userInfo: UserInfo + user_info: UserInfo } const useUserStore = defineStore('user', { @@ -48,29 +46,29 @@ const useUserStore = defineStore('user', { // 用户登录 async userLogin(loginForm: LoginForm) { try { - const response = await loginApi(loginForm) as unknown as ApiResponse + const response = await loginApi(loginForm) as unknown as LoginResponse - // 假设API返回格式为 { code: 200, data: { token: string, userInfo: UserInfo } } - if (response.code === 200 && response.data) { - const { token, userInfo } = response.data + // 后端返回格式为 { token: string, user_info: UserInfo } + if (response && response.token && response.user_info) { + const { token, user_info } = response // 保存登录状态 this.token = token - this.userInfo = userInfo + this.userInfo = user_info this.isLogin = true // 保存到 localStorage localStorage.setItem(ENV_CONFIG.TOKEN_KEY, token) - localStorage.setItem(ENV_CONFIG.USER_INFO_KEY, JSON.stringify(userInfo)) + localStorage.setItem(ENV_CONFIG.USER_INFO_KEY, JSON.stringify(user_info)) - return userInfo + return user_info } else { - throw new Error(response.message || '登录失败') + throw new Error('登录响应数据格式错误') } } catch (error: any) { // 处理不同类型的错误 - if (error.response?.data?.message) { - throw new Error(error.response.data.message) + if (error.response?.data?.msg) { + throw new Error(error.response.data.msg) } else if (error.message) { throw new Error(error.message) } else { @@ -104,10 +102,10 @@ const useUserStore = defineStore('user', { if (token && userInfoStr) { try { // 验证token是否有效 - const response = await getUserInfoApi() as unknown as ApiResponse - if (response.code === 200 && response.data) { + const response = await getUserInfoApi() as unknown as UserInfo + if (response && response.id) { this.token = token - this.userInfo = response.data + this.userInfo = response this.isLogin = true } else { // token无效,清除本地存储但不调用logout API diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue index b5c465e..db16f34 100644 --- a/frontend/src/views/login/index.vue +++ b/frontend/src/views/login/index.vue @@ -9,7 +9,7 @@ @@ -45,7 +45,7 @@ import { ElNotification } from 'element-plus' import useUserStore from '@/store/modules/user' // 收集账号与密码数据 -const loginForm = reactive({ username: '', password: '' }) +const loginForm = reactive({ account: '', password: '' }) // 按钮加载状态 const loading = ref(false) // 获取路由实例