From 397971378357db417763bd388c25cdeeaed5d33c Mon Sep 17 00:00:00 2001
From: RuoYi <yzz_ivy@163.com>
Date: Fri, 17 Apr 2026 14:47:43 +0800
Subject: [PATCH] 用户密码支持自定义配置规则

---
 ruoyi-ui/src/views/register.vue                                                             |   41 +++++++------
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java |    7 ++
 ruoyi-ui/src/store/modules/user.js                                                          |    2 
 ruoyi-ui/src/utils/passwordRule.js                                                          |   71 +++++++++++++++++++++++
 ruoyi-ui/src/views/system/user/profile/resetPwd.vue                                         |   37 ++++++------
 ruoyi-ui/src/views/system/user/index.vue                                                    |   19 +----
 sql/ry_20260417.sql                                                                         |    1 
 7 files changed, 126 insertions(+), 52 deletions(-)

diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
index a59c4b4..6fce9c9 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
@@ -189,11 +189,18 @@
         ajax.put("user", user);
         ajax.put("roles", roles);
         ajax.put("permissions", permissions);
+        ajax.put("pwdChrtype", getSysAccountChrtype());
         ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
         ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
         return ajax;
     }
 
+    // 获取用户密码自定义配置规则
+    public String getSysAccountChrtype()
+    {
+        return Convert.toStr(configService.selectConfigByKey("sys.account.chrtype"), "0");
+    }
+
     // 检查初始密码是否提醒修改
     public boolean initPasswordIsModify(Date pwdUpdateDate)
     {
diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js
index 18ccf1c..8769ea2 100644
--- a/ruoyi-ui/src/store/modules/user.js
+++ b/ruoyi-ui/src/store/modules/user.js
@@ -1,5 +1,6 @@
 import store from '@/store'
 import router from '@/router'
+import cache from '@/plugins/cache'
 import { MessageBox, } from 'element-ui'
 import { login, logout, getInfo, refreshToken } from '@/api/login'
 import { getToken, setToken, setExpiresIn, removeToken } from '@/utils/auth'
@@ -82,6 +83,7 @@
           commit('SET_NAME', user.userName)
           commit('SET_NICK_NAME', user.nickName)
           commit('SET_AVATAR', avatar)
+          cache.session.set('pwrChrtype', res.pwdChrtype)
           /* 初始密码提示 */
           if(res.isDefaultModifyPwd) {
             MessageBox.confirm('您的密码还是初始密码,请修改密码!',  '安全提示', {  confirmButtonText: '确定',  cancelButtonText: '取消',  type: 'warning' }).then(() => {
diff --git a/ruoyi-ui/src/utils/passwordRule.js b/ruoyi-ui/src/utils/passwordRule.js
new file mode 100644
index 0000000..dd56f81
--- /dev/null
+++ b/ruoyi-ui/src/utils/passwordRule.js
@@ -0,0 +1,71 @@
+/**
+ * 密码强度规则
+ * 根据参数 chrtype 动态生成校验规则
+ *
+ * chrtype 说明:
+ *   0 - 任意字符(默认)
+ *   1 - 纯数字(0-9)
+ *   2 - 纯字母(a-z / A-Z)
+ *   3 - 字母 + 数字(必须同时包含)
+ *   4 - 字母 + 数字 + 特殊字符(必须同时包含,特殊字符:~!@#$%^&*()-=_+)
+ */
+import cache from '@/plugins/cache'
+
+// 各类型对应的正则、错误提示
+const PWD_RULES = {
+  '0': { pattern: /^[^<>"'|\\]+$/, message: '密码不能包含非法字符:< > " \' \\ |' },
+  '1': { pattern: /^[0-9]+$/, message: '密码只能为数字(0-9)' },
+  '2': { pattern: /^[a-zA-Z]+$/, message: '密码只能为英文字母(a-z、A-Z)' },
+  '3': { pattern: /^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$/, message: '密码必须同时包含字母和数字' },
+  '4': { pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[~!@#$%^&*()\-=_+])[A-Za-z\d~!@#$%^&*()\-=_+]+$/, message: '密码必须同时包含字母、数字和特殊字符(~!@#$%^&*()-=_+)' }
+}
+
+export default {
+  data() {
+    return {
+      // 密码限制类型
+      pwdChrType: cache.session.get('pwrChrtype') || '0'
+    }
+  },
+  computed: {
+    // 默认密码校验
+    pwdValidator() {
+      const rule = PWD_RULES[this.pwdChrType] || PWD_RULES['0']
+      return [
+        { required: true, message: '密码不能为空', trigger: 'blur' },
+        { min: 6, max: 20, message: '密码长度必须介于 6 和 20 之间', trigger: 'blur' },
+        { pattern: rule.pattern, message: rule.message, trigger: 'blur' }
+      ]
+    },
+    // 校验prompt的inputValidator函数
+    pwdPromptValidator() {
+      const rule = PWD_RULES['0']
+      return (value) => {
+        if (!value || value.length < 6 || value.length > 20) {
+          return '密码长度必须介于 6 和 20 之间'
+        }
+        if (!rule.pattern.test(value)) {
+          return rule.message
+        }
+      }
+    },
+    // 个人中心密码校验
+    infoPwdValidator() {
+      const rule = PWD_RULES[this.pwdChrType] || PWD_RULES['0']
+      return [
+        { required: true, message: '新密码不能为空', trigger: 'blur' },
+        { min: 6, max: 20, message: '新密码长度必须介于 6 和 20 之间', trigger: 'blur' },
+        { pattern: rule.pattern, message: rule.message, trigger: 'blur' }
+      ]
+    },
+    // 注册页面密码校验
+    registerPwdValidator() {
+      const rule = PWD_RULES['0']
+      return [
+        { required: true, message: '请输入您的密码', trigger: 'blur' },
+        { min: 6, max: 20, message: '用户密码长度必须介于 6 和 20 之间', trigger: 'blur' },
+        { pattern: rule.pattern, message: rule.message, trigger: 'blur' }
+      ]
+    }
+  }
+}
diff --git a/ruoyi-ui/src/views/register.vue b/ruoyi-ui/src/views/register.vue
index 73f865f..6dbfa87 100644
--- a/ruoyi-ui/src/views/register.vue
+++ b/ruoyi-ui/src/views/register.vue
@@ -7,7 +7,7 @@
           <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
         </el-input>
       </el-form-item>
-      <el-form-item prop="password">
+      <el-form-item prop="password" :rules="registerPwdValidator">
         <el-input
           v-model="registerForm.password"
           type="password"
@@ -68,18 +68,12 @@
 
 <script>
 import { getCodeImg, register } from "@/api/login"
+import passwordRule from "@/utils/passwordRule"
 import defaultSettings from '@/settings'
 
 export default {
-  name: "Register",
+  mixins: [passwordRule],
   data() {
-    const equalToPassword = (rule, value, callback) => {
-      if (this.registerForm.password !== value) {
-        callback(new Error("两次输入的密码不一致"))
-      } else {
-        callback()
-      }
-    }
     return {
       title: process.env.VUE_APP_TITLE,
       footerContent: defaultSettings.footerContent,
@@ -91,24 +85,31 @@
         code: "",
         uuid: ""
       },
-      registerRules: {
+      loading: false,
+      captchaEnabled: true
+    }
+  },
+  computed: {
+    registerRules() {
+      return {
         username: [
           { required: true, trigger: "blur", message: "请输入您的账号" },
           { min: 2, max: 20, message: '用户账号长度必须介于 2 和 20 之间', trigger: 'blur' }
         ],
-        password: [
-          { required: true, trigger: "blur", message: "请输入您的密码" },
-          { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" },
-          { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
-        ],
         confirmPassword: [
-          { required: true, trigger: "blur", message: "请再次输入您的密码" },
-          { required: true, validator: equalToPassword, trigger: "blur" }
+          { required: true, message: "请再次输入您的密码", trigger: "blur" },
+          {
+            validator: (rule, value, callback) => {
+              if (this.registerForm.password !== value) {
+                callback(new Error("两次输入的密码不一致"))
+              } else {
+                callback()
+              }
+            }, trigger: "blur"
+          }
         ],
         code: [{ required: true, trigger: "change", message: "请输入验证码" }]
-      },
-      loading: false,
-      captchaEnabled: true
+      }
     }
   },
   created() {
diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue
index 7d3f940..73f4b49 100644
--- a/ruoyi-ui/src/views/system/user/index.vue
+++ b/ruoyi-ui/src/views/system/user/index.vue
@@ -116,7 +116,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
+            <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password" :rules="pwdValidator">
               <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
             </el-form-item>
           </el-col>
@@ -181,9 +181,11 @@
 import TreePanel from "@/components/TreePanel"
 import ExcelImportDialog from "@/components/ExcelImportDialog"
 import UserViewDrawer from "./view"
+import passwordRule from "@/utils/passwordRule"
 
 export default {
   name: "User",
+  mixins: [passwordRule],
   dicts: ['sys_normal_disable', 'sys_user_sex'],
   components: { Treeselect, TreePanel, ExcelImportDialog, UserViewDrawer },
   data() {
@@ -247,11 +249,6 @@
         ],
         nickName: [
           { required: true, message: "用户昵称不能为空", trigger: "blur" }
-        ],
-        password: [
-          { required: true, message: "用户密码不能为空", trigger: "blur" },
-          { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' },
-          { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
         ],
         email: [
           {
@@ -405,17 +402,11 @@
     },
     /** 重置密码按钮操作 */
     handleResetPwd(row) {
-      this.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
+      this.$prompt(`请输入「${row.userName}」的新密码`, "重置密码", {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         closeOnClickModal: false,
-        inputPattern: /^.{5,20}$/,
-        inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
-        inputValidator: (value) => {
-          if (/<|>|"|'|\||\\/.test(value)) {
-            return "不能包含非法字符:< > \" ' \\\ |"
-          }
-        },
+        inputValidator: this.pwdPromptValidator
       }).then(({ value }) => {
         resetUserPwd(row.userId, value).then(() => {
           this.$modal.msgSuccess("修改成功,新密码是:" + value)
diff --git a/ruoyi-ui/src/views/system/user/profile/resetPwd.vue b/ruoyi-ui/src/views/system/user/profile/resetPwd.vue
index acfdff1..369e451 100644
--- a/ruoyi-ui/src/views/system/user/profile/resetPwd.vue
+++ b/ruoyi-ui/src/views/system/user/profile/resetPwd.vue
@@ -1,9 +1,9 @@
 <template>
-  <el-form ref="form" :model="user" :rules="rules" label-width="80px">
+  <el-form ref="form" :model="user" :rules="formRules" label-width="80px">
     <el-form-item label="旧密码" prop="oldPassword">
       <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/>
     </el-form-item>
-    <el-form-item label="新密码" prop="newPassword">
+    <el-form-item label="新密码" prop="newPassword" :rules="infoPwdValidator">
       <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
     </el-form-item>
     <el-form-item label="确认密码" prop="confirmPassword">
@@ -18,35 +18,36 @@
 
 <script>
 import { updateUserPwd } from "@/api/system/user"
+import passwordRule from "@/utils/passwordRule"
 
 export default {
+  mixins: [passwordRule],
   data() {
-    const equalToPassword = (rule, value, callback) => {
-      if (this.user.newPassword !== value) {
-        callback(new Error("两次输入的密码不一致"))
-      } else {
-        callback()
-      }
-    }
     return {
       user: {
         oldPassword: undefined,
         newPassword: undefined,
         confirmPassword: undefined
-      },
-      // 表单校验
-      rules: {
+      }
+    }
+  },
+  computed: {
+    formRules() {
+      return {
         oldPassword: [
           { required: true, message: "旧密码不能为空", trigger: "blur" }
         ],
-        newPassword: [
-          { required: true, message: "新密码不能为空", trigger: "blur" },
-          { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
-          { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
-        ],
         confirmPassword: [
           { required: true, message: "确认密码不能为空", trigger: "blur" },
-          { required: true, validator: equalToPassword, trigger: "blur" }
+          {
+            validator: (rule, value, callback) => {
+              if (this.user.newPassword !== value) {
+                callback(new Error("两次输入的密码不一致"))
+              } else {
+                callback()
+              }
+            }, trigger: "blur"
+          }
         ]
       }
     }
diff --git a/sql/ry_20260402.sql b/sql/ry_20260417.sql
similarity index 98%
rename from sql/ry_20260402.sql
rename to sql/ry_20260417.sql
index 23c766d..99cd49e 100644
--- a/sql/ry_20260402.sql
+++ b/sql/ry_20260417.sql
@@ -552,6 +552,7 @@
 insert into sys_config values(5, '用户登录-黑名单列表',           'sys.login.blackIPList',            '',              'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
 insert into sys_config values(6, '用户管理-初始密码修改策略',     'sys.account.initPasswordModify',   '1',             'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框');
 insert into sys_config values(7, '用户管理-账号密码更新周期',     'sys.account.passwordValidateDays', '0',             'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框');
+insert into sys_config values(8, '用户管理-密码字符范围',         'sys.account.chrtype',              '0',             'Y', 'admin', sysdate(), '', null, '默认任意字符范围,0任意(密码可以输入任意字符),1数字(密码只能为0-9数字),2英文字母(密码只能为a-z和A-Z字母),3字母和数字(密码必须包含字母,数字),4字母数字和特殊字符(目前支持的特殊字符包括:~!@#$%^&*()-=_+)');
 
 
 -- ----------------------------

--
Gitblit v1.9.3