前文
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(一)答案获取
Spring Boot——易班优课YOOC课群在线测试自动答题解决方案(二)答案储存
Spring Boot——易班优课YOOC课群在线测试自动答题解决方案(三)答案查询
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(四)答案显示
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(五)简单脚本
Spring Boot——易班优课YOOC课群在线测试自动答题解决方案(六)后端改造
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(七)随机答案
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(八)功能面板
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(九)ID标签
Vue + Element UI + Spring Boot——易班优课YOOC课群在线测试自动答题解决方案(十)问题管理页面
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(十一)恢复右键、选择和复制
辅助工具
问题分析
考试列表页
重做按钮
每次练习以后,重做按钮的链接都会发生改变,这个链接可以通过考试页面一个参数生成。
对整个考试页面就行正则匹配即可
let examuser=/var AnswerData = JSON.parse\(localStorage.getItem\("exam(.*)"\)\) \|\| {};/.exec(res.responseText)[1]
console.log(examuser)
考试页
提交按钮事件
submitAnswer()方法
如果直接使用 submitAnswer()方法,服务端的校验不能通过。
_AnswerData()方法和AnswerData
题目事件监听
答案保存
大致流程:点击选项/或者填写内容 -> 触发事件监听 -> 保存答案到浏览器缓存和服务器 -> 提交试卷
答案页
考试信息
题目ID
答案隐藏脚本
答案上传
JavaScript——易班优课YOOC课群在线测试自动答题解决方案(一)答案获取
解决方案
前端
// ==UserScript==
// @name YOOC Exams
// @namespace http://tampermonkey.net/
// @version 0.2
// @description try to take over the world!
// @author STZG
// @match https://www.yooc.me/group/*/exams*
// @grant none
// ==/UserScript==
(function() {
var ajax=(options)=>{
// 1. 首先简单验证传进来的参数是否合法
if(!options) return undefined;
// 2. 对参数容错处理
options.method = options.method ? options.method.toUpperCase() : 'GET'; // 默认 GET 请求
options.data = options.data || {};
options.type = options.type || 'FormData';
if(options.type==='JSON'){
options.data = JSON.stringify(options.data)
}else if(options.type==='FormData'){
var formData = [];
for(let key in options.data) { // Object.keys.forEach
formData.push(''.concat(key, '=', options.data[key]))
}
options.data = formData.join('&') //eg: a=b&c=d&e=f
}
// 3. 实例化 XMLHttpRequest 对象,并进行一些设置
var xmlhttp = new XMLHttpRequest();//获取对象
// 4. 处理请求回调
xmlhttp.onreadystatechange = function(){//设置回调函数
if(xmlhttp.readyState == 4){//这里的4是请求的状态码,代表请求已经完成
if(xmlhttp.status == 200 || xmlhttp.status == 304){//这里是获得响应的状态码,200代表成功,304代表无修改可以直接从缓存中读取
options.success(xmlhttp)
}else if(xmlhttp.status==500||xmlhttp.status==404){
options.failure(xmlhttp)
}else {
options.failure(xmlhttp)
}
}
}
// 5. 打开请求
xmlhttp.open(options.method,options.url,true);
// 6. 设置请求头
if(options.header){
for(let key in options.header){
xmlhttp.setRequestHeader(key, options.header[key])
}
}
// 7. 发送请求
xmlhttp.send(options.method==='POST'?options.data:null);//GET请求
}
//获取考试信息
var group=document.getElementById('group-data')
var groupId=group.getAttribute("data-group-id")
var csrf=group.getAttribute("data-csrf")
var auth=group.getAttribute("data-auth")
var seeExam=(exam)=>{
let a_score=exam.getElementsByClassName('score')[0]
if(a_score&&a_score.innerHTML==='禁止查卷'){
let t_bgc=document.getElementsByClassName('t-bgc fl')[7]
let edit_history_url=t_bgc.getElementsByTagName('a')[0].href
a_score.href=edit_history_url.replace('edit_history','detail')
a_score.innerHTML='查看详情'
}
}
var autoPractice=(exam)=>{
let search=exam.getElementsByClassName('board-bottom')[0]
if(search){
return
}
let examId=exam.getAttribute("data-exam-id")
let sum=Number(/(.*)\u9898/.exec(exam.getElementsByClassName('board-det')[0].childNodes[1]
.getElementsByTagName('span')[0]
.innerText.trim())[1])
let is_repeat=exam.getElementsByClassName('board-left')[0].childNodes[25].innerText.trim()==='允许'
let template='<div class="fl board-bottom robot" style="width: 770px;"><div class="fl board-left" style=" height: 40px!important;line-height: 40px;font-size: 14px;padding: 0 10px;"><div class="progress-tar" style="width:70%;display: inline-block;"><span style="line-height: 20px;height: 20px;">刷题进度:</span><progress class="progress" value="30" max="100" style="width: 60%;border-radius: 2px;border-left: 1px #ccc solid;border-right: 1px #ccc solid;border-top: 1px #aaa solid;background-color: #eee;margin-bottom: 1px;">您的浏览器不支持progress元素</progress><span style="margin: 10px; line-height: 20px;"><span class="count-practice">0</span>/<span class="sum-practice"></span></span></div><div style="width: 30%;text-align: center;display: inline-block;"><span class="status-practice" style="margin: 10px;">正在xxxx</span></div></div><div class="fl board-right" style="height: 40px!important;line-height:40px;font-size:14px;background-color: #fe8333;cursor: pointer;color: white;"><div class="button-practice"></div></div></div>'
//添加伪元素CSS
document.styleSheets[0].addRule('progress::-webkit-progress-bar','background-color: #d7d7d7;'); // 支持IE
document.styleSheets[0].addRule('progress::-webkit-progress-value','background-color: #aadd6a;'); // 支持IE
let board_bottom=document.createElement('div')
board_bottom.innerHTML=template
board_bottom=board_bottom.childNodes[0]
exam.appendChild(board_bottom)
let button_practice=board_bottom.getElementsByClassName('button-practice')[0]
let count_practice=board_bottom.getElementsByClassName('count-practice')[0]
let sum_practice=board_bottom.getElementsByClassName('sum-practice')[0]
let status_practice=board_bottom.getElementsByClassName('status-practice')[0]
let progress=board_bottom.getElementsByClassName('progress')[0]
console.log(exam)
console.log(board_bottom)
console.log(button_practice)
console.log(count_practice)
console.log(progress)
sum_practice.innerHTML=sum
progress.value=0/sum*100
count_practice.innerHTML=0
let updateFlag=false;
let updateStatus=(now_sum)=>{
if(updateFlag){
return
}else{
let timer=setInterval(()=>{
let now_num=Math.round(Number(count_practice.innerHTML))
let cmp=now_sum-now_num
if(cmp===0){
updateFlag=false;
clearInterval(timer);
}else{
if(now_num===0){
now_num=1;
}else{
now_num=now_num+Math.round(cmp/Math.abs(cmp))
}
count_practice.innerHTML=now_num
progress.value=now_num/sum*100
}
},50)
}
}
let refreshStatusSum=()=>{
ajax({
url:'https://localhost/MyZSTU/yooc/group/'+groupId+'/exam/'+examId+'/answer/total',
method:'get',
success:(res)=>{
updateStatus(JSON.parse(res.responseText).data)
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
}
})
}
if(is_repeat){
button_practice.innerHTML="开始自动刷题"
let autoPracticeStatus=false
button_practice.onclick=e=>{
let exam_status=0
let repeat=exam.getElementsByClassName('repeat')[0]
if(repeat){
if(autoPracticeStatus){
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}else{
autoPracticeStatus=true
button_practice.innerHTML="关闭自动刷题"
let repeat_url=repeat.getAttribute("repeat-url")
let start_exam=exam.getElementsByClassName('start_exam')[0]
if(start_exam){
exam_status=0
}else if(repeat){
exam_status=4
}
let autoPracticeMain=()=>{
if(!autoPracticeStatus){return}
console.log("start")
status_practice.innerHTML="刷题开始"
console.log("apply-repeat")
status_practice.innerHTML="申请重做"
ajax({
url: repeat_url,
method: 'post',
header:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRFToken':csrf},
data: {'csrfmiddlewaresretoken': csrf},
success: (res)=>{
if(!autoPracticeStatus){return}
status_practice.innerHTML="申请重做成功"
let data=JSON.parse(res.responseText)
if(data.result){
console.log('申请重做成功')
if(data.url){
console.log("practice")
if(!autoPracticeStatus){return}
status_practice.innerHTML="申请考试页面"
ajax({
url: data.url,
method: 'get',
success: (res)=>{
if(!autoPracticeStatus){return}
status_practice.innerHTML="自动练习开始"
let examuser=/var AnswerData = JSON.parse\(localStorage.getItem\("exam(.*)"\)\) \|\| {};/.exec(res.responseText)[1]
console.log(examuser)
repeat_url='https://www.yooc.me/group/'+groupId+'/exam/'+examId+'/examuser/'+examuser+'/repeat'
let practice=document.createElement('html')
practice.innerHTML=res.responseText
console.log(practice)
//获取问题信息
var question=Array.from(practice.getElementsByClassName('question-board'))
let AnswerData={}
question.forEach(q=>{console.log(q)
let inputTag=q.getElementsByTagName('input')
console.log(inputTag)
if(inputTag.length>0){
let Ele=inputTag[0]
if(Ele.type==="radio"||Ele.type==="checkbox"){
let arr=Ele.id.split('_')
let questionId=arr[0]
let name=arr[1]
let qData=[arr[2]]
AnswerData[questionId] = {};
AnswerData[questionId][name] = qData;
}else if(Ele.type==="text"){
let Eles=Array.from(inputTag)
Eles.forEach(e=>{
e.value="test"
let arr=e.id.split('_')
let questionId=arr[0]
let name=arr[1]
AnswerData[questionId] = {};
if (AnswerData[questionId][name]===undefined) {
AnswerData[questionId][name] = [e.value.trim()];
} else {
AnswerData[questionId][name].push(e.value.trim());
}
})
}
}
})
status_practice.innerHTML="自动练习完成"
console.log("save")
localStorage.setItem("exam"+examuser,JSON.stringify(AnswerData));
status_practice.innerHTML="提交答案"
ajax({
url: 'https://www.yooc.me/group/'+groupId+'/exam/'+examId+'/answer/save',
method: 'post',
type:'JSON',
header:{'Content-Type':'application/json; charset=UTF-8',
'X-CSRFToken':csrf},
data: AnswerData,
success: (res)=>{
if(!autoPracticeStatus){return}
status_practice.innerHTML="提交答案成功"
console.log("submit")
var submitUrl = 'https://www.yooc.me/group/'+groupId+'/exam/'+examId+'/answer/submit';
var _AnswerData = [];
for(obj in AnswerData){
var m = {};
m[obj] = AnswerData[obj];
_AnswerData.push(m);
}
var postData = {
'csrfmiddlewaretoken': csrf,
'answers': JSON.stringify(_AnswerData),
'type': 0,
'auto': 0,
'completed':1
};
if(!autoPracticeStatus){return}
status_practice.innerHTML="提交试卷"
ajax({
url: submitUrl,
method:'post',
header:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRFToken':csrf},
data: postData,
success: (res)=>{
if(!autoPracticeStatus){return}
status_practice.innerHTML="提交试卷成功"
console.log("upload")
status_practice.innerHTML="申请答案页面"
ajax({
url: 'https://www.yooc.me/group/'+groupId+'/exam/'+examId+'/detail',
method: 'get',
success: (res)=>{
if(!autoPracticeStatus){return}
status_practice.innerHTML="答案分析"
console.log(res);
//创建DOM
var html=document.createElement("html");
html.innerHTML=res.responseText
console.log(html)
//获取问题信息
var question=Array.from(html.getElementsByClassName('question-board'))
console.log(question)
//数据封装
var question_arr=[]
question.forEach(q=>{
question_arr.push({id:q.id,question:q.outerHTML
.replace(/the-ans fls/g,"the-ans crt")
.replace(/<li class="crt"/g,'<li class=""')
.replace(/<li class="fls"/g,'<li class=""')})
})
//上传服务器
if(!autoPracticeStatus){return}
status_practice.innerHTML="上传答案"
ajax({
url:"https://localhost/MyZSTU/yooc/group/"+groupId+"/exam/"+examId+"/upload",
method:'POST',
type:'JSON',
header:{"Content-Type":"application/json"},
data:question_arr,
success: (res)=>{
if(!autoPracticeStatus){return}
status_practice.innerHTML="上传成功"
console.log("end")
ajax({
url:'https://localhost/MyZSTU/yooc/group/'+groupId+'/exam/'+examId+'/answer/total',
method:'get',
success:(res)=>{
if(!autoPracticeStatus){return}
console.log("update")
status_practice.innerHTML="更新状态"
updateStatus(JSON.parse(res.responseText).data)
console.log("reboot")
status_practice.innerHTML="正在重新开始"
autoPracticeMain()
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
})
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
});
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
});
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
});
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
})
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
})
}
}else{
//页面方法
xAlert(data.message);
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
},
failure:(e)=>{
//页面方法
xAlert('失败','网络请求失败')
autoPracticeStatus=false
button_practice.innerHTML="开始自动刷题"
window.location.href=document.URL
}
});
}
autoPracticeMain()
}
}else{
//页面方法
xAlert('失败','无法获取重做按钮,请测试允许反复练习并且确保有重做按钮在页面上')
}
}
}else{
button_practice.innerHTML="刷新刷题进度"
button_practice.onclick=e=>{
console.log("start")
console.log("get")
console.log("update")
refreshStatusSum()
console.log("end")
}
}
//初始化状态
refreshStatusSum()
status_practice.innerHTML=""
}
var start=()=>{
let exams_board=document.getElementsByClassName('exams-board')[0]
if(exams_board){
let exams=Array.from( exams_board.getElementsByTagName('li'))
if(exams||exams!==[]){
exams.forEach(exam=>{
autoPractice(exam)
seeExam(exam)
})
}else{
return
}
}
}
var int=self.setInterval(()=>{
start()
},1000);
})();
后端
答案上传
@ResponseBody
@RequestMapping(value = "/group/{groupId}/exam/{examId}/upload",method = RequestMethod.POST)
public Object uploadExam(@PathVariable("groupId")String groupId,
@PathVariable("examId")String examId,
@RequestBody List<QuestionDTO> questionDTOS,
HttpServletResponse response){
response.setHeader("Content-Security-Policy","upgrade-insecure-requests");
List<Question> questions = new ArrayList<>();
for (QuestionDTO questionDTO: questionDTOS) {
Question question =new Question(groupId,
examId,
questionDTO.getId().substring(9),
questionDTO.getQuestion());
questions.add(question);
//System.out.println(question);
}
iyoocExamQuestionService.saveOrUpdateBatch(questions);
return questions;
}
某考试的总答案数
@ResponseBody
@RequestMapping(value = "/group/{groupId}/exam/{examId}/answer/total",method = RequestMethod.GET)
public Object getAnswerTotalByQuestionId(@PathVariable("groupId")String groupId,
@PathVariable("examId")String examId){
int total = iyoocExamQuestionService.count(new QueryWrapper<>(new Question(groupId, examId)));
ApiResponse retTemp = ApiResponseUtil.getRetTemp();
retTemp.setData(total);
return retTemp;
}
运行结果
参考文章
https://shentuzhigang.blog.csdn.net/article/details/105878462
https://shentuzhigang.blog.csdn.net/article/details/105878607
https://shentuzhigang.blog.csdn.net/article/details/105847036