接口参数加密,通过请求控制加解密开关

This commit is contained in:
zzyuan 2024-12-13 16:12:59 +08:00
parent aa29ef5646
commit 00d20664ac
23 changed files with 230 additions and 340 deletions

View File

@ -9,7 +9,7 @@
// import ThemePicker from '@/components/ThemePicker'
import autofit from 'autofit.js'
import { get } from '@/utils/config'
export default {
name: 'App',
// components: { ThemePicker },
@ -25,7 +25,9 @@ export default {
},
}
},
created() {
get();
},
mounted() {
autofit.init({
/* designHeight: 1080,

9
src/api/config.js Normal file
View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
// 获取系统配置
export const getConfig = () => {
return request({
url: '/auth/getConfig',
method: 'get'
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -558,16 +558,19 @@ export default {
roam: false,
coordinateSystem: 'geo', //使
zlevel: 1,
symbolSize: [80, 90],
symbolSize: [60, 70],
symbol: (value, params) => {
if (params.data.errorNum>0) {
return (
'image://' + require('@/assets/img/myImage/point_red.png')
)
} else {
return (
'image://' + require('@/assets/img/myImage/point.png')
)
if(params.data.devStatus==0){
return ('image://' + require('@/assets/img/myImage/point_grey.png'))
}else{
return ('image://' + require('@/assets/img/myImage/point.png'))
}
}
/*if (params.name == '') {
return 'image://' + require('@/assets/img/myImage/markPoints.png')

View File

@ -47,7 +47,7 @@ import TableModel from '@/components/TableModel'
import JsonExcel from 'vue-json-excel'
Vue.prototype.$eventBus = new Vue()
import global_ from '@/utils/globalUrl'
// import global_ from '@/utils/globalUrl'
// 全局方法挂载
Vue.prototype.getDicts = getDicts
@ -60,7 +60,7 @@ Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.downloadJson = downloadJson
Vue.prototype.handleTree = handleTree
Vue.prototype.globalUrl = global_
// Vue.prototype.globalUrl = global_
Vue.prototype.indexContinuation = indexContinuation
// 全局组件挂载

View File

@ -232,3 +232,10 @@ export function tansParams(params) {
export function blobValidate(data) {
return data.type !== 'application/json'
}
// 处理表格索引延续问题
export function indexContinuation(num, size) {
return (num - 1) * size + 1
}

View File

@ -1,76 +0,0 @@
let chapter = (text, companyName) => {
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
//let text = "XXX专用章";
//let companyName = "XXX科技股份有限公司";
// 绘制印章边框
let width = canvas.width / 2;
let height = canvas.height / 2;
context.lineWidth = 3;
context.strokeStyle = "#f00";
context.beginPath();
context.arc(width, height, 80, 0, Math.PI * 2); //宽、高、半径
context.stroke();
//画五角星
create5star(context, width, height, 20, "#f00", 0);
// 绘制印章名称
context.font = "14px 宋体";
context.textBaseline = "middle"; //设置文本的垂直对齐方式
context.textAlign = "center"; //设置文本的水平对对齐方式
context.lineWidth = 1;
context.strokeStyle = "#f00";
context.strokeText(text, width, height + 50);
// 绘制印章单位
context.translate(width, height); // 平移到此位置,
context.font = "14px 宋体";
let count = companyName.length; // 字数
let angle = (4 * Math.PI) / (3 * (count - 1)); // 字间角度
let chars = companyName.split("");
let c;
for (let i = 0; i < count; i++) {
c = chars[i]; // 需要绘制的字符
if (i == 0) {
context.rotate((5 * Math.PI) / 6);
} else {
context.rotate(angle);
}
context.save();
context.translate(65, 0); // 平移到此位置,此时字和x轴垂直公司名称和最外圈的距离
context.rotate(Math.PI / 2); // 旋转90度,让字平行于x轴
context.strokeText(c, 0, 0); // 此点为字的中心点
context.restore();
}
//绘制五角星
function create5star(context, sx, sy, radius, color, rotato) {
context.save();
context.fillStyle = color;
context.translate(sx, sy); //移动坐标原点
context.rotate(Math.PI + rotato); //旋转
context.beginPath(); //创建路径
// let x = Math.sin(0);
// let y = Math.cos(0);
let dig = (Math.PI / 5) * 4;
for (let i = 0; i < 5; i++) {
//画五角星的五条边
let x = Math.sin(i * dig);
let y = Math.cos(i * dig);
context.lineTo(x * radius, y * radius);
}
context.closePath();
context.stroke();
context.fill();
context.restore();
}
};
export default chapter;

11
src/utils/config.js Normal file
View File

@ -0,0 +1,11 @@
import { getConfig } from '@/api/config';
export function get() {
getConfig()
.then(response => {
console.log(response)
localStorage.setItem('systemConfig', JSON.stringify(response.data));
})
.catch(error => {
console.error('Failed to fetch config:', error);
});
}

View File

@ -1,80 +1,23 @@
// 密码强度级别常量
const STRENGTH_LEVELS = {
WEAK: 'weak', // 弱:一类字符
MEDIUM: 'medium', // 中:两类字符
STRONG: 'strong', // 强:三类字符
VERY_STRONG: 'very-strong' // 非常强:四类字符
}
// 数据设置常量
const DATA_SETTINGS = {
OPEN: true, // 开启
CLOSE: false // 关闭
}
// SM 配置
const SM_CONFIG = {
SALT: '2cc0c5f9f1749f1632efa9f63e902323' // SM3 盐值16 字节)
export const SM_CONFIG = {
SALT: '2cc0c5f9f1749f1632efa9f63e902323', // SM3 盐值16 字节)
SM4_KEY: "78d1295afa99449b99d6f83820e6965c", // SM4 对称加密密钥
SM4_SALT: "f555adf6c01d0ab0761e626a2dae34a2",
SM2_PUBLIC_KEY: 'your-public-key', // SM2 公钥
SM2_PRIVATE_KEY: 'your-private-key' // SM2 私钥
}
// AES 配置
const AES_CONFIG = {
export const AES_CONFIG = {
AES_KEY: 'zhgd@bonus@zhgd@bonus@1234567890', // AES key值
AES_IV: '1234567812345678' // AES 偏移量
}
// 登录配置
const LOGIN_CONFIG = {
CODE_PHONE_LOGIN: DATA_SETTINGS.CLOSE, // 手机号验证码登录true开启false关闭
CODE_EMAIL_LOGIN: DATA_SETTINGS.OPEN,// 邮箱验证码登录true开启false关闭
PHONE_LOGIN: DATA_SETTINGS.OPEN, // 手机号密码登录true开启false关闭
EMAIL_LOGIN: DATA_SETTINGS.CLOSE // 邮箱密码登录true开启false关闭
}
//注册配置
const REGISTER_CONFIG = {
PHONE_REGISTER: DATA_SETTINGS.OPEN, // 手机号注册true开启false关闭
EMAIL_REGISTER: DATA_SETTINGS.CLOSE // 邮箱注册true开启false关闭
export function generateUUID() {
// 使用当前时间戳和随机数生成一个 UUID
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0; // 生成随机数
const v = c === 'x' ? r : (r & 0x3 | 0x8); // 根据 UUID 规范生成相应的值
return v.toString(16); // 转换为十六进制
});
}
// 配置设置
const CONFIG = {
STRENGTH: STRENGTH_LEVELS.STRONG,//密码强度配置
IS_OPEN_REGISTER: REGISTER_CONFIG.PHONE_REGISTER || REGISTER_CONFIG.EMAIL_REGISTER, // 是否开启注册
IS_CODE_LOGIN: LOGIN_CONFIG.CODE_EMAIL_LOGIN || LOGIN_CONFIG.CODE_PHONE_LOGIN, // 是否开启短信登录
// 数据设置
dataSettings: {
integrityCheck: DATA_SETTINGS.OPEN, // 数据完整性校验true开启false关闭
encryptRequest: DATA_SETTINGS.OPEN, // 数据传输加密true开启false关闭
encryptResponse: DATA_SETTINGS.OPEN // 数据返回解密true开启false关闭
}
}
// 获取占位符文本的函数
// 获取占位符文本的函数
function getPlaceholderText() {
const loginOptions = []
if (LOGIN_CONFIG.PHONE_LOGIN) loginOptions.push('手机号')
if (LOGIN_CONFIG.EMAIL_LOGIN) loginOptions.push('邮箱')
return `用户名${loginOptions.length ? '/' + loginOptions.join('/') : ''}`
}
// 获取占位符文本的函数
// 获取占位符文本的函数
function getCodePlaceholderText() {
const loginOptions = []
if (LOGIN_CONFIG.CODE_PHONE_LOGIN) loginOptions.push('手机号')
if (LOGIN_CONFIG.CODE_EMAIL_LOGIN) loginOptions.push('邮箱')
return loginOptions.length ? loginOptions.join('/') : ''
}
module.exports = {
STRENGTH_LEVELS,
DATA_SETTINGS,
CONFIG,
SM_CONFIG,
AES_CONFIG,
LOGIN_CONFIG,
REGISTER_CONFIG,
getPlaceholderText,
getCodePlaceholderText
}

View File

@ -1,5 +1,5 @@
import Vue from 'vue'
import { mergeRecursive } from "@/utils/ruoyi";
import { mergeRecursive } from "@/utils/bonus";
import DictMeta from './DictMeta'
import DictData from './DictData'
@ -77,7 +77,6 @@ function loadDict(dict, dictMeta) {
dicts.forEach(d => {
Vue.set(dict.label[type], d.value, d.label)
})
// console.log(dict)
return dicts
})
}

View File

@ -1,4 +1,4 @@
import { mergeRecursive } from "@/utils/ruoyi";
import { mergeRecursive } from "@/utils/bonus";
import DictOptions from './DictOptions'
/**

View File

@ -1,4 +1,4 @@
import { mergeRecursive } from "@/utils/ruoyi";
import { mergeRecursive } from "@/utils/bonus";
import dictConverter from './DictConverter'
export const options = {
@ -8,7 +8,6 @@ export const options = {
* 字典请求方法签名为function(dictMeta: DictMeta): Promise
*/
request: (dictMeta) => {
console.log(`load dict ${dictMeta.type}`)
return Promise.resolve([])
},
/**

26
src/utils/download.js Normal file
View File

@ -0,0 +1,26 @@
// 下载blob文件
export const downloadFile = ({ fileData, fileType, fileName }) => {
const blob = new Blob([fileData], {
type: fileType
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = fileName
link.style.display = 'none'
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(link.href)
document.body.removeChild(link)
}
// 通用a链接下载
export const downloadFileByUrl = (url) => {
const link = document.createElement('a')
link.href = url
link.setAttribute('download', '')
link.style.display = 'none'
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(link.href)
document.body.removeChild(link)
}

View File

@ -1,26 +0,0 @@
// const qrUrl = 'http://192.168.0.14:21624/qrCode/qrCodePage?qrCode='; //测试
// const qrUrl = 'http://112.29.103.165:21626/qrCode/qrCodePage?qrCode='; //重庆
// const qrUrl = 'http://112.29.103.165:21624/qrCode/qrCodePage?qrCode='; //宁夏
// const qrUrl = 'https://z.csgmall.com.cn/gl/qrCode/qrCodePage?qrCode='; //南网
// const qrUrl = process.env.NODE_ENV === 'production' ? 'http://192.168.0.14:18866/qrCode/qrCodePage?qrCode=' : 'http://192.168.0.14:21624/qrCode/qrCodePage?qrCode='
let qrUrl = ''
const origin = window.location.origin
if (origin == 'http://112.29.103.165:21626') {
qrUrl = 'http://112.29.103.165:21626/qrCode/qrCodePage?qrCode='
} else {
qrUrl = 'http://192.168.0.14:18866/qrCode/qrCodePage?qrCode='
}
export default {
qrUrl,
}

View File

@ -1,16 +1,16 @@
import { parseTime } from './ruoyi'
import { parseTime } from './bonus'
/**
* 表格时间格式化
*/
export function formatDate(cellValue) {
if (cellValue == null || cellValue == "") return "";
var date = new Date(cellValue)
var date = new Date(cellValue)
var year = date.getFullYear()
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
}
@ -330,7 +330,7 @@ export function makeMap(str, expectsLowerCase) {
? val => map[val.toLowerCase()]
: val => map[val]
}
export const exportDefault = 'export default '
export const beautifierConf = {
@ -387,4 +387,4 @@ export function camelCase(str) {
export function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
}

View File

@ -1,30 +0,0 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
'UP8iWi1Qw0Y='
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对数据进行加密
}
// 解密
export function decrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey) // 设置私钥
return encryptor.decrypt(txt) // 对数据进行解密
}

View File

@ -6,11 +6,10 @@ import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/bonus'
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import { encryptCBC, decryptCBC } from '@/utils/aescbc'
import { CONFIG } from '@/utils/configure'
import { hashWithSM3AndSalt } from '@/utils/sm'
//let token = localStorage.getItem("tokens");
import { decryptWithSM4, encryptWithSM4, hashWithSM3AndSalt } from '@/utils/sm'
const systemConfig = JSON.parse(localStorage.getItem('systemConfig')) || {
requestConfig: { encryptRequest: false, checkIntegrity: false, encryptResponse: false }
};
let downloadLoadingInstance
// 是否显示重新登录
@ -40,11 +39,11 @@ service.interceptors.request.use(config => {
// 设置请求头
//入参加密
config.headers['encryptRequest'] = CONFIG.dataSettings.encryptRequest && encryptRequest ? 'true' : 'false'
config.headers['encryptRequest'] = systemConfig.requestConfig.encryptRequest && encryptRequest ? 'true' : 'false'
// 数据完整性校验
config.headers['checkIntegrity'] = CONFIG.dataSettings.integrityCheck && checkIntegrity ? 'true' : 'false'
config.headers['checkIntegrity'] = systemConfig.requestConfig.checkIntegrity && checkIntegrity ? 'true' : 'false'
//回参是否加密
config.headers['encryptResponse'] = CONFIG.dataSettings.encryptResponse && encryptResponse ? 'true' : 'false'
config.headers['encryptResponse'] = systemConfig.requestConfig.encryptResponse && encryptResponse ? 'true' : 'false'
const isRepeatSubmit = repeatSubmit
// 处理 Token
@ -52,21 +51,6 @@ service.interceptors.request.use(config => {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义 token
}
// // 处理 GET 请求
// if (config.method === 'get' && config.params) {
// let params = tansParams(config.params).slice(0, -1)
// // 数据完整性校验
// if (CONFIG.dataSettings.integrityCheck && checkIntegrity) {
// config.headers['Params-Hash'] = hashWithSM3AndSalt(params)
// }
// // 加密参数
// if (CONFIG.dataSettings.encryptRequest && encryptRequest) {
// params = encryptCBC(params)
// }
// config.url = `${config.url}?${params}`
// config.params = {}
// }
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
@ -78,15 +62,12 @@ service.interceptors.request.use(config => {
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
let data = typeof config.data === 'object' ? JSON.stringify(config.data) : config.data
let contentType = config.headers['Content-Type']
if (contentType.includes('application/json')) {
// 数据完整性校验
if (CONFIG.dataSettings.integrityCheck && checkIntegrity) {
config.headers['Params-Hash'] = hashWithSM3AndSalt(data)
config.data = data
}
if (contentType.includes('application/json') && typeof data !== 'undefined') {
// 加密数据
if (CONFIG.dataSettings.encryptRequest && encryptRequest) {
config.data = encryptCBC(data)
if (systemConfig.requestConfig.encryptRequest && encryptRequest) {
console.log(data);
console.log(hashWithSM3AndSalt(data));
config.data = encryptWithSM4(data+"|"+hashWithSM3AndSalt(data))
}
}
// 检查请求数据大小
@ -115,8 +96,8 @@ service.interceptors.request.use(config => {
// 响应拦截器
service.interceptors.response.use(res => {
if (res.headers.encryptresponse) {
res.data = JSON.parse(decryptCBC(res.data))
if (res.headers.encryptresponse && !res.data.hasOwnProperty('code')) {
res.data = JSON.parse(decryptWithSM4(res.data))
}
// 未设置状态码则默认成功状态
const code = res.data.code || 200
@ -157,7 +138,6 @@ service.interceptors.response.use(res => {
}
},
error => {
console.log('err' + error)
let { message } = error
if (message == 'Network Error') {
message = '后端接口连接异常'

View File

@ -1,6 +1,9 @@
// src/utils/encryption.js
import sm3 from 'sm-crypto/src/sm3'
import { sm2, sm3, sm4 } from 'sm-crypto'
// 配置项例如盐值、SM2 公私钥、SM4 密钥
import { SM_CONFIG } from './configure'
import SM4 from 'sm-crypto/src/sm4'
import { hexToArray } from 'sm-crypto/src/sm2/utils'
// SM3 哈希
export function hashSM3(text) {
@ -15,3 +18,33 @@ export function hashWithSM3AndSalt(text) {
// 使用 SM3 进行哈希
return hashSM3(textWithSalt)
}
// SM2 加密
export function encryptWithSM2(text) {
// SM2 公钥加密
return sm2.doEncrypt(text, SM_CONFIG.SM2_PUBLIC_KEY)
}
// SM2 解密
export function decryptWithSM2(encryptedText) {
// SM2 私钥解密
return sm2.doDecrypt(encryptedText, SM_CONFIG.SM2_PRIVATE_KEY)
}
/**
* 加密函数
* @param {string} plainText
* @returns {string} 加密后的密文Hex 编码格式
*/
export function encryptWithSM4(plainText) {
return sm4.encrypt(plainText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5',iv:SM_CONFIG.SM4_SALT});
}
/**
* 解密函数
* @param {string} cipherText
* @returns {string} 解密后的明文
*/
export function decryptWithSM4(cipherText){
return SM4.decrypt(cipherText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5' ,iv:SM_CONFIG.SM4_SALT});
}

View File

@ -1,6 +1,4 @@
import { CONFIG } from '@/utils/configure'
import passwordConfig from '@/utils/passwordConfig'
const systemConfig = JSON.parse(localStorage.getItem('systemConfig'));
/**
* @param {string} path
* @returns {Boolean}
@ -92,71 +90,12 @@ export function validPwd(value) {
return reg.test(value)
}
/**
* 长度至少为8个字符
* 一般长度至少为8个字符并包含至少一种字符类型
* 长度至少为8个字符并包含至少两种字符类型
* 非常强长度至少为8个字符并包含所有四种字符类型
* @param rule
* @param value
* @param callback
* @returns {*}
*/
export function validatePassword(rule, value, callback) {
if (!value) {
return callback(new Error('请输入密码'))
}
const lengthRegex = /^.{8,20}$/
const uppercaseRegex = /[A-Z]/
const lowercaseRegex = /[a-z]/
const digitRegex = /\d/
const specialCharRegex = /[!@#$%^&*(),.?":{}|<>]/
if (!lengthRegex.test(value)) {
return callback(new Error('密码长度必须为8到20位'))
}
const checks = [
{ regex: uppercaseRegex, message: '必须包含至少一个大写字母' },
{ regex: lowercaseRegex, message: '必须包含至少一个小写字母' },
{ regex: digitRegex, message: '必须包含至少一个数字' },
{ regex: specialCharRegex, message: '必须包含至少一个特殊字符' }
]
let passedChecks = checks.filter(check => check.regex.test(value)).length
let requiredChecks
switch (CONFIG.STRENGTH) {
case 'weak':
requiredChecks = 1
break
case 'medium':
requiredChecks = 2
break
case 'strong':
requiredChecks = 3
break
case 'very-strong':
requiredChecks = 4
break
default:
return callback(new Error('请选择有效的密码强度'))
}
if (passedChecks < requiredChecks) {
return callback(new Error(`密码至少包含 ${requiredChecks} 类字符(大写字母,小写字母,数字,特殊字符)`))
}
callback()
}
export function validateNewPassword(rule, value, callback) {
// 使用配置文件中的策略进行验证
// 1. 检查密码长度
if (value.length < passwordConfig.minLength || value.length > passwordConfig.maxLength) {
callback(new Error('密码长度应为' + passwordConfig.minLength + '至' + passwordConfig.maxLength + '位!'))
if (value.length < systemConfig.passwordConfig.minLength || value.length > systemConfig.passwordConfig.maxLength) {
callback(new Error('密码长度应为' + systemConfig.passwordConfig.minLength + '至' + systemConfig.passwordConfig.maxLength + '位!'))
return
}
@ -166,46 +105,113 @@ export function validateNewPassword(rule, value, callback) {
const hasDigit = /\d/.test(value)
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value)
if (passwordConfig.requireUpperCase && !hasUpperCase) {
if (systemConfig.passwordConfig.requireUpperCase && !hasUpperCase) {
callback(new Error('密码必须包含大写字母!'))
return
}
if (passwordConfig.requireLowerCase && !hasLowerCase) {
if (systemConfig.passwordConfig.requireLowerCase && !hasLowerCase) {
callback(new Error('密码必须包含小写字母!'))
return
}
if (passwordConfig.requireDigit && !hasDigit) {
if (systemConfig.passwordConfig.requireDigit && !hasDigit) {
callback(new Error('密码必须包含数字!'))
return
}
if (passwordConfig.requireSpecialChar && !hasSpecialChar) {
if (systemConfig.passwordConfig.requireSpecialChar && !hasSpecialChar) {
callback(new Error('密码必须包含特殊字符!'))
return
}
// 3. 检查是否包含弱密码
for (const weakPwd of passwordConfig.weakPasswords) {
if (value.includes(weakPwd)) {
for (const weakPwd of systemConfig.passwordConfig.weakPasswords) {
// 将密码和弱密码都转换为小写进行比较
if (value.toLowerCase().includes(weakPwd.toLowerCase())) {
callback(new Error(`密码包含常见的弱密码片段: ${weakPwd}`))
return
}
}
// 4. 检查是否包含超过规定数量的连续字符
if (passwordConfig.restrictConsecutiveChars && containsConsecutiveCharacters(value, passwordConfig.maxConsecutiveChars)) {
callback(new Error(`密码不能包含超过${passwordConfig.maxConsecutiveChars}位连续字符!`))
if (systemConfig.passwordConfig.restrictConsecutiveChars && containsConsecutiveCharacters(value, systemConfig.passwordConfig.maxConsecutiveChars)) {
callback(new Error(`密码不能包含超过${systemConfig.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
/**
* 检查密码中是否包含超过 n 个连续相同字符连续递增/递减的数字或字母不区分大小写
*/
function containsConsecutiveCharacters(password, n) {
// 检查连续相同字符
n = n + 1; // 允许最多 n 个连续字符
for (let i = 0; i <= password.length - n; i++) {
let consecutiveSameChar = true;
for (let j = 1; j < n; j++) {
if (password[i + j] !== password[i]) {
consecutiveSameChar = false;
break;
}
}
if (consecutiveSameChar) {
return true; // 包含超过 n 个连续相同字符
}
}
return false
// 检查连续递增或递减的数字
for (let i = 0; i <= password.length - n; i++) {
let consecutiveIncreasing = true;
let consecutiveDecreasing = true;
for (let j = 1; j < n; j++) {
const currentChar = password[i];
const nextChar = password[i + j];
// 检查数字递增或递减
if (/\d/.test(currentChar) && /\d/.test(nextChar)) {
if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) + j) {
consecutiveIncreasing = false;
}
if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) - j) {
consecutiveDecreasing = false;
}
} else {
consecutiveIncreasing = false;
consecutiveDecreasing = false;
break;
}
}
if (consecutiveIncreasing || consecutiveDecreasing) {
return true; // 包含超过 n 个递增或递减的连续数字
}
}
// 检查连续递增或递减的字母(不区分大小写)
for (let i = 0; i <= password.length - n; i++) {
let consecutiveIncreasing = true;
let consecutiveDecreasing = true;
for (let j = 1; j < n; j++) {
const currentChar = password[i].toLowerCase(); // 转为小写
const nextChar = password[i + j].toLowerCase(); // 转为小写
// 检查字母递增或递减
if (/[a-zA-Z]/.test(currentChar) && /[a-zA-Z]/.test(nextChar)) {
if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) + j) {
consecutiveIncreasing = false;
}
if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) - j) {
consecutiveDecreasing = false;
}
} else {
consecutiveIncreasing = false;
consecutiveDecreasing = false;
break;
}
}
if (consecutiveIncreasing || consecutiveDecreasing) {
return true; // 包含超过 n 个递增或递减的连续字母
}
}
// 不包含连续相同字符、数字或字母序列
return false;
}

View File

@ -272,7 +272,9 @@ export default {
handelCloseSub(){
this.showProjSec = true
this.showLefSec = false
this.renderAllProj()
setTimeout(()=>{
this.getProjData()
},500)
},
validForbid(value) { value = value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g, '').replace(/\s/g, ""); return value; }

View File

@ -188,6 +188,7 @@ export default {
devNum: item.devNum,
successNum: item.successNum,
errorNum: item.errorNum,
devStatus: item.devStatus,
itemStyle: {
normal: {
areaColor: '#7DDEFF'

View File

@ -222,6 +222,7 @@ export default {
devNum: item.devNum,
successNum: item.successNum,
errorNum: item.errorNum,
devStatus: item.devStatus,
itemStyle: {
normal: {
areaColor: '#7DDEFF',