feat: 数据安全-加密解密与掩码

master
LCJ-MinYa 3 months ago
parent cbe629746e
commit 3a961fdf39

@ -122,6 +122,10 @@ const titleArr = [
key: 'chat',
title: '聊天(智能客服)(移动端查看)',
},
{
key: 'dataSafe',
title: '数据安全-加密解密与掩码',
},
];
// @/views/demo/**/*.vue

@ -0,0 +1,83 @@
<template>
<el-form-item
:label="label"
:prop="propName"
@click.stop.prevent
><el-input
v-model="formData[propName]"
placeholder="请输入"
:value="maskVal"
@blur="maskValBlur(true)"
@focus="maskValFocus"
></el-input
></el-form-item>
</template>
<script>
import { IdNumberFilter, phoneFilter, nameMask, carVinFilter, carBoardNumFilter, cardNoFilter } from '../utils/textMask';
export default {
name: 'EditMaskVal',
props: {
label: String,
formData: Object,
propName: String,
typeName: String,
},
data() {
return {
maskVal: '',
};
},
watch: {
formData: {
handler() {
this.maskValBlur(false);
},
immediate: true,
},
//
'formData.carVin': {
handler() {
if (this.propName != 'carVin') {
return;
}
this.maskValBlur(false);
},
},
},
methods: {
//
maskValBlur(shouldEmit) {
switch (this.typeName) {
case 'IdNumberFilter':
this.maskVal = IdNumberFilter(this.formData[this.propName], 'ON');
break;
case 'phoneFilter':
this.maskVal = phoneFilter(this.formData[this.propName]);
break;
case 'nameMask':
this.maskVal = nameMask(this.formData[this.propName]);
break;
case 'carVinFilter':
this.maskVal = carVinFilter(this.formData[this.propName]);
break;
case 'carBoardNumFilter':
this.maskVal = carBoardNumFilter(this.formData[this.propName], 'ON');
break;
case 'cardNoFilter':
this.maskVal = cardNoFilter(this.formData[this.propName]);
break;
default:
break;
}
if (shouldEmit) {
this.$emit('currentBlur');
}
},
//
maskValFocus() {
this.maskVal = this.formData[this.propName];
},
},
};
</script>

@ -0,0 +1,135 @@
<template>
<div class="p-5 space-y-5 !bg-gray-100">
<el-card header="测试加解密掩码场景">
<el-form
ref="formRef"
:model="formData"
label-width="140px"
>
<el-row>
<el-col :span="12">
<editMaskVal
label="姓名"
:formData="formData"
:propName="'name'"
:typeName="'nameMask'"
/>
</el-col>
<el-col :span="12">
<el-form-item
label="年龄"
prop="age"
>
<el-input v-model="formData.age" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="性别"
prop="sex"
>
<el-input v-model="formData.sex" />
</el-form-item>
</el-col>
<el-col :span="12">
<editMaskVal
label="手机号"
:formData="formData"
:propName="'tel'"
:typeName="'phoneFilter'"
/>
</el-col>
<el-col :span="12">
<editMaskVal
label="银行卡号"
:formData="formData"
:propName="'cardNo'"
:typeName="'cardNoFilter'"
/>
</el-col>
<!--车牌号改变的时候清空车架号-->
<el-col :span="12">
<editMaskVal
label="车牌号"
:formData="formData"
:propName="'carNo'"
:typeName="'carBoardNumFilter'"
@currentBlur="changeCarNo"
/>
</el-col>
<el-col :span="12">
<editMaskVal
label="车架号"
:formData="formData"
:propName="'carVin'"
:typeName="'carVinFilter'"
/>
</el-col>
<el-col :span="12">
<editMaskVal
label="身份证号"
:formData="formData"
:propName="'idNo'"
:typeName="'IdNumberFilter'"
/>
</el-col>
</el-row>
<div class="flex justify-center">
<el-button
type="primary"
class="w-40"
:loading="loading"
@click="doSubmit"
>提交</el-button
>
</div>
</el-form>
</el-card>
</div>
</template>
<script setup lang="jsx">
import { ref, onMounted } from 'vue';
import mockData from './utils/mockData';
import editMaskVal from './components/editMaskVal.vue';
import { decrypt, encrypt } from './utils/paEncrypt';
import { ElMessageBox } from 'element-plus';
const formData = ref({});
const loading = ref(false);
const formRef = ref();
onMounted(() => {
initData();
});
const initData = () => {
setTimeout(() => {
decrypt(mockData, ['name', 'tel', 'cardNo', 'carNo', 'carVin', 'idNo']);
formData.value = mockData;
console.log('获取接口后解密的数据', formData.value);
}, 200);
};
const doSubmit = () => {
loading.value = true;
setTimeout(() => {
const { ...submitInfo } = formData.value;
encrypt(submitInfo, ['name', 'tel', 'cardNo', 'carNo', 'carVin', 'idNo']);
ElMessageBox({
title: '提交成功',
message: <p>请打开控制台查看结果</p>,
});
console.log('提交给接口加密的数据', submitInfo);
console.log('本地表单数据', formData.value);
loading.value = false;
}, 1000);
};
const changeCarNo = () => {
formData.value.carVin = '';
};
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,19 @@
/**
* 这里模拟后端返回的数据
* 需要加密返回
*/
import { str2binb } from './paEncrypt';
const result = {
name: str2binb('洛子息'),
tel: str2binb('18888888888'),
cardNo: str2binb('6228480000000000000'),
carNo: str2binb('浙A88888'),
carVin: str2binb('5YJSA19A7DF109075'),
idNo: str2binb('110101199901011234'),
// 普通数据
age: 18,
sex: '男',
};
export default result;

@ -0,0 +1,50 @@
/**
* 加解密随便使用的一种(XOR还原)可以自行更换
*/
//解密
export function binb2str(para) {
if (para == null || para == '') return '';
var data = para.split('_');
var str = '';
var chrsz = 16;
var mask = (1 << chrsz) - 1;
for (var i = 0; i < data.length * 32; i += chrsz) {
var asciiCode = (data[i >> 5] >>> (32 - chrsz - (i % 32))) & mask;
if (asciiCode > 0) {
str += String.fromCharCode(asciiCode);
}
}
return str;
}
//加密
export function str2binb(str) {
if (str == null || str == '') return '';
var chrsz = 16;
var splitStr = '_';
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz) {
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - (i % 32));
}
var s = '';
for (var i = 0; i < bin.length; i++) {
s += splitStr + bin[i];
}
return s.substring(1);
}
//业务数据解密
export function decrypt(data, propNameArray) {
propNameArray.forEach((element) => {
data[element] = binb2str(data[element]);
});
}
//业务数据加密
export function encrypt(data, propNameArray) {
propNameArray.forEach((element) => {
data[element] = str2binb(data[element]);
});
}

@ -0,0 +1,101 @@
/**
* 手机号脱敏
* @name phoneFilter
* @param {*} phone
*/
export const phoneFilter = (phone) => {
if (phone != null) {
return phone.replace(/^(.{3}).+(.{4})$/, '$1****$2');
}
};
/**
* 身份证号脱敏
* @name IdNumberFilter
* @param {*} IdNum
*/
export const IdNumberFilter = (IdNum, sensitiveSwitch = 'OFF') => {
if (IdNum != null) {
if (IdNum.length <= 8) {
return IdNum.replace(/^(.{2}).+(.{2})$/, '$1****$2');
} else {
if (sensitiveSwitch === 'ON') {
return IdNum.replace(/^(.{2}).+(.{2})$/, '$1****$2');
} else {
return IdNum.replace(/^(.{4}).+(.{4})$/, '$1****$2');
}
}
}
};
/**
* 车架号脱敏
* @name carVinFilter
* @param {*} carVin
*/
export const carVinFilter = (carVin) => {
if (carVin != null) {
return carVin.replace(/^(.{4}).+(.{4})$/, '$1****$2');
}
};
/**
* 车牌号脱敏
* @name carBoardNumFilter
* @param {*} carBoardNum
*/
export const carBoardNumFilter = (carBoardNum, sensitiveSwitch = 'OFF') => {
if (carBoardNum != null) {
if (sensitiveSwitch === 'ON') {
return carBoardNum.replace(/^(.{3}).+(.{1})$/, '$1****$2');
} else {
return carBoardNum.replace(/^(.{2}).+(.{2})$/, '$1****$2');
}
}
};
/**
* 邮箱脱敏
* @name emailFilter
* @param {*} email
*/
export const emailFilter = (email) => {
if (email != null) {
return email.replace(/^(.{3}).+(.{4})$/, '$1****@$2');
}
};
/**
* 姓名脱敏
* @name nameMask
* @param {*} email
*/
export const nameMask = (name) => {
if (name) {
return name.slice(0, 1) + '*'.repeat(name.length - 1);
}
};
export const cardNoMask = (no) => {
if (!no) {
return '';
}
if (no.length > 7) {
return no.slice(0, 3) + '*'.repeat(no.length - 7) + no.slice(-4);
}
return '*******';
};
/**
* 银行卡号脱敏
* @name cardNoFilter
* @param {*} cardNo
*/
export const cardNoFilter = (cardNo) => {
if (cardNo != null) {
return cardNo.replace(/^(.{4}).+(.{4})$/, '$1****$2');
}
};
Loading…
Cancel
Save