feat(安全): 添加加密解密功能并更新考试组件

- 新增crypto-js依赖用于数据加密解密
- 添加decrypt.js工具文件实现AES解密功能
- 修改Exam.vue组件使用加密接口获取考试数据
- 清理main.js中多余空行并格式化代码
This commit is contained in:
yindongqi 2025-08-15 10:49:17 +08:00
parent efb38e1106
commit f7eca311fa
5 changed files with 7157 additions and 7576 deletions

View File

@ -19,6 +19,7 @@
"console": "^0.7.2", "console": "^0.7.2",
"core-js": "^3.39.0", "core-js": "^3.39.0",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"crypto-js": "^4.2.0",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"exif-js": "^2.3.0", "exif-js": "^2.3.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",

View File

@ -116,7 +116,8 @@
</div> </div>
</div> </div>
</mt-popup> </mt-popup>
<mt-popup v-model="popupVisible2" popup-transition="popup-fade" style="background-color: #5e5e5e; border-radius: 20px;"> <mt-popup v-model="popupVisible2" popup-transition="popup-fade"
style="background-color: #5e5e5e; border-radius: 20px;">
<div style=" top: 5px; font-size: 20px; font-weight: bold; margin: 20px 0; color: white;">请选择要考试的课程</div> <div style=" top: 5px; font-size: 20px; font-weight: bold; margin: 20px 0; color: white;">请选择要考试的课程</div>
<div style=" margin: 30px; width: 250px;" v-for="(item, index) in courseData" :key="index"> <div style=" margin: 30px; width: 250px;" v-for="(item, index) in courseData" :key="index">
<mt-button @click="btn_courseID(item.id)" type="default" class="" <mt-button @click="btn_courseID(item.id)" type="default" class=""
@ -143,6 +144,7 @@ import { getStore } from "@/utils/storage";
import { Toast } from "mint-ui"; import { Toast } from "mint-ui";
import { MessageBox } from "mint-ui"; import { MessageBox } from "mint-ui";
import WXTake from "../study/weixinTake.vue"; import WXTake from "../study/weixinTake.vue";
import { decrypt } from "@/utils/decrypt";
export default { export default {
name: "LiuYan", name: "LiuYan",
components: { Back, WXTake }, components: { Back, WXTake },
@ -185,11 +187,11 @@ export default {
token: getStore("token"), token: getStore("token"),
}).then((data) => { }).then((data) => {
this.courseData = data.data; this.courseData = data.data;
if(this.courseData.length === 1){ if (this.courseData.length === 1) {
this.btn_courseID(this.courseData[0].id); this.btn_courseID(this.courseData[0].id);
}else if(this.courseData.length > 1){ } else if (this.courseData.length > 1) {
this.popupVisible2 = true; this.popupVisible2 = true;
}else { } else {
Toast("暂无考试"); Toast("暂无考试");
} }
}); });
@ -267,19 +269,20 @@ export default {
var current_time_stamp = new Date().getTime(); var current_time_stamp = new Date().getTime();
// //
this.getData("/Question/getExamQuestions", { // this.getData("/Question/getExamQuestions", {
this.getData("/Question/getExamQuestionsSecurity", {
token: getStore("token"), token: getStore("token"),
course_id: this.courseID, course_id: this.courseID,
}).then( }).then(
(data) => { (data) => {
// console.log("data=>",data.data.exam_data);
if (data.code == 1) { if (data.code == 1) {
const questions = decrypt(data.data);
let _this = this; let _this = this;
this.is_start = 1; this.is_start = 1;
this.id = data.data.id; this.id = questions.id;
this.questions = data.data.question_data; this.questions = questions.question_data;
this.current_question_data = data.data.question_data[0]; this.current_question_data = questions.question_data[0];
this.exam_data = data.data.exam_data; this.exam_data = questions.exam_data;
this.title = "正式考试"; this.title = "正式考试";
this.monishow = false; this.monishow = false;
var count = 1; var count = 1;
@ -873,6 +876,7 @@ export default {
align-items: center; align-items: center;
z-index: 1000; z-index: 1000;
} }
.mask-content { .mask-content {
background: white; background: white;
padding: 30px; padding: 30px;
@ -881,6 +885,7 @@ export default {
width: 80%; width: 80%;
max-width: 400px; max-width: 400px;
} }
.mask-content p { .mask-content p {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 16px; font-size: 16px;

View File

@ -25,7 +25,6 @@ import "regenerator-runtime/runtime";
let wx = require('weixin-js-sdk') let wx = require('weixin-js-sdk')
Vue.config.productionTip = false Vue.config.productionTip = false
// mintui模块 // mintui模块
Vue.component(Button.name, Button) Vue.component(Button.name, Button)
Vue.component(Swipe.name, Swipe); Vue.component(Swipe.name, Swipe);

29
src/utils/decrypt.js Normal file
View File

@ -0,0 +1,29 @@
import CryptoJS from 'crypto-js'
const SECRET_KEY = 'mIS*fo4T2ioFSw91Flaovn@ofiq89Fqe';
export function decrypt(encryptedResponse) {
// 1. Base64解码
const iv = CryptoJS.enc.Base64.parse(encryptedResponse.iv)
const payload = CryptoJS.enc.Base64.parse(encryptedResponse.payload)
// 2. 执行AES解密
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: payload },
CryptoJS.enc.Utf8.parse(SECRET_KEY),
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
)
// 3. 转为UTF-8字符串并解析JSON
try {
return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8))
} catch (e) {
console.error("解密失败: ", e)
return null
}
}

14669
yarn.lock

File diff suppressed because it is too large Load Diff