diff --git a/package.json b/package.json index 5c90ab92..0b43c319 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "nprogress": "0.2.0", "quill": "1.3.7", "screenfull": "5.0.2", + "sm-crypto": "^0.3.13", "sortablejs": "1.10.2", "vue": "2.6.12", "vue-count-to": "1.0.13", diff --git a/src/api/system/user.js b/src/api/system/user.js index cda6526e..450f9dd1 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -1,5 +1,5 @@ import request from '@/utils/request' -import { parseStrEmpty } from "@/utils/bonus"; +import { parseStrEmpty } from '@/utils/bonus' // 查询用户列表 export function listUser(query) { @@ -136,3 +136,12 @@ export function deptTreeSelect() { method: 'get' }) } + +//用户注册审批 +export function approvalStatus(data) { + return request({ + url: '/system/user/approvalStatus', + method: 'post', + data: data + }) +} diff --git a/src/store/modules/user.js b/src/store/modules/user.js index d2990829..5c2c2ff6 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -76,7 +76,7 @@ const user = { mobile: userInfo.mobile.trim(), uuid: userInfo.uuid, code: userInfo.code, - loginType: '' + loginType: userInfo.loginType } return new Promise((resolve, reject) => { getPhoneCode(payload).then(res => { diff --git a/src/utils/bonus.js b/src/utils/bonus.js index 488bb7ff..792bafcd 100644 --- a/src/utils/bonus.js +++ b/src/utils/bonus.js @@ -224,6 +224,7 @@ export function tansParams(params) { } } } + console.log(result) return result } diff --git a/src/utils/request.js b/src/utils/request.js index 7cc83ac7..bfda5152 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,143 +1,155 @@ import axios from 'axios' -import { Notification, MessageBox, Message, Loading } from 'element-ui' +import { Loading, Message, MessageBox, Notification } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' import errorCode from '@/utils/errorCode' -import { tansParams, blobValidate } from "@/utils/bonus"; +import { blobValidate, tansParams } from '@/utils/bonus' import cache from '@/plugins/cache' import { saveAs } from 'file-saver' -import { encryptCBC, decryptCBC } from '@/utils/aescbc' +import { decryptCBC, encryptCBC } from '@/utils/aescbc' +import { hashWithSM3AndSalt } from '@/utils/sm' // 导入SM3哈希函数 + +let downloadLoadingInstance +export let isRelogin = { show: false } -let downloadLoadingInstance; -// 是否显示重新登录 -export let isRelogin = { show: false }; axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' + // 创建axios实例 const service = axios.create({ - // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: process.env.VUE_APP_BASE_API, - // 超时 timeout: 10000 }) -// request拦截器 -service.interceptors.request.use(config => { - // 是否需要设置 token - const isToken = (config.headers || {}).isToken === false - // 是否需要防止数据重复提交 - const isRepeatSubmit = (config.headers || {}).repeatSubmit === false - if (getToken() && !isToken) { - config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 - } -// return Promise.reject('无效的会话,或者会话已过期,请重新登录。') - // get请求映射params参数 - if (config.method === 'get' && config.params) { - let param=tansParams(config.params); - if(param){ - param = param.slice(0, -1); - param=encryptCBC(param); - } - let url = config.url + '?' + param; - config.params = {}; - config.url = url; - } - if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { +// 请求拦截器 +service.interceptors.request.use( + (config) => { + const isToken = (config.headers || {}).isToken === false + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false - const requestObj = { - url: config.url, - data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, - time: new Date().getTime() + // 如果需要token且存在token,则设置Authorization头 + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() } - const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 - const limitSize = 5 * 1024 * 1024; // 限制存放数据5M - if (requestSize >= limitSize) { - console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') - return config; + + // 处理GET请求参数并加密 + if (config.method === 'get' && config.params) { + let param = tansParams(config.params) + + if (param) { + param = param.slice(0, -1) + param = encryptCBC(param) + config.headers['Params-Hash'] = hashWithSM3AndSalt(param) + } + config.url = `${config.url}?${param}` + config.params = {} } - const sessionObj = cache.session.getJSON('sessionObj') - if (sessionObj === undefined || sessionObj === null || sessionObj === '') { - cache.session.setJSON('sessionObj', requestObj) - } else { - const s_url = sessionObj.url; // 请求地址 - const s_data = sessionObj.data; // 请求数据 - const s_time = sessionObj.time; // 请求时间 - const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 - if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { - const message = '数据正在处理,请勿重复提交'; - console.warn(`[${s_url}]: ` + message) - return Promise.reject(new Error(message)) - } else { + + // 防止重复提交 + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length + const limitSize = 5 * 1024 * 1024 // 限制请求数据大小为5M + + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: 请求数据大小超出允许的5M限制,无法进行防重复提交验证。`) + return config + } + + const sessionObj = cache.session.getJSON('sessionObj') + if (!sessionObj) { cache.session.setJSON('sessionObj', requestObj) + } else { + const { url, data, time } = sessionObj + const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交 + + if (data === requestObj.data && requestObj.time - time < interval && url === requestObj.url) { + const message = '数据正在处理,请勿重复提交' + console.warn(`[${url}]: ${message}`) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } } } - } - console.log(config) - if( config.headers['Content-Type']=='application/json;charset=utf-8'){ - if(typeof (config.data)=='object'){ - config.data = encryptCBC(JSON.stringify(config.data)) - config.headers['Content-Type']='application/json'; - } - } - //对下载请求进行数据参数拦截加密 - if(config.headers['Content-Type']=='application/x-www-form-urlencoded'){ - console.log(config) - console.log(config.data) - if(typeof (config.data)=='object'){ + // 如果Content-Type为application/json且数据为对象,则加密数据 + if (config.headers['Content-Type'] === 'application/json;charset=utf-8' && typeof config.data === 'object') { + config.data = encryptCBC(JSON.stringify(config.data)) + config.headers['Content-Type'] = 'application/json' console.log(config.data) - let formData=tansParams(config.data); - if(formData){ - formData = formData.slice(0, -1); - let formdata={}; - formdata.formData=encryptCBC(formData) - config.data=formdata; - } - }else{ - config.data ="formData="+ encryptCBC(JSON.stringify(config.data)) + config.headers['Params-Hash'] = hashWithSM3AndSalt(config.data) // 添加数据哈希到请求头 } - } - if(config.headers['Content-Type']==null || config.headers['Content-Type']==''){ - config.headers['Content-Type']='application/json'; - console.warn("请求类型为空"); - } - return config -}, error => { + // 对下载请求进行数据参数拦截加密 + if (config.headers['Content-Type'] === 'application/x-www-form-urlencoded') { + if (typeof config.data === 'object') { + let formData = tansParams(config.data) + if (formData) { + formData = formData.slice(0, -1) + const encryptedData = encryptCBC(formData) + config.data = { formData: encryptedData } + config.headers['Params-Hash'] = hashWithSM3AndSalt(config.data) // 添加参数哈希到请求头 + } + } else { + const encryptedData = encryptCBC(JSON.stringify(config.data)) + config.data = `formData=${encryptedData}` + config.headers['Params-Hash'] = hashWithSM3AndSalt(config.data) // 添加参数哈希到请求头 + } + } + + // 如果Content-Type为空,则设置为application/json + if (!config.headers['Content-Type']) { + config.headers['Content-Type'] = 'application/json' + console.warn('请求类型为空') + } + + return config + }, + (error) => { console.log(error) - Promise.reject(error) -}) + return Promise.reject(error) + } +) // 响应拦截器 -service.interceptors.response.use(res => { - //自动解密 - console.log("res.data.decrypt=="+res.data.decrypt) - if(typeof res.data.decrypt!='undefined' && res.data.decrypt){ - const resultData=decryptCBC(res.data.data); - res.data=JSON.parse(resultData); +service.interceptors.response.use( + (res) => { + // 自动解密响应数据 + if (res.data.decrypt) { + res.data = JSON.parse(decryptCBC(res.data.data)) + } else if (typeof res.data.code === 'undefined') { + res.data = res.data.data } - else if(typeof res.data.code=='undefined'){ - res.data= res.data.data - } - // 未设置状态码则默认成功状态 - const code = res.data.code || 200; - // 获取错误信息 + + // 获取状态码 + const code = res.data.code || 200 const msg = errorCode[code] || res.data.msg || errorCode['default'] - // 二进制数据则直接返回 - if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + + // 处理二进制数据直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { return res.data } + if (code === 401) { if (!isRelogin.show) { - isRelogin.show = true; - MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { - isRelogin.show = false; + isRelogin.show = true + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + isRelogin.show = false store.dispatch('LogOut').then(() => { - location.href = '/index'; + location.href = '/index' }) - }).catch(() => { - isRelogin.show = false; - }); - } + }).catch(() => { + isRelogin.show = false + }) + } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { Message({ message: msg, type: 'error' }) @@ -152,47 +164,52 @@ service.interceptors.response.use(res => { return res.data } }, - error => { + (error) => { console.log('err' + error) - let { message } = error; - if (message == "Network Error") { - message = "后端接口连接异常"; - } else if (message.includes("timeout")) { - message = "系统接口请求超时"; - } else if (message.includes("Request failed with status code")) { - message = "系统接口" + message.substr(message.length - 3) + "异常"; + let { message } = error + + if (message === 'Network Error') { + message = '后端接口连接异常' + } else if (message.includes('timeout')) { + message = '系统接口请求超时' + } else if (message.includes('Request failed with status code')) { + message = `系统接口${message.substr(message.length - 3)}异常` } - Message({ message: message, type: 'error', duration: 5 * 1000 }) + + Message({ message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) // 通用下载方法 export function download(url, params, filename, config) { - downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + downloadLoadingInstance = Loading.service({ + text: '正在下载数据,请稍候', + spinner: 'el-icon-loading', + background: 'rgba(0, 0, 0, 0.7)' + }) + return service.post(url, params, { - transformRequest: [(params) => { return tansParams(params) }], - headers: { 'Content-Type': 'application/x-www-form-urlencoded', - "encryption":"encryption" - }, + transformRequest: [(params) => tansParams(params)], + headers: { 'Content-Type': 'application/x-www-form-urlencoded', encryption: 'encryption' }, responseType: 'blob', ...config - }).then(async (data) => { - const isBlob = blobValidate(data); + }).then(async(data) => { + const isBlob = blobValidate(data) if (isBlob) { const blob = new Blob([data]) saveAs(blob, filename) } else { - const resText = await data.text(); - const rspObj = JSON.parse(resText); + const resText = await data.text() + const rspObj = JSON.parse(resText) const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] - Message.error(errMsg); + Message.error(errMsg) } - downloadLoadingInstance.close(); + downloadLoadingInstance.close() }).catch((r) => { console.error(r) Message.error('下载文件出现错误,请联系管理员!') - downloadLoadingInstance.close(); + downloadLoadingInstance.close() }) } diff --git a/src/utils/sm.js b/src/utils/sm.js new file mode 100644 index 00000000..2a7e2616 --- /dev/null +++ b/src/utils/sm.js @@ -0,0 +1,51 @@ +// src/utils/encryption.js +import sm2 from 'sm-crypto/src/sm2' +import sm3 from 'sm-crypto/src/sm3' +import sm4 from 'sm-crypto/src/sm4' + +// 示例密钥对,实际使用中需要从安全来源获取 +const privateKey = 'your-private-key' +const publicKey = 'your-public-key' +// 生成随机盐值(16 字节) +const salt = '2cc0c5f9f1749f1632efa9f63e902323' +const sm4Key = 'your-sm4-key' // SM4 对称密钥,需要为 128 比特 (16 字节) + +// SM2 加密 +export function encryptSM2(data) { + // 使用公钥对数据进行加密 + return sm2.doEncrypt(data, publicKey, 1) // 1 表示 C1C3C2 加密模式 +} + +// SM2 解密 +export function decryptSM2(data) { + // 使用私钥对数据进行解密 + return sm2.doDecrypt(data, privateKey, 1) // 1 表示 C1C3C2 加密模式 +} + +// SM3 哈希 +export function hashSM3(data) { + // 对数据进行哈希计算 + return sm3(data) +} + +// 使用 SM3 进行哈希并加入盐值 +export function hashWithSM3AndSalt(text) { + // 将文本和盐值拼接在一起 + const textWithSalt = salt + text + // 使用 SM3 进行哈希 + return hashSM3(textWithSalt) +} + +// SM4 加密 +export function encryptSM4(data) { + // 使用对称密钥对数据进行加密 + const sm4Instance = new sm4() + return sm4Instance.encrypt(data, sm4Key) +} + +// SM4 解密 +export function decryptSM4(data) { + // 使用对称密钥对数据进行解密 + const sm4Instance = new sm4() + return sm4Instance.decrypt(data, sm4Key) +} diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 0dd52cde..5a863e24 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -133,8 +133,9 @@ v-hasPermi="['system:user:edit']" >分配角色 - 账号审批 @@ -292,12 +293,13 @@ import { updateUser, resetUserPwd, changeUserStatus, - deptTreeSelect + deptTreeSelect, approvalStatus } from '@/api/system/user' import { getToken } from '@/utils/auth' import Treeselect from '@riophae/vue-treeselect' import '@riophae/vue-treeselect/dist/vue-treeselect.css' import { validPwd } from '@/utils/validate' +import { hashWithSM3AndSalt } from '@/utils/sm' export default { name: 'User', @@ -423,6 +425,7 @@ export default { } }, created() { + console.log(hashWithSM3AndSalt('1234567890')) this.getList() this.getDeptTree() this.getConfigKey('sys.user.initPassword').then(response => { @@ -451,6 +454,7 @@ export default { this.loading = true listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => { this.userList = response.rows + console.log(this.userList) this.total = response.total this.loading = false } @@ -472,6 +476,17 @@ export default { this.queryParams.deptId = data.id this.handleQuery() }, + //用户审批状态修改 + handleApprovalStatus(row) { + this.$modal.confirm('确认要审批' + row.userName + '"用户吗?').then(function() { + console.log({ userId: row.userId }) + return approvalStatus({ userId: row.userId }) + }).then(() => { + this.$modal.msgSuccess('审批成功') + this.getList() + }).catch(function() { + }) + }, // 用户状态修改 handleStatusChange(row) { let text = row.status === '0' ? '启用' : '停用' @@ -536,6 +551,9 @@ export default { case 'handleAuthRole': this.handleAuthRole(row) break + case 'approvalStatus': + this.handleApprovalStatus(row) + break default: break }