From eb9e9db8b1344c246502e78c8bc02d9270004373 Mon Sep 17 00:00:00 2001 From: jiang Date: Sun, 8 Sep 2024 20:13:32 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/login.js | 12 ++ src/api/system/user.js | 9 + src/layout/index.vue | 159 ++++++++++++----- src/store/modules/user.js | 188 +++++++++------------ src/utils/passwordConfig.js | 14 ++ src/utils/validate.js | 62 ++++++- src/views/index.vue | 6 +- src/views/index_v1.vue | 2 +- src/views/login1.vue | 72 +++++++- src/views/register1.vue | 7 +- src/views/system/user/index.vue | 73 +++++++- src/views/system/user/profile/resetPwd.vue | 4 +- vue.config.js | 10 +- 13 files changed, 452 insertions(+), 166 deletions(-) create mode 100644 src/utils/passwordConfig.js diff --git a/src/api/login.js b/src/api/login.js index 899b1311..bf8772db 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -25,6 +25,18 @@ export function isLogin(data) { }) } +export function isAdmin(data) { + return request({ + url: '/auth/isAdmin', + headers: { + isToken: false, + repeatSubmit: false + }, + method: 'post', + data: data + }) +} + export function getPhoneCode(payload) { return request({ url: '/auth/getPhoneCode', diff --git a/src/api/system/user.js b/src/api/system/user.js index 450f9dd1..cd5af3d9 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -145,3 +145,12 @@ export function approvalStatus(data) { data: data }) } + +//用户注册审批 +export function checkPasswordStatus(data) { + return request({ + url: '/system/user/checkPasswordStatus', + method: 'get' + }) +} + diff --git a/src/layout/index.vue b/src/layout/index.vue index dba4393d..26f9ebf7 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -12,6 +12,29 @@ + + + + + + + + + + + + + + + @@ -21,9 +44,43 @@ import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components' import ResizeMixin from './mixin/ResizeHandler' import { mapState } from 'vuex' import variables from '@/assets/styles/variables.scss' +import { validateNewPassword } from '@/utils/validate' +import { updateUserPwd, checkPasswordStatus } from '@/api/system/user' export default { name: 'Layout', + data() { + const equalToPassword = (rule, value, callback) => { + if (this.user.newPassword !== value) { + callback(new Error('两次输入的密码不一致')) + } else { + callback() + } + } + return { + showChangePasswordDialog: false, // 控制弹窗显示 + title: '', + user: { + oldPassword: undefined, + newPassword: undefined, + confirmPassword: undefined + }, + // 表单校验规则 + rules: { + oldPassword: [ + { required: true, message: '旧密码不能为空', trigger: 'blur' } + ], + newPassword: [ + { required: true, message: '新密码不能为空', trigger: 'blur' }, + { validator: validateNewPassword, trigger: 'blur' } + ], + confirmPassword: [ + { required: true, message: '确认密码不能为空', trigger: 'blur' }, + { required: true, validator: equalToPassword, trigger: 'blur' } + ] + } + } + }, components: { AppMain, Navbar, @@ -51,61 +108,87 @@ export default { } }, variables() { - return variables; + return variables } }, + created() { + this.checkPasswordStatus() + }, methods: { + checkPasswordStatus() { + checkPasswordStatus().then(response => { + if (response.code === 200) { + this.showChangePasswordDialog = response.data + this.title = response.msg + } + }) + }, handleClickOutside() { this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) + }, + submit() { + this.$refs['form'].validate(valid => { + if (valid) { + updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => { + this.showChangePasswordDialog = false + this.$modal.msgSuccess('修改成功') + }) + } + }) + }, + close() { + this.$store.dispatch('LogOut').then(() => { + location.href = '/index'; + }) } } } diff --git a/src/store/modules/user.js b/src/store/modules/user.js index fdede50d..6520776b 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -1,6 +1,35 @@ -import { login, logout, getInfo, refreshToken, getPhoneCode, isLogin } from '@/api/login' +import { login, logout, getInfo, refreshToken, getPhoneCode, isLogin,isAdmin } from '@/api/login' import { getToken, setToken, setExpiresIn, removeToken } from '@/utils/auth' +// 更严格的手机号和邮箱正则表达式 +const phonePattern = /^(\+86)?1[3-9]\d{9}$/ // 支持前缀 +86 +const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + +// 构建 payload 函数 +const buildPayload = ({ loginMethod, username, password, uuid, code, mobile, verificationCode }) => { + let loginType = '' + if (loginMethod === 'mobile') { + loginType = phonePattern.test(mobile.trim()) ? 'PHONE_OTP' : emailPattern.test(mobile.trim()) ? 'EMAIL_OTP' : 'PHONE_OTP' + return { + username: mobile.trim(), + verificationCode, + uuid, + code, + loginType + } + } else { + loginType = phonePattern.test(username.trim()) ? 'PHONE_PASSWORD' : emailPattern.test(username.trim()) ? 'EMAIL_PASSWORD' : 'USERNAME_PASSWORD' + return { + username: username.trim(), + password, + verificationCode, + uuid, + code, + loginType + } + } +} + const user = { state: { token: getToken(), @@ -12,159 +41,110 @@ const user = { }, mutations: { - SET_TOKEN: (state, token) => { + SET_TOKEN(state, token) { state.token = token }, - SET_EXPIRES_IN: (state, time) => { + SET_EXPIRES_IN(state, time) { state.expires_in = time }, - SET_ID: (state, id) => { + SET_ID(state, id) { state.id = id }, - SET_NAME: (state, name) => { + SET_NAME(state, name) { state.name = name }, - SET_AVATAR: (state, avatar) => { + SET_AVATAR(state, avatar) { state.avatar = avatar }, - SET_ROLES: (state, roles) => { + SET_ROLES(state, roles) { state.roles = roles }, - SET_PERMISSIONS: (state, permissions) => { + SET_PERMISSIONS(state, permissions) { state.permissions = permissions } }, actions: { IsLogin({ commit }, userInfo) { - let payload = {} - const loginType = userInfo.loginType - if (loginType === 'password') { - payload = { - username: userInfo.username.trim(), - password: userInfo.password, - uuid: userInfo.uuid, - code: userInfo.code, - loginType: loginType - } - } else if (loginType === 'mobile') { - payload = { - mobile: userInfo.mobile.trim(), - verificationCode: userInfo.verificationCode, - uuid: userInfo.uuid, - code: userInfo.code, - loginType: loginType - } - } - return new Promise((resolve, reject) => { - isLogin(payload).then(res => { - resolve(res); - }).catch(error => { - reject(error) - }) - }) + const payload = buildPayload(userInfo) + return isLogin(payload) + .then(res => res) + .catch(error => Promise.reject(error)) }, + IsAdmin({ commit }, userInfo) { + const payload = buildPayload(userInfo) + return isAdmin(payload) + .then(res => res) + .catch(error => Promise.reject(error)) + }, + // 登录 Login({ commit }, userInfo) { - let payload = {} - const loginType = userInfo.loginType - if (loginType === 'password') { - payload = { - username: userInfo.username.trim(), - password: userInfo.password, - uuid: userInfo.uuid, - code: userInfo.code, - loginType: loginType - } - } else if (loginType === 'mobile') { - payload = { - mobile: userInfo.mobile.trim(), - verificationCode: userInfo.verificationCode, - uuid: userInfo.uuid, - code: userInfo.code, - loginType: loginType - } - } - return new Promise((resolve, reject) => { - login(payload).then(res => { - let data = res.data - setToken(data.access_token) - commit('SET_TOKEN', data.access_token) - setExpiresIn(data.expires_in) - commit('SET_EXPIRES_IN', data.expires_in) - resolve() - }).catch(error => { - reject(error) + const payload = buildPayload(userInfo) + return login(payload) + .then(res => { + const { access_token, expires_in } = res.data + setToken(access_token) + commit('SET_TOKEN', access_token) + setExpiresIn(expires_in) + commit('SET_EXPIRES_IN', expires_in) }) - }) + .catch(error => Promise.reject(error)) }, + + // 获取手机验证码 GetPhoneCode({ commit }, userInfo) { - let payload = {} - payload = { + const payload = { mobile: userInfo.mobile.trim(), uuid: userInfo.uuid, code: userInfo.code, mobileCodeType: userInfo.mobileCodeType } - return new Promise((resolve, reject) => { - getPhoneCode(payload).then(res => { - resolve() - }).catch(error => { - reject(error) - }) - }) + return getPhoneCode(payload) + .then(res => res) + .catch(error => Promise.reject(error)) }, + // 获取用户信息 - GetInfo({ commit, state }) { - return new Promise((resolve, reject) => { - getInfo().then(res => { + GetInfo({ commit }) { + return getInfo() + .then(res => { const user = res.user - const avatar = (user.avatar == '' || user.avatar == null) ? require('@/assets/images/profile.jpg') : user.avatar - if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 - commit('SET_ROLES', res.roles) - commit('SET_PERMISSIONS', res.permissions) - } else { - commit('SET_ROLES', ['ROLE_DEFAULT']) - } + const avatar = user.avatar ? user.avatar : require('@/assets/images/profile.jpg') + commit('SET_ROLES', res.roles && res.roles.length > 0 ? res.roles : ['ROLE_DEFAULT']) + commit('SET_PERMISSIONS', res.permissions) commit('SET_ID', user.userId) commit('SET_NAME', user.userName) commit('SET_AVATAR', avatar) - resolve(res) - }).catch(error => { - reject(error) + return res }) - }) + .catch(error => Promise.reject(error)) }, - // 刷新token + // 刷新 token RefreshToken({ commit, state }) { - return new Promise((resolve, reject) => { - refreshToken(state.token).then(res => { - setExpiresIn(res.data) - commit('SET_EXPIRES_IN', res.data) - resolve() - }).catch(error => { - reject(error) + return refreshToken(state.token) + .then(res => { + const expiresIn = res.data + setExpiresIn(expiresIn) + commit('SET_EXPIRES_IN', expiresIn) }) - }) + .catch(error => Promise.reject(error)) }, - // 退出系统 + // 退出登录 LogOut({ commit, state }) { - return new Promise((resolve, reject) => { - logout(state.token).then(() => { + return logout(state.token) + .then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() - resolve() - }).catch(error => { - reject(error) }) - }) + .catch(error => Promise.reject(error)) }, - // 前端 登出 + // 前端退出 FedLogOut({ commit }) { return new Promise(resolve => { commit('SET_TOKEN', '') diff --git a/src/utils/passwordConfig.js b/src/utils/passwordConfig.js new file mode 100644 index 00000000..f2a8f9be --- /dev/null +++ b/src/utils/passwordConfig.js @@ -0,0 +1,14 @@ +// src/config/passwordConfig.js +export default { + minLength: 8, // 密码最小长度 + maxLength: 16, // 密码最大长度 + requireUpperCase: true, // 是否需要大写字母 + requireLowerCase: true, // 是否需要小写字母 + requireDigit: true, // 是否需要数字 + requireSpecialChar: true, // 是否需要特殊字符 + weakPasswords: ['123456', 'password', 'qwerty'], // 弱密码列表 + restrictConsecutiveChars: true, // 是否限制连续字符 + maxConsecutiveChars: 3, // 最大连续字符数 + excludeUsernameInPassword: true, // 是否不允许密码包含用户名 + passwordHistoryLimit: 5 // 历史密码限制条数 +} diff --git a/src/utils/validate.js b/src/utils/validate.js index c83e72d0..e5a1abcf 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -1,4 +1,5 @@ -import {CONFIG} from '@/utils/configure' +import { CONFIG } from '@/utils/configure' +import passwordConfig from '@/utils/passwordConfig' /** * @param {string} path @@ -149,3 +150,62 @@ export function validatePassword(rule, value, callback) { } callback() } + +export function validateNewPassword(rule, value, callback) { + // 使用配置文件中的策略进行验证 + + // 1. 检查密码长度 + if (value.length < passwordConfig.minLength || value.length > passwordConfig.maxLength) { + callback(new Error('密码长度应为' + passwordConfig.minLength + '至' + passwordConfig.maxLength + '位!')) + return + } + + // 2. 检查密码复杂度 + const hasUpperCase = /[A-Z]/.test(value) + const hasLowerCase = /[a-z]/.test(value) + const hasDigit = /\d/.test(value) + const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value) + + if (passwordConfig.requireUpperCase && !hasUpperCase) { + callback(new Error('密码必须包含大写字母!')) + return + } + if (passwordConfig.requireLowerCase && !hasLowerCase) { + callback(new Error('密码必须包含小写字母!')) + return + } + if (passwordConfig.requireDigit && !hasDigit) { + callback(new Error('密码必须包含数字!')) + return + } + if (passwordConfig.requireSpecialChar && !hasSpecialChar) { + callback(new Error('密码必须包含特殊字符!')) + return + } + // 3. 检查是否包含弱密码 + for (const weakPwd of passwordConfig.weakPasswords) { + if (value.includes(weakPwd)) { + callback(new Error(`密码包含常见的弱密码片段: ${weakPwd}`)) + return + } + } + // 4. 检查是否包含超过规定数量的连续字符 + if (passwordConfig.restrictConsecutiveChars && containsConsecutiveCharacters(value, passwordConfig.maxConsecutiveChars)) { + callback(new Error(`密码不能包含超过${passwordConfig.maxConsecutiveChars}位连续字符!`)) + return + } + callback() // 验证成功 +} + +function containsConsecutiveCharacters(password, maxConsecutive) { + let count = 1 + for (let i = 1; i < password.length; i++) { + if (password[i] === password[i - 1]) { + count++ + if (count > maxConsecutive) return true + } else { + count = 1 + } + } + return false +} diff --git a/src/views/index.vue b/src/views/index.vue index d5548d6f..da69c889 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -7,7 +7,6 @@ 当前版本: v{{ version }}

- @@ -57,9 +56,6 @@ >http://www.ahbonus.cn

- - -
@@ -91,6 +87,8 @@
+ + diff --git a/src/views/index_v1.vue b/src/views/index_v1.vue index d2d2ec63..405cb9e7 100644 --- a/src/views/index_v1.vue +++ b/src/views/index_v1.vue @@ -25,7 +25,7 @@ - + diff --git a/src/views/login1.vue b/src/views/login1.vue index 6e372f53..2adf4dbe 100644 --- a/src/views/login1.vue +++ b/src/views/login1.vue @@ -40,6 +40,21 @@ + + + + + +