diff --git a/app/admin/library/Auth.php b/app/admin/library/Auth.php index 31e562aa..a7b08950 100644 --- a/app/admin/library/Auth.php +++ b/app/admin/library/Auth.php @@ -111,21 +111,21 @@ class Auth */ public function login($username, $password, $keeptime = 0) { - $this->admin = Admin::where('username', $username)->find(); - if (!$this->admin) { + $this->model = Admin::where('username', $username)->find(); + if (!$this->model) { $this->setError('Username is incorrect'); return false; } - if ($this->admin['status'] == '0') { + if ($this->model['status'] == '0') { $this->setError('Administrator disabled'); return false; } $adminLoginRetry = Config::get('buildadmin.admin_login_retry'); - if ($adminLoginRetry && $this->admin->loginfailure >= $adminLoginRetry && time() - $this->admin->lastlogintime < 86400) { + if ($adminLoginRetry && $this->model->loginfailure >= $adminLoginRetry && time() - $this->model->lastlogintime < 86400) { $this->setError('Please try again after 1 day'); return false; } - if ($this->admin->password != $this->admin->encryptPassword($password, $this->admin->salt)) { + if ($this->model->password != Admin::encryptPassword($password, $this->model->salt)) { $this->loginFailed(); $this->setError('Password is incorrect'); return false; @@ -146,15 +146,15 @@ class Auth } Db::startTrans(); try { - $this->admin->loginfailure = 0; - $this->admin->lastlogintime = time(); - $this->admin->lastloginip = request()->ip(); - $this->admin->save(); + $this->model->loginfailure = 0; + $this->model->lastlogintime = time(); + $this->model->lastloginip = request()->ip(); + $this->model->save(); $this->logined = true; if (!$this->token) { $this->token = Random::uuid(); - Token::set($this->_token, 'admin', $this->admin->id, $this->keeptime); + Token::set($this->token, 'admin', $this->model->id, $this->keeptime); } Db::commit(); } catch (Exception $e) { @@ -176,13 +176,13 @@ class Auth } Db::startTrans(); try { - $this->admin->loginfailure++; - $this->admin->lastlogintime = time(); - $this->admin->lastloginip = request()->ip(); - $this->admin->save(); + $this->model->loginfailure++; + $this->model->lastlogintime = time(); + $this->model->lastloginip = request()->ip(); + $this->model->save(); $this->token = ''; - $this->admin = null; + $this->model = null; $this->logined = false; Db::commit(); } catch (Exception $e) { @@ -242,6 +242,9 @@ class Auth */ public function getInfo() { + if (!$this->model) { + return []; + } $info = $this->model->toArray(); $info = array_intersect_key($info, array_flip($this->getAllowFields())); $info['token'] = $this->getToken(); diff --git a/app/admin/model/Admin.php b/app/admin/model/Admin.php index 306e6824..95bbb698 100644 --- a/app/admin/model/Admin.php +++ b/app/admin/model/Admin.php @@ -17,13 +17,13 @@ class Admin extends Model */ public function resetPassword($uid, $NewPassword) { - $passwd = $this->encryptPassword($NewPassword); + $passwd = self::encryptPassword($NewPassword); $ret = $this->where(['id' => $uid])->update(['password' => $passwd]); return $ret; } // 密码加密 - protected function encryptPassword($password, $salt = '', $encrypt = 'md5') + public static function encryptPassword($password, $salt = '', $encrypt = 'md5') { return $encrypt($encrypt($password) . $salt); } diff --git a/public/static/images/avatar.png b/public/static/images/avatar.png new file mode 100644 index 00000000..e32edd3c Binary files /dev/null and b/public/static/images/avatar.png differ diff --git a/web/src/api/backend/index.ts b/web/src/api/backend/index.ts index 7078d33f..f6c4de37 100644 --- a/web/src/api/backend/index.ts +++ b/web/src/api/backend/index.ts @@ -1,9 +1,14 @@ -import Axios from '/@/utils/axios' +import createAxios from '/@/utils/axios' -export function login(params: object, method: 'get' | 'post') { - return Axios.request({ - url: '/index.php/admin/index/login', - data: params, - method: method, - }) +export function login(method: 'get' | 'post', params: object = {}) { + return createAxios( + { + url: '/index.php/admin/index/login', + data: params, + method: method, + }, + { + showCodeMessage: true, + } + ) } diff --git a/web/src/utils/axios.ts b/web/src/utils/axios.ts index 256d737d..75d0f6ec 100644 --- a/web/src/utils/axios.ts +++ b/web/src/utils/axios.ts @@ -1,24 +1,220 @@ import axios from 'axios' +import type { AxiosRequestConfig } from 'axios' import { computed } from 'vue' -import { ElMessage, ElMessageBox } from 'element-plus' -import { Local } from '/@/utils/storage' +import { ElMessage, ElLoading, LoadingOptions } from 'element-plus' import { store } from '/@/store/index' -import { isProd } from '/@/utils/vite' + +const lang = computed(() => store.state.config.defaultLang) +const pendingMap = new Map() +const loadingInstance: LoadingInstance = { + target: null, + count: 0, +} export const getUrl = (): string => { const value: string = import.meta.env.VITE_AXIOS_BASE_URL as string return value == 'getCurrentDomain' ? window.location.protocol + '//' + window.location.host : value } -const lang = computed(() => store.state.config.defaultLang) +function createAxios(config: AxiosRequestConfig, options: Options = {}, loading: LoadingOptions = {}) { + const Axios = axios.create({ + baseURL: getUrl(), + timeout: 1000 * 10, + headers: { + 'Content-Type': 'application/json', + 'think-lang': lang.value, + }, + }) -const Axios = axios.create({ - baseURL: getUrl(), - timeout: 50000, - headers: { - 'Content-Type': 'application/json', - 'think-lang': lang.value, - }, -}) + options = Object.assign( + { + CancelDuplicateRequest: true, // 是否开启取消重复请求, 默认为 true + loading: false, // 是否开启loading层效果, 默认为false + reductDataFormat: true, // 是否开启简洁的数据结构响应, 默认为true + showErrorMessage: true, // 是否开启接口错误信息展示,默认为true + showCodeMessage: true, // 是否开启code不为1时的信息提示, 默认为false + }, + options + ) -export default Axios + // 请求拦截 + Axios.interceptors.request.use( + (config) => { + removePending(config) + options.CancelDuplicateRequest && addPending(config) + // 创建loading实例 + if (options.loading) { + loadingInstance.count++ + if (loadingInstance.count === 1) { + loadingInstance.target = ElLoading.service(loading) + } + } + + // 自动携带token - 待完善 + + return config + }, + (error) => { + return Promise.reject(error) + } + ) + + // 响应拦截 + Axios.interceptors.response.use( + (response) => { + removePending(response.config) + options.loading && closeLoading(options) // 关闭loading + + if (options.showCodeMessage && response.data && response.data.code !== 1) { + ElMessage({ + type: 'error', + message: response.data.msg, + }) + // code不等于1, 页面then内的具体逻辑就不执行了 + return Promise.reject(response.data) + } + return options.reductDataFormat ? response.data : response + }, + (error) => { + error.config && removePending(error.config) + options.loading && closeLoading(options) // 关闭loading + options.showErrorMessage && httpErrorStatusHandle(error) // 处理错误状态码 + return Promise.reject(error) // 错误继续返回给到具体页面 + } + ) + + return Axios(config) +} + +export default createAxios + +/** + * 处理异常 + * @param {*} error + */ +function httpErrorStatusHandle(error: any) { + // 处理被取消的请求 + if (axios.isCancel(error)) return console.error('因为请求重复被自动取消:' + error.message) + let message = '' + if (error && error.response) { + switch (error.response.status) { + case 302: + message = '接口重定向了!' + break + case 400: + message = '参数不正确!' + break + case 401: + message = '您未登录,或者登录已经超时,请先登录!' + break + case 403: + message = '您没有权限操作!' + break + case 404: + message = `请求地址出错: ${error.response.config.url}` + break + case 408: + message = '请求超时!' + break + case 409: + message = '系统已存在相同数据!' + break + case 500: + message = '服务器内部错误!' + break + case 501: + message = '服务未实现!' + break + case 502: + message = '网关错误!' + break + case 503: + message = '服务不可用!' + break + case 504: + message = '服务暂时无法访问,请稍后再试!' + break + case 505: + message = 'HTTP版本不受支持!' + break + default: + message = '异常问题,请联系网站管理员!' + break + } + } + if (error.message.includes('timeout')) message = '网络请求超时!' + if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!' + + ElMessage({ + type: 'error', + message, + }) +} + +/** + * 关闭Loading层实例 + */ +function closeLoading(options: Options) { + if (options.loading && loadingInstance.count > 0) loadingInstance.count-- + if (loadingInstance.count === 0) { + loadingInstance.target.close() + loadingInstance.target = null + } +} + +/** + * 储存每个请求的唯一cancel回调, 以此为标识 + */ +function addPending(config: AxiosRequestConfig) { + const pendingKey = getPendingKey(config) + config.cancelToken = + config.cancelToken || + new axios.CancelToken((cancel) => { + if (!pendingMap.has(pendingKey)) { + pendingMap.set(pendingKey, cancel) + } + }) +} + +/** + * 删除重复的请求 + */ +function removePending(config: AxiosRequestConfig) { + const pendingKey = getPendingKey(config) + if (pendingMap.has(pendingKey)) { + const cancelToken = pendingMap.get(pendingKey) + cancelToken(pendingKey) + pendingMap.delete(pendingKey) + } +} + +/** + * 生成每个请求的唯一key + */ +function getPendingKey(config: AxiosRequestConfig) { + let { url, method, params, data } = config + if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象 + return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&') +} + +interface LoadingInstance { + target: any + count: number +} +interface Options { + // 是否开启取消重复请求, 默认为 true + CancelDuplicateRequest?: boolean + // 是否开启loading层效果, 默认为false + loading?: boolean + // 是否开启简洁的数据结构响应, 默认为true + reductDataFormat?: boolean + // 是否开启接口错误信息展示,默认为true + showErrorMessage?: boolean + // 是否开启code不为0时的信息提示, 默认为true + showCodeMessage?: boolean +} + +/* + * 感谢掘金@橙某人提供的思路和分享 + * 本axios封装详细解释请参考:https://juejin.cn/post/6968630178163458084?share_token=7831c9e0-bea0-469e-8028-b587e13681a8#heading-27 + */ diff --git a/web/src/views/backend/login.vue b/web/src/views/backend/login.vue index 8596fff4..32e91b78 100644 --- a/web/src/views/backend/login.vue +++ b/web/src/views/backend/login.vue @@ -50,7 +50,7 @@ - + { vm.ctx.$refs.captcha.focus() } - login({}, 'get').then((res) => { - if (res.data.code == 302) { - console.log('登录过了~') - } + login('get').then((res) => { + state.showCaptcha = res.data.captcha }) }) @@ -194,7 +193,7 @@ const onSubmit = (formEl: InstanceType | undefined) => { if (valid) { form.loading = true form.captcha_id = state.captchaId - login(form, 'post') + login('post', form) .then((res) => { form.loading = false console.log(res) @@ -203,7 +202,6 @@ const onSubmit = (formEl: InstanceType | undefined) => { form.loading = false console.log(err) }) - console.log(form) } else { console.log('error submit!') return false