前文
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.οnclick=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.οnclick=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